今天我决定尝试用另外一种方式来表达,任何新语法的的产生都是为了解决某个问题,所以今天先看问题。
class myStr { protected: char* str_; public: myStr(void) // 默认的构造函数,什么也不做 : str_(nullptr) {} myStr(const char* rhs) // 普通赋值构造函数 : str_(nullptr) { if (!rhs) return; str_ = new char[1024]; strcpy(str_, rhs); // cout << "Str constructor " << str_ << std::endl; } myStr(const myStr& rhs) // 拷贝构造函数 : str_(nullptr) { if (!rhs) return; str_ = new char[1024]; strcpy(str_, rhs.str_); // cout << "Str copy constructor " << str_ << std::endl; } myStr(myStr&& rhs) : str_(nullptr) { swap(rhs); // std::cout << "Str move constructor " << str_ << std::endl; } ~myStr() // 析构函数 { if (!str_) return; // std::cout << "Str destructor " << str_ << std::endl; delete [] str_; } const myStr& operator=(myStr rhs) // 赋值操作符重载 { rhs.swap(*this); // 使用copy-and-swap惯用法获得数据 return (*this); // 避免重复撰写operator= } void swap(myStr& rhs) // 交换算法 { std::swap(str_, rhs.str_); } operator char*(void) const { return str_; } myStr& operator+=(const char* rhs) { if (rhs) strcat(str_, rhs); return (*this); } // friend myStr operator+(const myStr& x, const myStr& y) // { // return myStr(x) += y; // } friend myStr operator+(const myStr& x, const myStr& y) { return std::move(myStr(x) += y); }
执行下面的代码
myStr ss("000"); myStr s1("11111"), s2("22222"), s3("3333333"), s4("4444444"); cout << std::endl; time_t timestamp1; time_t timestamp2; time_t timestamp3; const long long max = 30000000; time(×tamp1); for (long long i = 0; i<max; i++) { ss = s1 + s2 + s3 + s4; } time(×tamp2); timestamp3 = timestamp2 - timestamp1;
下面的代码是唯一不同的实现,但是却带来30-40%的性能差距。
/ friend myStr operator+(const myStr& x, const myStr& y) // { // return myStr(x) += y; // } friend myStr operator+(const myStr& x, const myStr& y) { return std::move(myStr(x) += y); }
再找一个例子
class MemoryBlock { public: // 构造器(初始化资源) explicit MemoryBlock(size_t length) : _length(length) , _data(new int[length]) { std::cout << "MemoryBlock constructor " << std::endl; } // 析构器(释放资源) ~MemoryBlock() { if (_data != nullptr) { delete[] _data; } std::cout << "MemoryBlock destructor " << std::endl; } // 拷贝构造器(实现拷贝语义:拷贝that) MemoryBlock(const MemoryBlock& that) // 拷贝that对象所拥有的资源 : _length(that._length) , _data(new int[that._length]) { std::copy(that._data, that._data + _length, _data); std::cout << "copy constructor " << std::endl; } // 拷贝赋值运算符(实现拷贝语义:释放this + 拷贝that) MemoryBlock& operator=(const MemoryBlock& that) { if (this != &that) { // 释放自身的资源 delete[] _data; // 拷贝that对象所拥有的资源 _length = that._length; _data = new int[_length]; std::copy(that._data, that._data + _length, _data); } return *this; } // 移动构造器(实现移动语义:移动that) MemoryBlock(MemoryBlock&& that) // 将自身的资源指针指向that对象所拥有的资源 : _length(that._length) , _data(that._data) { // 将that对象原本指向该资源的指针设为空值 that._data = nullptr; that._length = 0; } // 移动赋值运算符(实现移动语义:释放this + 移动that) MemoryBlock& operator=(MemoryBlock&& that) { if (this != &that) { // 释放自身的资源 delete[] _data; // 将自身的资源指针指向that对象所拥有的资源 _data = that._data; _length = that._length; // 将that对象原本指向该资源的指针设为空值 that._data = nullptr; that._length = 0; } return *this; } private: size_t _length; // 资源的长度 int* _data; // 指向资源的指针,代表资源本身 }; MemoryBlock f() { return MemoryBlock(50); }
执行下面的代码
const long long max = 100000; time_t timestamp1; time_t timestamp2; time_t timestamp3; time(×tamp1); for (long long i = 0; i<max; i++) { MemoryBlock a = MemoryBlock(50); MemoryBlock c = std::move(a); } time(×tamp2); timestamp3 = timestamp2 - timestamp1;
如果把MemoryBlock c = std::move(a)换成MemoryBlock c = a;
性能上大概也有30%的差距。
这就是右值引用和移动构造语义带来的好处,我理解就是以前只能引用左值,而右值是不能引用的,新语法的加入实现了右值的引用,减少了零时对象的产生销毁,但是也带来了更多怪异的语法,明显增加了c++的学习成本,如果语法设计角度,像oc一样增加类似引用计数器来管理对象,会不会更加优雅一下,至少让上层的码农不会那么累,其实通智能指针也能达到同样的效果。
这么多年了c++都在做加法,让学习,使用成本太高了,标准委员为的大爷些什么时候考虑一下做点减法呢,不要让c++那么学院派或者满地都是陷阱,也不要让实现一种技术有10种方法,但是有5种都是陷阱。靠!