对象的生命周期
例子1:
class Test { public: Test(int a= 10) :ma(a) { cout << "Test()" <<endl; } ~Test() { cout << "~Test()" <<endl; } Test(const Test &t):ma(t.ma) { cout << "Test(const Test&)" <<endl; } Test& operator=(const Test &t) { cout << "operator=" <<endl; ma = t.ma; return *this; } private: int ma; }; int main01() { Test t1; Test t2(t1); Test t3 = t1; //Test(20) 显示生成临时对象 生存周期:所在的语句 //Test t4(20); 没有区别的 /* C++编译器对于对象构造的优化:用临时对象生成新对象的时候,临时对象就不产生了,直接构造新的对象 */ Test t4 = Test(20); cout << "-------------------" <<endl; t4 = t2; //operator= //显示生成临时对象:先生成临时对象,然后调用拷贝构造 t4 = Test(30); //Test() operator= ~Test() //隐式生成临时对象,找test中合适的构造函数 t4 = 30; //Test() operator= ~Test() cout << "-------------------" <<endl; //p指向的是一个已经析构的临时对象,编译没通过,能编译通过也是不安全的 // Test *p = &Test(40); //用 引用 临时量的生命周期不是在当前语句 const Test &ref = Test(50); cout << "-------------------" <<endl; return 0; }
例子2:
class Test01 { public: Test01(int a = 5,int b =5): ma(a),mb(b) { cout << "Test01(int,int)" << endl; } ~Test01() { cout << "~Test01()" <<endl; } Test01(const Test01 &src) :ma(src.ma),mb(src.mb) { cout << "Test01(const Test&)" << endl; } Test01& operator=(const Test01 &src) { if (this == &src) return *this; ma = src.ma; mb = src.mb; return *this; } private: int ma; int mb; }; //#1 Test01(int,int) Test01 t1(10,10); //#20 ~Test01() int main() { //#3 Test01(int,int) Test01 t2(20,20); //#17 ~Test01() //#4 Test01(const Test&) Test01 t3 = t2; //#16 ~Test01() //t4 编译时期静态变量的内存已存在,但是这个对象也是在运行的时候在构造的 //static Test01 t4(30,30) 这句和下句是一样的 //#5 Test01(int,int) static Test01 t4 = Test01(30,30); //#18 ~Test01() //#6 Test01(int,int) operator= ~Test01() t2 = Test01(40,40); //#7 Test01(int,int) operator= ~Test01() t2 = (Test01)(50,45); //(50,50) = (Test01)45; ==> Test01(int); //#8 Test01(int) operator= ~Test01() t2 = 60; //#9 Test01(int,int) Test01 *p1 = new Test01(70,70); //#10 Test01(int,int) Test01(int,int) Test01 *p2 = new Test01[2]; // Test01 *p3 = &Test01(80,80); //错误的 //#12 Test01(int,int) const Test01 &p4 = Test01(90,90); //#15 ~Test01() //#13 ~Test01() delete p1; //#14 ~Test01() ~Test01() delete []p2; return 0; } //#2 Test01(int,int) Test01 t5(100,100); //#19 ~Test01()
函数调用的对象参数
class Test { public: Test(int data = 10): ma(data) { cout << "Test(int)" << endl; } ~Test() { cout << "~Test(int)" << endl; } Test(const Test &t):ma(t.ma) { cout << "Test(const Test&)" <<endl; } Test& operator=(const Test &t) { if (this == &t) return *this; cout << "operator = " << endl; ma = t.ma; return *this; } int getData() const { return ma; } private: int ma; }; //注意不要返回局部变量,不过static 存放的数据段不一样,static编译时期就分配好内存的,所以该函数生命周期结束对static并没有影响 /* 函数调用,实参传递给行参是拷贝构造,也算是初始化 1.两个都存在的对象叫赋值 2.初始化是有一个是新建的对象 很明显调用过程Test Getobject(Test t) 是 ==》Test t = t1; */ Test Getobject(Test t) //按值传递 { int val = t.getData(); Test tmp(val); //这一步:临时量到寄存器带出去 所需操作 Test(const Test &) 函数结束再析构 return tmp; } int main() { Test t1; Test t2; t2 = Getobject(t1); return 0; }
对象调用的三条优化规则
1.函数参数传递过程中,对象优先按引用传递, 不要按值传递
值传递【Test Getobject(Test t)】:会重新初始化(拷贝构造成)另一个对象,函数结束还有析构
引用传递【Test Getobject(Test &t)】:会把实参地址传进来,就不会重新初始化另一个对象,也不用再析构
2.函数返回对象的时候,应该优先返回一个临时对象,而不要返回一个定义过的对象
3.接收返回值是对象的函数调用的时候,优先按初始化的方式接收,不要按赋值的方式接收
Test Getobject(Test &t) { int val = t.getData(); //Test t = Test(val); //等于 是 Test t(val);也就是直接返回临时对象就不会产生临时对象 return Test(val); } int main() { Test t1; //等于是 Test t2 = 临时对象; Test t2 = Getobject(t1); return 0; }
String的代码问题(C++对象运算符重载,容器迭代器这篇的String)拷贝构造大内存效率就显得差劲了
注意:g++ 高版本把tempStr拷贝构造main函数栈帧上的临时对象给优化掉了,所以会有看不到那一步的现象
以上例子的解决方案就是添加带右值引用参数的拷贝构造和赋值构造
右值:没名字(临时量)或没内存,
int a = 10; //int &&b = a; //无法将左值绑定到右值引用 //int &c = 20; //不能用左值引用绑定一个右值 const int &c = 20; //int temp = 20; const int &c = temp; int &&d = 20; //int temp = 20; int &&d = temp; // String &e = String("aaa"); //不能用左值引用绑定一个右值 String &&e = String("aaa"); //int &&f = d; //一个右值的引用本身是一个左值 int &f = d;
添加了右值引用拷贝构造,和右值引用参数的赋值函数重载
#include <iostream> #include <cstring> using namespace std; class String { public: String(const char*p = nullptr) { cout << "String(const char*p = nullptr)" << endl; if(p != nullptr) { _pstr = new char[strlen(p) + 1]; strcpy(_pstr,p); }else { _pstr = new char[1]; *_pstr = '\0'; } } ~String() { cout << "~String()" << endl; if (_pstr != nullptr){ //因为临时对象把 _pstr = nullptr 了。 delete []_pstr; _pstr = nullptr; } } //带左值引用参数的拷贝构造 String(const String &str) { cout << "String(const String &str)" << endl; _pstr = new char[strlen(str._pstr) + 1]; int len = strlen(str._pstr) + 1; strcpy(_pstr,str._pstr); } //带右值引用参数的拷贝构造,因为需要改变指向内存的值,不能带const String(String &&str) //引用的就是一个临时对象 { cout << "String(String &&str)" << endl; _pstr = str._pstr; str._pstr = nullptr; } //带左值引用参数的赋值函数 String& operator= (const String &str) { cout << "String& operator= (const String &str)" << endl; if(this == &str) return *this; delete []_pstr; _pstr = new char[strlen(str._pstr) + 1]; strcpy(_pstr, str._pstr); return *this; } //带右值引用参数的赋值函数 String& operator= (String &&str) { cout << "String& operator= (String &&str)" << endl; if(this == &str) return *this; delete []_pstr; _pstr = str._pstr; str._pstr = nullptr; return *this; } bool operator>(const String &str) const { return strcmp(_pstr,str._pstr) > 0; } bool operator<(const String &str) const { return strcmp(_pstr,str._pstr) < 0; } bool operator==(const String &str) const { return strcmp(_pstr,str._pstr) == 0; } int length()const { return strlen(_pstr); } char& operator[](int index) { return _pstr[index]; } const char& operator[](int index) const { return _pstr[index]; } const char* c_str() const { return _pstr; } private: char *_pstr; friend String operator+(const String &lhs, const String &rhs); friend ostream& operator<<(ostream &out, const String &str); friend istream& operator>>(istream &in , String &str); }; String operator+(const String &lhs, const String &rhs){ char *ptmp = new char[strlen(lhs._pstr) + strlen(rhs._pstr) + 1]; strcpy(ptmp, lhs._pstr); strcat(ptmp,rhs._pstr); return String(ptmp); } ostream& operator<<(ostream &out, const String &str) { out << str._pstr; return out; } istream& operator>>(istream &in , String &str) { in >> str._pstr; return in; } String GetString(String &str) { const char* pstr = str.c_str(); String tempStr(pstr); return tempStr; } int main() { String str1("aaaaaaaaaaaaaa"); String str2; str2 = GetString(str1); cout << str2.c_str() << endl; return 0; }
注意下面例子:String 使用容器vector
1.一个右值引用变量本身还是个左值,如果调用push_back() 传入的是一个右值引用的变量,然后到了_allocator.construct(_last,val);这行代码的val 它的本身就是一个左值了
//向容器末尾添加元素 void push_back(const T &val) { if(full()) expand(); // *_last++ = val; //_last 指针指向的内存构造一个值为val的对象 _allocator.construct(_last,val); _last ++; } //右值向容器末尾添加元素 void push_back(const T &&val) { if(full()) expand(); // *_last++ = val; //_last 指针指向的内存构造一个值为val的对象 _allocator.construct(_last,val); _last ++; } //指定内存初始化对象,右值引用 void construct(T *p, T &&val) { new (p) T(val); } void construct(T *p, const T &val) { new (p) T(val); }
可以两处都 std::move(val) 强转成右值引用类型:
//向容器末尾添加元素 void push_back(const T &val) { if(full()) expand(); // *_last++ = val; //_last 指针指向的内存构造一个值为val的对象 _allocator.construct(_last,val); _last ++; } //右值向容器末尾添加元素 void push_back(const T &&val) { if(full()) expand(); // *_last++ = val; //_last 指针指向的内存构造一个值为val的对象 _allocator.construct(_last,std::move(val)); _last ++; } //指定内存初始化对象,右值引用 void construct(T *p, T &&val) { new (p) T(std::move(val)); } void construct(T *p, const T &val) { new (p) T(val); }
以上方法处理起来太麻烦了,毕竟有一个左值引用和一个右值引用的函数。
std::forward<Ty>(val):类型完美转发,能够识别左值还是右值
1.函数模板的类型推演template<typename Ty> + 引用 = 引用折叠
比如传入的是一个左值,那模板推演出来的是左值引用 然后加上右值引用的&&val == String &,也就是传入的是左值引用它就是一个左值引用
如果传入的是一个右值,那模板推演出来的是右值引用,然后加上右值引用的&&val == String&&,他就是一个右值引用
template<typename Ty> void push_back(Ty &&val) { if(full) expand(); //_allocator.construct(_last,val); 这一句val不管是左值还是右值它都是一个左值 //std::forward<Ty>(val) 根据模板传进来的引用类型完美的转发给下一个 _allocator.construct(_last,std::forward<Ty>(val)); _last ++; }