函数返回对象优化
#include <iostream>
using namespace std;
class Test {
public:
Test(int a = 10) :ma(a) {
cout << "Test(int)" << endl;
}
Test(const Test& src):ma(src.ma) {
cout << "Test(const Test& src)" << endl;
}
Test& operator=(const Test& src) {
cout << "operator=" << endl;
ma = src.ma;
return *this;
}
~Test() {
cout << "~Test()" << endl;
}
private:
int ma;
};
int main() {
Test t1;
Test t2(t1);
Test t3 = t1;
// Test(20)显示生成临时对象 生命周期:所在语句
// 被优化为 Test t4(20)
Test t4 = Test(20);
cout << "------------" << endl;
return 0;
}
重点:用临时对象拷贝构造新对象的时候,临时对象不构造了,直接生成目标对象
类型转换时的临时对象
// 这句调用operator=,必须生成临时对象,出了这条语句后,临时对象被析构
t4 = Test(30);
// 显式(生成临时对象)类型转换int->Test,编译器会查看Test是否存在一个合适的构造函数,存在就直接构造
t4 = (Test)40;
// 隐式(生成临时对象)类型转换int->Test,出了该语句就被析构
t4 = 50;
指针、引用、临时对象
// 出了这条语句后,临时对象被析构,ptr成为野指针
Test* ptr = &Test(10);
// 引用相当于给了临时对象一个名字,这个临时对象的生命周期延长为这个引用变量的周期
const Test& ref = Test(10);
构造析构顺序
构造的时候先构造全局变量、然后进入函数顺序执行(执行到static时才初始化)
析构的时候,先析构局部变量,然后是static变量和全局变量
#include <iostream>
using namespace std;
class Test {
public:
Test(int a = 5, int b = 5) :ma(a), mb(b) {
cout << "Test(int, int)" << endl;
}
Test(const Test& src):ma(src.ma), mb(src.mb) {
cout << "Test(const Test& src)" << endl;
}
void operator=(const Test& src) {
cout << "operator=" << endl;
ma = src.ma;
mb = src.mb;
}
~Test() {
cout << "~Test()" << 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(const Test& src)
// static变量,运行到这条语句才初始化
// 临时对象构造新对象,直接优化为构造目标对象
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 = (Test)50;
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()
const Test& p4 = Test(90, 90); // 12. Test(int, int)
delete p1; // 13. ~Test()
delete[] p2; // 14. ~Test() ~Test()
// 最后析构顺序:p4、t3、t2、t4、t5、t1(局部变量、静态变量、全局变量)
return 0;
}
Test t5(10, 10); // 2. Test(int, int)
函数调用过程
#include <iostream>
using namespace std;
class Test {
public:
Test(int a = 5) :ma(a){
cout << "Test(int, int)" << endl;
}
Test(const Test& src):ma(src.ma){
cout << "Test(const Test& src)" << endl;
}
void operator=(const Test& src) {
cout << "operator=" << endl;
ma = src.ma;
// return *this;
}
~Test() {
cout << "~Test()" << endl;
}
int get_data() {
return ma;
}
private:
int ma;
};
// 返回值为Test*和Test&都不对,不能返回局部对象指针和引用(针对栈区数据,栈帧清退,对象销毁。指向堆区和数据区的指针可以返回)
Test get_obj(Test t) { // 3. Test(const Test& src) 实参拷贝构造形参
int val = t.get_data();
Test tmp(val); // 4. Test(int)
// 5. Test(const Test& src) 用tmp在main函数栈帧上拷贝构造临时对象
// 6. 析构tmp ~Test() 7. 析构形参t ~Test()
return tmp;
}
int main() {
Test t1; // 1. Test(int)
Test t2; // 2. Test(int)
t2 = get_obj(t1); // 8. t2.operator=(临时对象)
// 9. 临时对象析构
// 10. t2析构、t1析构
return 0;
}
优化后的代码:
Test get_obj(Test& t) {
return(t.get_data());
}
int main() {
Test t1;
Test t2 = get_obj(t1);
return 0;
}
总结对象优化规则:
- 函数参数传递过程中,对象优先用引用传递,不要按值传递。避免实参形参的拷贝构造以及出作用域的析构
- 用临时对象拷贝构造一个新对象的时候,C++编译器直接优化为构造目标对象。所以被调用函数返回的时候直接返回临时对象即可(此时被调用函数的栈帧上不会产生临时对象,也就不用出作用域析构),无需先构造后返回。
- 接收一个对象返回值的时候,以初始化(拷贝构造)的方式接收,而不是以赋值的方式接收
#include <iostream>
using namespace std;
class String
{
public:
String(const char* str = nullptr)
{
cout << "String(const char*)" << endl;
if (str != nullptr)
{
m_data = new char[strlen(str) + 1];
strcpy(m_data, str);
}
else
{
m_data = new char[1];
*m_data = '\0';
}
}
String(const String& src)
{
cout << "String(const String& src)" << endl;
m_data = new char[strlen(src.m_data) + 1];
strcpy(m_data, src.m_data);
}
~String()
{
cout << "~String()" << endl;
delete[]m_data;
m_data = nullptr;
}
//调用String&是为了支持连续的operator=赋值操作
String& operator=(const String& src)
{
cout << "operator=" << endl;
if (&src == this)
{
return *this;
}
delete[]m_data;
m_data = new char[strlen(src.m_data) + 1];
strcpy(m_data, src.m_data);
return *this;
}
const char* c_str() {
return m_data;
}
private:
char* m_data;//用于保存字符串
};
String get_string(String& str) {
const char* pstr = str.c_str();
String tmp(pstr);
return tmp;
}
int main(){
String str1("11111111111111111");
String str2;
str2 = get_string(str1);
cout << str2.c_str() << endl;
return 0;
}
*/
使用右值引用解决临时对象拷贝构造等问题
// 临时对象(右值)会匹配到右值引用为参数的函数,这里的src是临时对象(右值)
String(String&& src){
cout << "String(const String&& src)" << endl;
m_data = src.m_data;
src.m_data = nullptr;
}
String& operator=(String&& src){
cout << "operator=(String&& src)" << endl;
if (&src == this)
{
return *this;
}
delete[]m_data;
m_data = src.m_data; // 改变堆区资源指向
src.m_data = nullptr; // 临时对象的指针置空,防止析构的时候释放资源
return *this;
}
vector上应用String
std::move
:移动语义,得到右值类型
std::forward
:类型的完美转发,得到真实的左/右值
函数模板的类型推演 + 引用折叠:
String&& + && = String&&
String& + && = String&
//void construct(T* p, const T& val)//负责对象构造
//{
// new(p)T(val);//定位new
//}
//void construct(T* p, T&& val)//负责对象构造
//{
// new(p)T(std::move(val));//定位new
//}
template<typename Ty2>
void construct(T* p, Ty2&& val)//负责对象构造
{
new (p) T(std::forward<Ty2>(val));//定位new
}
//void push_back(const T& val)//向容器末尾添加元素
//{
// if (full())
// expand();
// //*_last++ = val;//last指针指向的内存构造一个值为val的对象
// _allocator.construct(_last, val);
// _last++;
//}
//void push_back(T&& val)//向容器末尾添加元素
//{
// if (full())
// expand();
// //*_last++ = val;//last指针指向的内存构造一个值为val的对象
// _allocator.construct(_last, std::move(val));
// _last++;
//}
// 函数模板的类型推演 + 引用折叠
// String&& + && = String&&
// String& + && = String&
// T char
// Ty1 String
template<typename Ty1> // 换个名会进行类型推演,直接用T直接断定是右值引用
void push_back(Ty1&& val) {
if (full()) {
expand();
}
// forward:类型的完美转发
_allocator.construct(_last, std::forward<Ty1>(val));
_last++;
}
// 函数模板的类型推演 + 引用折叠
// String&& + && = String&&
// String& + && = String&
template<typename Ty1>
void push_back(Ty1&& val) {
if (full()) {
expand();
}
// forward:类型的完美转发
_allocator.construct(_last, std::forward<Ty1>(val));
_last++;
}