C++对象调用优化
临时对象拷贝构造新对象,临时对象就不会产生!!!
常见的对象调用过程
c++编译器对于对象构造的优化:用临时对象拷贝新对象的时候,临时对象就不产生了,直接构造新对象就可以了。
#include<iostream>
using namespace std;
class Test
{
public:
Test(int data = 10) : ma(data), mb(data) { cout << "Test(int)" << endl; }
~Test() { cout << "~Test()" << endl; }
Test(const Test &t) { cout << "Test(&)" << endl; }
Test &operator=(const Test &t) { cout << "operator=" << endl; }
private:
int ma;
int mb;
};
int main()
{
Test t1;//普通构造函数
Test t2(t1);//拷贝构造函数
Test t3 = t1; // 拷贝构造函数
t2 = t3;//拷贝构造函数
// 这里和Test t4(20)没有区别,并不会产生Test(20)临时对象
Test t4 = Test(20);//普通构造函数
cout << "-------------------" << endl;
//显示调用构造函数生成临时对象
t4 = Test(30); // 普通构造函数,结束这条语句就会调用析构函数
t4 = (Test)30; // 普通构造函数,结束这条语句就会调用析构函数
//隐式调用构造函数生成临时对象
t4 = 30; // 普通构造函数,结束这条语句就会调用析构函数
cout << "-----------------" << endl;
return 0;
}
const Test &t1 = Test(20, 20);
//仅仅调用了普通构造函数,不会产生临时对象(所以不会有析构)。
对象创建顺序
需要注意的是:static Test t4 = Test(30, 30);
这里编译器会优化,临时对象生成新对象,不会产生临时对象,等价于static Test t4(30,30)。Test *p3 = &Test(80, 80);
,这里语句结束后就被析构,p3就是空指针。const Test &p4 = Test(90, 90);
,引用变量就会保存临时对象,所以不会被析构。静态对象的析构在作用域结束之后才进行析构。
#include<iostream>
using namespace std;
class Test
{
public:
Test(int x = 10, int y = 10) : ma(x), mb(y) { cout << "Test(int,int)" << endl; }
~Test() { cout << "~Test()" << endl; }
Test(const Test &t) { cout << "Test(&)" << endl; }
Test &operator=(const Test &t) { cout << "operator=" << endl; }
private:
int ma;
int mb;
};
Test t1(10, 10);//1.Test(int,int)
int main()
{
Test t2(20, 20);//3.Test(int,int)
Test t3 = t2; // 4.Test(&)
// 这里编译器会优化,临时对象生成新对象,不会产生临时对象,等价于static Test t4(30,30)
static Test t4 = Test(30, 30); // 5.Test(int,int),
t2 = Test(40, 40); // 6.Test(int,int),operator=,~Test()
t2 = (Test)(50, 50); // 7.Test(int,int),operator=,~Test()
t2 = 60; // 8.Test(int,int),operator=,~Test()
Test *p1 = new Test(70, 70); // 9.Test(int,int)
Test *p2 = new Test[2]; // 10.Test(int,int),Test(int,int)
// Test *p3 = &Test(80, 80); //可能会报错// 11.Test(int,int),~Test(),这里语句结束后就被析构,p3就是空指针
const Test &p4 = Test(90, 90); // 12.Test(int,int),引用变量就会保存临时对象,所以不会被析构
delete p1; // 13.~Test()
delete[] p2; // 14.~Test()
return 0;
}
Test t5(100, 100);//2.Test(int,int)
/*
剩下的析构顺序,p4->t3->t2->t4->t5->t1
t4在t3和t2后面析构的原因是它是静态变量,存储在数据段中,所以在后面析构
*/
函数调用存在的问题
函数调用过程中存在很多对象优化的问题,后面会具体介绍怎么去优化。
#include<iostream>
using namespace std;
class Test
{
public:
Test(int data = 10) : ma(data) { cout << "Test(int)" << endl; }
Test(const Test& t) { cout << "Test(&)" << endl; }
Test& operator=(const Test& t) { cout << "operator=" << endl; return *this; }
~Test() { cout << "~Test()" << endl; }
int getData() { return ma; }
private:
int ma;
};
Test getObj(Test t)
{
int val = t.getData();
Test temp(val);
return temp;
}
int main()
{
Test t1;
Test t2;
cout << "------------------" << endl;
t2 = getObj(t1);
cout << "------------------" << endl;
Test t3 = getObj(t2);
cout << "------------------" << endl;
return 0;
}
函数对象调用过程图。
函数的优化:尽量传递引用,而不是传值;尽量返回临时对象,而不是在函数内部构造出对象。优化后的代码如下所示:
Test getObj(Test& t)
{
int val = t.getData();
//Test temp(val);
return Test(val);
}
可以减少实参到形参的拷贝构造,以及函数内部对象的构造和析构。
函数调用优化总结!!!
函数传递过程中,对象优先按照引用传递,不要按值传递。
函数返回对象时,尽量返回一个临时对象,而不是返回一个已经定义好的对象。
接收返回值是对象的时候,尽量以初始化的形式接收,而不是以赋值的形式接收。
具体可以函数调用存在的问题部分讲解和分析!!!
String的右值引用拷贝构造和操作符重载
#include<iostream>
#include<string.h>
using namespace std;
class String
{
public:
String(const char* p = nullptr)
{
if (p != nullptr)
{
_pstr = new char[strlen(p) + 1];
strcpy(_pstr, p);
}
else
{
_pstr = new char[1];
*_pstr = '\0';
}
cout << "String(const char*)" << endl;
}
~String()
{
cout << "~String()" << endl;
delete[] _pstr;
_pstr = nullptr;
}
String(const String& src)
{
cout << "String(const String&)" << endl;
_pstr = new char[strlen(src._pstr) + 1];
strcpy(_pstr, src._pstr);
}
String(String&& src)
{
//因为是临时对象,所以就没有用const修饰
cout << "String(const String&&)" << endl;
_pstr = src._pstr;
src._pstr = nullptr;
}
String& operator=(const String& src)
{
cout << "String& operator=(const String&)" << endl;
if (this == &src)
return *this;
delete[] _pstr;
_pstr = new char[strlen(src._pstr) + 1];
strcpy(_pstr, src._pstr);
return *this;
}
String& operator=(String&& src)
{
//右值引用的操作符重载函数
cout << "String& operator=(String&&)" << endl;
if (this == &src)
return *this;
delete[] _pstr;
_pstr = src._pstr;
src._pstr = nullptr;
return *this;
}
bool operator>(const String& src) const
{
return strcmp(_pstr, src._pstr) > 0;
}
bool operator==(const String& src) const
{
return strcmp(_pstr, src._pstr) == 0;
}
bool operator<(const String& src) const
{
return strcmp(_pstr, src._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& lsrc, const String& rsrc);
friend ostream& operator<<(ostream& out, const String& src);
friend istream& operator>>(istream& in, String& src);
};
String operator+(const String& lsrc, const String& rsrc)
{
char* temp = new char[strlen(lsrc._pstr) + strlen(rsrc._pstr) + 1];
strcpy(temp, lsrc._pstr);
strcat(temp, rsrc._pstr);
String s(temp);
delete[] temp;
return s;
}
ostream& operator<<(ostream& out, const String& src)
{
out << src._pstr;
return out;
}
istream& operator>>(istream& in, String& src)
{
in >> src._pstr;
return in;
}
int main()
{
String str1;
String str2 = "aaa";
String str3 = "bbb";
String str4 = str2 + str3;
String str5 = str2 + "ccc";
String str6 = "ddd" + str2;
cout << "----------------------------------" << endl;
String str7 = String("hello,world");//C++会优化,直接调用构造函数,不会产生临时对象
str6 = String("hello") + String(",world");//先调用两个构造函数,在调用operator+重载,
cout << "----------------------------------" << endl;
return 0;
}
没有右值操作符和拷贝构造函数的情况:
设置了带右值引用的拷贝构造和操作符重载函数。
可以看到原来的使用临时对象生成对象中的函数全部替换为带右值引用的拷贝构造函数,赋值操作符重载也换为带右值引用的拷贝构造函数。使用右值引用的好处主要是节省拷贝构造和操作符重载过程中资源的开辟和释放。
String在vector上的使用
move && forward
因为右值引用变量本身是一个左值,所以在一个函数传递右值引用变量的过程中会出现问题,可以如下所示:
void construct(T* p, const T& val)//对象创建
{
// 更多new初始化方法 https://blog.csdn.net/qq_45041871/article/details/132251733
new (p) T(val);//使用的是定位new,将val传到地址为p的空间去,p地址指向val
}
void construct(T* p, T&& val)//对象创建
{
// 更多new初始化方法 https://blog.csdn.net/qq_45041871/article/details/132251733
//这里虽然val是一个右值引用变量,但是还是会调用左值的拷贝构造函数
new (p) T(val);//使用的是定位new,将val传到地址为p的空间去,p地址指向val
}
void push_back(T&& val)
{
if (full())
expand();
//虽然传入的val是一个右值引用变量,但是它本身是一个左值,
//所以还是会调用void construct(T* p, const T& val)而不是带右值引用的函数
_allocator.construct(_last, val);
_last++;
}
move
:移动语义,强制获得右值类型。可以实现将一个左值转化为一个右值(这个值其实原来可能就是右值引用变量)。
forward
:类型的完美转化,能够识别左值和右值类型。如果原来是左值,那么还是左值;如果原来是右值,那么就传递为右值。
String& &&val->String&
一个引用加两个引用还是一个引用。
String&& &&val->String&
两个引用加两个引用还是两个引用。
String&& &val->String&
两个引用加一个引用还是一个引用,因为右值引用其实本身就是就是一个左值。
后面的val其实就是函数传递过程中的形参类型。
所以上面代码可以考虑更改为以下两种类型:
方式一:move强制转化
void construct(T* p, const T& val)//对象创建
{
// 更多new初始化方法 https://blog.csdn.net/qq_45041871/article/details/132251733
new (p) T(val);//使用的是定位new,将val传到地址为p的空间去,p地址指向val
}
void construct(T* p, T&& val)//对象创建
{
// 更多new初始化方法 https://blog.csdn.net/qq_45041871/article/details/132251733
new (p) T(std::move(val));//使用的是定位new,将val传到地址为p的空间去,p地址指向val
}
void push_back(const T& val)
{
if (full())
expand();
//虽然传入的val是一个右值引用变量,但是它本身是一个左值,
//所以还是会调用void construct(T* p, const T& val)而不是带右值引用的函数
_allocator.construct(_last, val);
_last++;
}
void push_back(T&& val)
{
if (full())
expand();
_allocator.construct(_last, std::move(val));
_last++;
}
方式二:forward完美转化
//不管val传递的是左值还是右值,T&& val都能接收
//如果是左值,则接收后还是左值;反之右值,则还是右值
void construct(T* p, T&& val)//对象创建
{
// 更多new初始化方法 https://blog.csdn.net/qq_45041871/article/details/132251733
new (p) T(std::forward(val));//使用的是定位new,将val传到地址为p的空间去,p地址指向val
}
void push_back(T&& val)
{
if (full())
expand();
//虽然传入的val是一个右值引用变量,但是它本身是一个左值,
//所以还是会调用void construct(T* p, const T& val)而不是带右值引用的函数
_allocator.construct(_last, std::forward(val));
_last++;
}