[转载]如何在C++03中模拟C++11的右值引用std::move特性
本文摘自: http://adamcavendish.is-programmer.com/posts/38190.htm
引言
众所周知,C++11 的新特性中有一个非常重要的特性,那就是 rvalue reference ,右值引用。
引入它的一个非常重要的原因是因为在 C++ 中,常常右值,通俗地讲"在等号右边的"临时变量或者临时对象,我们是无法得到它的修改权限的。
由于类的构造和析构机制,往往产生的临时变量或临时对象的拷贝构造及析构,会带来不少的时间、资源消耗。
也同样由于这样的限制,有不少 C++ 程序员依然保有一部分 C 风格的写法,例如将 A = factory(B,
C); 之中的A,以函数引用参数的形式传入等等。但在 C++11 之后,我们可以完全保留 C++ 的写法,将右值明显指出,就可以完成 "直接获得临时对象" 的资源的权限,例如 A = std::move(B); 或者 A = factory(B,
C); ,这时候就 "几乎完全" 省去了拷贝的过程,通过直接获取由 factory(B,
C); 造出的临时对象中的资源,达到省略拷贝的过程,最终析构的临时对象,实际上只是一具空空的皮囊。
以下有一个简单的右值引用的例子:(注,本文中的例子仅仅只是例子,请大家不要使用这种风格)
/** * Please use g++ -std=c++0x or g++ -std=c++11 to compile. */ #include <iostream> #include <string> #include <cstring> template <typename T, std::size_t Num> class Array { public: T * _M_data; Array() : _M_data(new T[Num]) {} // default constructor Array(const Array & rhs) : _M_data(new T[Num]) { std::cout << "Copy Constructor." << std::endl; memcpy(_M_data, rhs._M_data, sizeof(T)*Num); } // copy constructor // Move constructor -- from rvalue Array(Array && rhs) : _M_data(rhs._M_data) { std::cout << "Move Constructor." << std::endl; rhs._M_data = nullptr; } // move constructor Array & operator=(const Array & rhs) { std::cout << "Copy Assignment." << std::endl; if (this == &rhs) return (*this); memcpy(_M_data, rhs._M_data, sizeof(T)*Num); return (*this); } // copy assignment // Move Assignment -- from rvalue Array & operator=(Array && rhs) { std::cout << "Move Assignment." << std::endl; if (this == &rhs) return (*this); _M_data = rhs._M_data; rhs._M_data = nullptr; return (*this); } // move assignment ~Array() { std::cout << "destructor." << std::endl; delete[] _M_data; } // destructor static Array factory(const T & __default_val) { Array __ret; for (auto __i = 0ul; __i < Num; ++__i) __ret._M_data[__i] = __default_val; return __ret; } // factor(defalt_value) void print(const std::string & __info) const { std::cout << __info; if (_M_data == nullptr) { std::cout << "_M_data is nullptr." << std::endl; return; } for(auto __i = 0ul; __i < Num; ++__i) std::cout << _M_data[__i] << ' '; std::cout << std::endl; } }; int main() { const std::size_t NUM = 10ul; Array<int, NUM> a0; for (auto __i = 0ul; __i < NUM; ++__i) a0._M_data[__i] = __i; a0.print("a0: "); Array<int, NUM> a1(a0); a0.print("a0: "); a1.print("a1: "); Array<int, NUM> a2(std::move(a1)); a1.print("a1: "); a2.print("a2: "); Array<int, NUM> a3; a3.print("a3(uninitialized): "); a3 = a2; a3.print("a3: "); a3 = Array<int, NUM>::factory(1024); a3.print("a3: "); std::cout << "----------" << std::endl; return 0; }
模拟原理介绍
01. 屏蔽普通的 copy assignment
由于我们要使用 C++03 的特性模拟右值引用 (rvalue-reference) ,所以最重要的就是要先获得对临时对象的访问权限。
故优先考虑的是
a3 = Array<int, NUM>::factory(1024);
的实现。
我们考虑我们平时代码中的 operator = 的重载函数,一般 C++03 中处理以上这句代码的,是使用 Array & opeartor =(const Array & rhs); 函数的,即我们平常说的 copy assign operation 。
同时,由于 factory(1024); 返回的是一个Array,当它是临时对象时,它不能被修改,所以不能绑定到Array &类型,而只能绑定到 const Array & 类型上。如果我们要简单屏蔽普通的 copy assignment ,那么最方便的,就是直接去除 Array & operator =(const Array & rhs); 函数。
在去除那个函数之后,你发现你的编译失败了。这就对了 :)
以下为编译失败的代码:
#include <iostream> #include <string> #include <cstring> template <typename T, std::size_t Num> class Array { public: T * _M_data; Array() : _M_data(new T[Num]) {} // default constructor Array(const Array & rhs) : _M_data(new T[Num]) { std::cout << "Copy Constructor." << std::endl; memcpy(_M_data, rhs._M_data, sizeof(T)*Num); } // copy constructor private: // Use private to block/disable default functions generated by c++ Array & operator =(const Array & rhs) { std::cout << "Copy Assignment." << std::endl; if (this == &rhs) return (*this); memcpy(_M_data, rhs._M_data, sizeof(T)*Num); return (*this); } // copy assignment public: ~Array() { std::cout << "destructor." << std::endl; delete[] _M_data; } // destructor static Array factory(const T & __default_val) { Array __ret; for (std::size_t __i = 0ul; __i < Num; ++__i) __ret._M_data[__i] = __default_val; return __ret; } // factor(defalt_value) void print(const std::string & __info) const { std::cout << __info; if (_M_data == NULL) { std::cout << "_M_data is nullptr." << std::endl; return; } for (std::size_t __i = 0ul; __i < Num; ++__i) std::cout << _M_data[__i] << ' '; std::cout << std::endl; } }; int main() { const std::size_t NUM = 10ul; Array<int, NUM> a0; for (std::size_t __i = 0ul; __i < NUM; ++__i) a0._M_data[__i] = __i; a0.print("a0: "); Array<int, NUM> a1(a0); a0.print("a0: "); a1.print("a1: "); Array<int, NUM> a3; a3.print("a3(uninitialized): "); a3 = Array<int, NUM>::factory(1024); a3.print("a3: "); std::cout << "----------" << std::endl; return 0; }
02. 获得临时对象的访问权限
由于我们无法直接对临时对象进行更改,所以在不改变函数 factory() 的情况下(改了函数就没有意义了),我们只能将其转换为另一个对象类型。这时候我们就要使用 conversion operator/cast operator 了,将其转换成我们可以具有修改权限的类型 -- 例如opeartor T &()。
03. 封装通用conversion类
由于我们需要02中所述的具有可修改性的T类型,这个类型的基本要求如下:
a. 拥有被转换类型的所有成员变量和成员函数,能够自由支配类型中的任意资源
b. 没有时间和空间上的性能损耗
c. 通用性,即不需要为每一个类都重写这个T类型
所以,鉴于此,我们需要使用泛型、继承这两个非常重要的工具。
template <typename T> class rv : public T { public: rv() {}; rv(const rv & rhs) {}; ~rv() {}; void operator =(const rv & rhs) {}; }; // class rv
至此,我们就可以通过撰写 Array & operator =(rv<Array> & rhs); 函数来完成我们的 move assignment 了。不过在转换与函数调用之间还差几小步。
04. 提供 move constructor 和 move assignment 接口(这里是机理最重要的部分)
这里要做的是,在我们的 Array 类内提供 move constructor 和 move assignment 的接口。
A:
希望:a = factory(); 时能够产生隐式转换,自动转换到 rv<Array> 类型。便于捕捉资源,区分 a = factory(); 和 a = a0; 之间的差别。
做法:提供 conversion opeartor 。
// conversion operator -- convert to "rv<Array> &"
// conversion operator rv<Array> &() operator rv<Array> &() { return *(static_cast<rv<Array> *>(this)); } // When the factory returns a const object
// conversion operator const rv<Array> &() operator const rv<Array> &() const { return *(static_cast<const rv<Array> *>(this)); }
B:
希望:a = factory(); 时调用的是 Array & operator =(rv<Array> & rhs); 接口。
做法:将原本设定为 private 的用于屏蔽 a = factory(); 的接口 Array & opeartor =(const Array
& rhs); 改写为用于间接调用 Array & operator =(rv<Array> &
rhs); 接口的方式。
// Lead to the correct move assignment emulation operator // operator =(const Array & rhs) Array & operator =(const Array & rhs) { this->operator =(static_cast< rv<Array> & >( const_cast< Array & >(rhs))); return (*this); }
C:
希望:区分 a = factory(); 和 a = a0; 的调用方式。分别调用模拟的 move assignment 和模拟的 copy assignment 。
做法:由于 a = a0; 既可以匹配 operator =(const Array & rhs) 又可以匹配 operator =(Array
& rhs) ,所以只需要分别撰写两个函数就可以达到区分的目的。另外,因为原本标准的 copy assignment 已经被使用作为 move
assignment 函数的跳板,即上面B解决的问题,所以我们需要重写一个用于 copy assignment 。
// Lead to the correct copy assignment emulation operator // operator =(Array & rhs) Array & operator =(Array & rhs) { this->operator =(static_cast<const rv<Array> &>( const_cast<const Array &>(rhs))); return (*this); }
// Copy Assignment -- emulated. // copy assignment operator =(const rv & rhs) Array & operator =(const rv<Array> & rhs) { std::cout << "Copy Assignment." << std::endl; if (this == &rhs) return (*this); memcpy(_M_data, rhs._M_data, sizeof(T) * Num); return (*this); }
我简单总结比较了一下 C++11 和 C++03 两个之间写法的差别:
C++11 的实现:
/** * Standard c++11 style. */ // Move constructor -- from rvalue Array(Array && rhs) : _M_data(rhs._M_data) { std::cout << "Move Constructor." << std::endl; rhs._M_data = nullptr; } // move constructor Array & operator =(const Array & rhs) { std::cout << "Copy Assignment." << std::endl; if (this == &rhs) return (*this); memcpy(_M_data, rhs._M_data, sizeof(T) * Num); return (*this); } // copy assignment // Move Assignment -- from rvalue Array & operator =(Array && rhs) { std::cout << "Move Assignment." << std::endl; if (this == &rhs) return (*this); _M_data = rhs._M_data; rhs._M_data = nullptr; return (*this); } // move assignment
C++03 的实现:
/** * Emulated rvalue-style */ // Lead to the correct copy assignment emulation operator Array & operator =(Array & rhs) { this->operator =(static_cast< const rv<Array> & >( const_cast<const Array &>(rhs))); return (*this); } // operato r=(Array & rhs) // Lead to the correct move assignment emulation operator Array & operator =(const Array & rhs) { this->operator =(static_cast< rv<Array> & >( const_cast<Array &>(rhs))); return (*this); } // operator =(const Array & rhs) // conversion operator -- convert to "rv<Array> &" operator rv<Array> &() { return *(static_cast< rv<Array> * >(this)); } // conversion operator rv<Array> &() // ------------------------------ // Move constructor -- emulated. Array(rv<Array> & rhs) : _M_data(rhs._M_data) { std::cout << "Move Constructor." << std::endl; rhs._M_data = NULL; } // move constructor // Copy Assignment -- emulated. Array & operator =(const rv<Array><array> & rhs) { std::cout << "Copy Assignment." << std::endl; if (this == &rhs) return (*this); memcpy(_M_data, rhs._M_data, sizeof(T)*Num); return (*this); } // copy assignment operator =(const rv<Array><array> & rhs) // Move Assignment -- emulated. Array & operator =(rv<Array> & rhs) { std::cout << "Move Assignment." << std::endl; if (this == &rhs) return (*this); _M_data = rhs._M_data; rhs._M_data = NULL; return (*this); } // move assignment
05. std::move的实现
在完成我们的任务之前,我们最后还需要一个函数能够将左值转换成右值,以替代std::move()函数。
由于我们需要这个函数能适配所有的类型,所以它依然要使用泛型~
/** * @brief std::move Implementations */ template <typename T> inline rv<T> & move_emu(T & rhs) { return *(static_cast< rv<T> * >(&rhs)); } // move_emu(T &)
然而,单单使用 T & 作为参数是不够的。因为:
a. 如果传入的对象本身是右值,即本身是 rv<T> 类型,我们应该返回的是它本身,而不应该返回为 rv< rv<T> > 类型。
b. 如果传入的对象本身是 cons t保护的,我们不应该夺取它的资源。我们应该按照 std::move 的标准,调用 copy assignment 或者 copy constructor 。
(这样的意义常常在于写泛型的时候使用 std::move(T) ,我们并不知情 T 是什么类型,当为 const 的时候调用 copy functions 即可)
c. 我们还需要对每一个基本数据类型进行模板特化,例如 template <> inline int move_emu(int rhs) { return rhs; } 。(这个是体力活了 :) 自己做咯~)
/** * @brief used for std::move(const values); * -- call copy construction/assignment */ template <typename T> inline const T & move_emu(const T & rhs) { return rhs; } // move_emu(const T &) template <typename T> inline rv<T> & move_emu(rv<T> & rhs) { return rhs; } // move_emu(rv<T> &)
上面要写的 rv<T> 和 move_emu 以及 helper functions 都是具有一定的通用性。所以我们完全可以写在一个文件中,作为头文件包含进来即可:
/** * @file move_emu.hpp */ #pragma once template <typename T> class rv : public T { rv() {}; rv(const rv & rhs) {}; ~rv() {}; void operator =(const rv & rhs) {}; }; template <typename T> inline rv<T> & move_emu(T & rhs) { return *(static_cast< rv<T> * >(&rhs)); } /** * @brief used for std::move(const values); * -- call copy construction/assignment */ template <typename T> inline const T & move_emu(const T & rhs) { return rhs; } template <typename T> inline rv<T> & move_emu(rv<T> & rhs) { return rhs; } #define COPYABLE_AND_MOVABLE(TYPE) \ public: \ TYPE & operator =(TYPE & t) \ { this->operator =(static_cast<const rv<TYPE> &>(const_cast<const TYPE &>(t))); return *this;} \ public: \ operator rv<TYPE> &() \ { return *static_cast< rv<TYPE> * >(this); } \ operator const rv<TYPE> &() const \ { return *static_cast<const rv<TYPE> * >(this); } \ private: \ // #define COPY_ASSIGN_REF(TYPE) \ const rv< TYPE > & \ // #define RV_REF(TYPE) \ rv< TYPE > & \ //
这时候你在类中就可以直接使用 #define 的宏来简化你的写法和记忆了 :)
再举一个例子:
#include "move_emu.hpp" #include <iostream> class integer { COPYABLE_AND_MOVABLE(integer) public: int * value;
// default constructor integer() : value(new int()) { std::cout << "default construct!" << std::endl; }
// constructor(int) integer(int __val) : value(new int(__val)) { std::cout << "num construct!" << std::endl; }
// copy constructor integer(const integer & x) : value(new int(*(x.value))) { std::cout << "Copy construct!" << std::endl; }
// move constructor integer(RV_REF(integer) x) : value(x.value) { std::cout << "Move construct!" << std::endl; x.value = NULL; }
// destructor ~integer() { std::cout << "Destructor!" << std::endl; delete value; } /** * Copy assignment -- replace const integer & x * with const rv<integer> & x * reason -- let factory(rhs) function use operator=(integer & x) */
// copy assignment integer & operator =(COPY_ASSIGN_REF(integer) x) { std::cout << "Copy assign!" << std::endl; if(this != &x) { delete value; value = new int(*(x.value)); } return *this; }
// move assignment integer & operator =(RV_REF(integer) x) { std::cout << "Move assign!" << std::endl; if (this != &x) { delete value; value = x.value; x.value = NULL; } return *this; } }; integer factory(int i) { integer ret; *(ret.value) = (i + 1024); return ret; } int main() { integer x1(100); std::cout << "x1 = " << *x1.value << std::endl; std::cout << "-----------------" << std::endl; integer x2 = move_emu(x1); std::cout << std::boolalpha << "x1.value == NULL: " << (x1.value == NULL) << std::endl; std::cout << "x2 = " << *x2.value << std::endl; std::cout << "-----------------" << std::endl; integer x3(move_emu(move_emu(x2))); std::cout << std::boolalpha << "x2.value == NULL: " << (x2.value == NULL) << std::endl; std::cout << "x3 = " << *x3.value << std::endl; std::cout << "-----------------" << std::endl; // do not use move_emu(factory(1024)), failure. // std::move(factory(1024)) success. integer x4; x4 = factory(1024); std::cout << "x4 = " << *x4.value << std::endl; std::cout << "-----------------" << std::endl; const integer x5(200); std::cout << "x5 = " << *x5.value << std::endl; std::cout << "-----------------" << std::endl; // std::move will convert it into a copy construction const integer x6 = move_emu(x5); std::cout << std::boolalpha << "x5.value == NULL: " << (x5.value == NULL) << std::endl; std::cout << "x6 = " << *x6.value << std::endl; std::cout << "-----------------" << std::endl; return 0; }
参考: