函数调用过程中对象背后调用的方法
示例1
#include <iostream>
using namespace std;
class Test
{
public:
Test(int data = 10)
: ma(data)
{
cout << "Test(int)" << 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 main()
{
Test t1;
Test t2(t1);
Test t3 = t1; // 定义的时候,拷贝构造,不要被迷惑
// Test(20) -> 显式生成临时对象(生成周期:所在的语句)
Test t4 = Test(20); // 相当于Test t4(20)
return 0;
}
/*
Test(int)
Test(const Test&)
Test(const Test&)
Test(int)
~Test()
~Test()
~Test()
~Test()
*/
Test t3 = t1;
调用的是拷贝构造!(定义时调用的是拷贝构造)Test t4 = Test(20);
其中,Test(20)
是临时对象,但从输出结果来看,t4是直接构造的,这是因为C++ 编译器对于对象构造的优化,用临时对象生成新对象的时候,临时对象不产生,直接构造新对象即可!
int main()
{
t4 = t2;
// t4.operator=(const Test &t)
// 显式生成临时对象
t4 = Test(30); // Test(30)生成临时对象,给t4赋值,出语句即析构
t4 = (Test)30;
// 隐式生成临时对象
t4 = 30; // 编译器“查看”是否有合适的构造函数 -> 若有则隐式生成Test对象,再给t4赋值
return 0;
}
/*
operator=
Test(int)
operator=
~Test()
Test(int)
operator=
~Test()
Test(int)
operator=
~Test()
*/
t4 = t2;
调用赋值函数,因为t4原本就存在!- 后面三句效果一样,
t4 = Test(30);
和t4 = (Test) 30;
是显式生成临时对象,而t4 = 30;
是隐式生成临时对象!
// Test* p = &Test(40); // 此时p指向的是一个已经析构的临时对象,p相当于野指针了,这是不行的
/*
生成一个临时对象,但是此时是常量左值引用
那么此时 出语句后,临时对象不析构了,因为引用相当于是别名,这块内存起了个名字
所以,引用变量引用临时对象是安全的,临时对象的生命周期就变成引用变量的生命周期了
现在,引用变量是这个函数的局部变量,main函数结束,这个临时对象才析构
*/
const Test& ref = Test(50);
示例2
#include <iostream>
using namespace std;
class Test
{
public:
// 这里有默认值,Test() Test(10) Test(10, 10)这三种都行
Test(int a = 5, int b = 5)
: ma(a), mb(b)
{
cout << "Test(int, int)" << endl;
}
~Test() { cout << "~Test()" << endl; }
Test(const Test &src)
: ma(src.ma), mb(src.mb)
{
cout << "Test(const Test&)" << endl;
}
void operator=(const Test &src)
{
ma = src.ma;
mb = src.mb;
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(const 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()
// 显式生成临时对象
// (20, 50)括号表达式,值是50
// 也即(Test)50,去找Test(int)一个参数的构造
t2 = (Test)(20, 50); // 7.Test(int, int) operator= ~Test()
// 隐式生成临时对象
t2 = 60; // 8.Test(int, int) operator= ~Test()
// new出来的要delete才析构
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()
}
Test t5(100, 100); // 2.Test(int, int)
示例3
#include <iostream>
using namespace std;
class Test
{
public:
Test(int data = 10)
: ma(data)
{
cout << "Test(int)" << endl;
}
~Test()
{
cout << "~Test()" << endl;
}
Test(const Test &t)
: ma(t.ma)
{
cout << "Test(const Test&)" << endl;
}
void operator=(const Test &t)
{
cout << "operator=" << endl;
ma = t.ma;
}
int getData() const { return ma; }
private:
int ma;
};
Test Getobject(Test t) // 不能返回局部的对象的指针或引用
{
int val = t.getData();
Test tmp(val);
return tmp; // tmp是局部对象,无法“走出”作用域
}
int main()
{
Test t1;
Test t2;
t2 = Getobject(t1);
return 0;
}
理论运行结果:
Test(int) // t1构造
Test(int) // t2构造
Test(const Test&) // 实参t1拷贝至形参t
Test(int) // val构造临时的Test对象tmp
Test(const Test&) // tmp是局部对象,tmp被构造到返回值(main函数栈帧上临时的匿名对象)
~Test() // tmp析构
~Test() // 形参t析构
operator= // 返回值(main函数栈帧上临时的匿名对象)赋值给t2
~Test() // 返回值(main函数栈帧上临时的匿名对象)析构
~Test() // t2析构
~Test() // t1析构
注:VS2022会有返回值优化(PVO)和命名返回值优化(NRVO),实际运行结果可能有所差异,在分析时尽量不考虑编辑器的优化!加粗样式
三条对象优化的规则
- 函数参数传递过程中,对象优先按引用传递,不要按值传递。
Test Getobject(Test t);
优化为Test Getobject(Test &t);
这样可以省去一个形参t的拷贝构造调用,形参没有构建新的对象,出作用域也不用析构了!
- 函数返回对象的时候,应该优先返回一个临时对象,而不要返回一个定义过的对象。
Test GetObject(Test& t)
{
int val = t.getData();
// 直接返回临时对象
return Test(val);
}
事实上,VS2022编译器的RVO/NRVO已经把这步给优化了,但由于其他编译器不一定有优化,在写代码的时候还是需要注意一下!
- 接收返回值是对象的函数调用的时候,优先按初始化的方式接收,不要按赋值的方式接收
int main()
{
Test t1;
Test t2 = Getobject(t1);
return 0;
}
- 优先引用传递、优先返回临时对象、优先初始化方式接收。利用这三条规则,示例3代码11个函数调用可被优化为4个函数调用!(
Test(int) Test(int) ~Test() ~Test()
)