对象优化1、C++编译器对于“ 创建新对象”时的优化

原文链接:C++编译器对于对象的优化_c++编译器对于对象构造的优化:用临时对象生成新对象的时候,-CSDN博客

文章详细探讨了C++编译器在对象构造过程中的优化,

例如在某些情况下直接构造新对象时候,将会避免临时对象的生成(将临时对象优化掉了,就不产生临时对象了);但是,如果是给已经生成的对象进行赋值,将不会对优化掉 临时对象。 

此外,还提到了对象构造和析构的调用顺序,以及函数参数传递和返回对象时的优化原则,强调了使用引用和初始化接收返回值的重要性。

1、C++编译器对于对象构造的优化

1.1、用临时对象生成新对象时, 临时对象就不产生了,直接构造新对象即可

class Test
{
public:
    Test(int a = 10) :ma(a)
    {
        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)显式生成临时对象
    // C++ 编译器对于对象构造优化:用临时对象生成新对象时,不会产生临时对象,直接构造
    Test t4 = Test(20); // 等价于 Test t4(20); 只调用一个构造函数
    cout << "----------------------" << endl;
    return 0;
}

输出结果

Test(int)
Test(const Test&)
Test(const Test&)
Test(int)
----------------------
~Test()
~Test()
~Test()
~Test()

1.2、但是如果是赋值,就会创建临时对象

t4 = t2;

//t4已经存在,属于赋值
//1.会调用构造函数生成临时对象,2.并调用赋值运算符,3.该行语句执行完会调用析构函数析构临时对象
t4 = Test(30);

执行结果

operator=
Test(int)
operator=
~Test()

2、显式和隐式生成临时对象

int类型30 =》转换为Test类型:

// 显示生成临时对象
t4 = Test(30); //1.会调用构造函数生成临时对象,2.并调用赋值运算符

// 隐式生成临时对象
t4 = (Test)30;//1.会调用“带有int参数的Test构造函数”生成临时对象,2.并调用赋值运算符
t4 = 30;      //1.会调用“带有int参数的Test构造函数”生成临时对象,2.并调用赋值运算符

执行结果

Test(int)
operator=
~Test()
Test(int)
operator=
~Test()
Test(int)
operator=
~Test()

3、注意:

3.1、指针指向临时对象,是不安全的!

如果用指针指向一个对象,那么这个对象必须是“左值”,不能是“右值”,或者说不能是“临时对象”!

因为,临时对象的生命周期只在当前语句内,出了这条语句,临时对象析构 不复存在。此时,指针p指向一个不存在的对象,就变成 野指针了!!
Test* p = &Test(40); // error

3.2、引用指向临时对象是安全的。

const引用可以接收临时构造的对象
const Test& ref = Test(50); //常引用扩展了临时对象的生命周期!(出了这条语句,临时对象仍存在)

4、函数中不同类型的对象 构造、析构先后顺序

分析案例1:

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是在局部对象后面析构
	static Test t4 = Test(30, 30); // 5.Test(int, int)
	t2 = Test(40, 40); // 6.Test(int, int) operator= ~Test()
	// (50, 50) =  (Test)50; Test(int)
	t2 = (Test)(50, 50); // 7.Test(int,int) operator=  ~Test()
	t2 = 60; //Test(int) 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); // 11. Test(int,int),常引用扩展了对象的生命期
	delete p1; // 12.~Test()
	delete[]p2; // 13. ~Test() ~Test()
}
// 14. p4::~Test()
// 15. t3::~Test()
// 16. t2::~Test()
// 17. t4::~Test()
// 18. t5::~Test()
// 19. t1::~Test()
Test t5(100, 100); // 2.Test(int, int)

1. 和2.全局变量先构造,在main()函数之前做好初始化;t1、t5

4.t2拷贝构造t3;

5.静态变量,程序运行的时候,它的内存已经存在了,因为数据段内存是事先就分配好了。但是,静态变量的初始化是在第一次运行到它(按照变量前后顺序)的时候,才初始化。没有运行到,就不会先初始化;t4(临时对象被优化掉,直接调用一次构造函数,构造t4)

6.显示生成临时对象,然后给t2赋值,之后析构临时对象;

11.p4指针指向临时对象;

main()函数结束后,如何继续析构?(后生成的 先析构)

14. 和15. 和16. p4, t3, t2。(函数结束后,函数内栈上数据先析构)

17. 和18. 和19. 析构t4,t5,t1(整个程序运行结束后,数据段上的数据才析构!)

5、对象构造和析构调用顺序

注意:不能返回 局部的或者临时对象 的指针或引用!!

对象构造和析构调用顺序分析案例2

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 &t)" << 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;//不能返回 局部的或者临时对象 的指针或引用
 }
Test* GetObject(Test t)
 {
    int val = t.getData();
    static Test tmp(val);
    return &tmp;//可以正常返回:静态局部的或者临时对象的引用(生命周期是整个程序)
 }*/

Test GetObject(Test t)
{
    int val = t.getData();
    Test tmp(val);
    return tmp;
}
int main()
{
    Test t1;
    Test t2;
    t2 = GetObject(t1);//3.由t1拷贝构造形参t 
    return 0;
}

构造析构顺序如下图所示:

3.由t1拷贝构造形参t

5.在main()函数栈帧上构造一个临时对象(没有名字),之后,由局部变量tmp拷贝构造这个临时对象;

6.出了GetObject(){}函数,局部变量tmp析构,

7.之后,是形参t析构;

8.之后,是回到main()函数中,用在main()函数栈帧上构造的临时对象给t2赋值;

9.出了赋值语句,就要将main()函数栈帧上构造的临时对象进行析构;

10.main函数结束后,是t2析构,之后是t1析构;

6、对象的三条优化原则

1.函数参数传递过程中,对象优先按引用传递,不要按值传递;

2.函数返回对象的时候,应该优先返回一个临时对象而不是返回一个定义过的对象;

3.接收返回值是对象的函数调用,优先按初始化方式接收,不要按赋值方式接收;

实例验证:

1.将上面的代码函数形参修改为引用,将会节省一次的拷贝构造 和 析构的开销,原来t要拷贝构造和析构

Test GetObject(Test &t) // 这里参数被修改为引用
{
    int val = t.getData();
    Test tmp(val);
    return tmp;
}
int main()
{
    Test t1;
    Test t2;
    t2 = GetObject(t1);
    return 0;
}

输出结果为:

Test(int)
Test(int)
Test(int)
Test(const Test &t)
~Test(int)
operator=
~Test(int)
~Test(int)
~Test(int)

可以发现少了两次的调用,3.和7.两个函数调用;

函数参数传递引用: GetObject(Test t)=>GetObject(Test &t)
好处是:传递引用 不用使用实参t1拷贝构造形参t(3.),就不用调用拷贝构造函数、离开函数作用域也就不用调用形参t的析构函数(7.),这样就少调用了2个函数,效率提高!

2.进一步进行优化,在GetObject 中返回临时对象

Test GetObject(Test &t)
{
    int val = t.getData();
    
    /*Test tmp(val);
    return tmp;*/
    // 这里正常情况会使用临时对象拷贝构造一个新对象
    // 而c++编译器优化则会导致只调用构造函数生成main函数中GetObject(t1)返回的的临时对象
    // 这样就又减少一对拷贝构造和析构
    return Test(val); 
}

输出结果:

Test(int)
Test(int)
Test(int)
operator=
~Test(int)
~Test(int)
~Test(int)

将 Test tmp(val);return tmp; => return Test(val);
用临时对象Test(val)拷贝构造一个新对象,临时对象都是要被优化掉。即不会创建临时对象。这里就是直接构造main函数栈帧上的临时对象(用这个临时对象,赋值给t2)。

可以发现:少了tmp的构造和析构,就是4.和6.两个函数调用;

3.再进行优化,将main函数中 GetObject(t1)直接赋值给t2

int main()
{
    Test t1;
     
    //Test t2;
    //t2 = GetObject(t1);
    //因为这里用临时对象构造同类型对象t2,所以会进行优化,只调用一次构造函数,而没有调用右边临时对象的构造
    Test t2 = GetObject(t1);

    return 0;
}

输出结果:

Test(int)
Test(int)
~Test(int)
~Test(int)

将 Test t2; t2=GetObject(t1); => Test t2=GetObject(t1);
用临时对象Test(val)拷贝构造一个同类型的新对象t2,临时对象都是要被优化掉。即不会创建临时对象。
少了构造main函数栈帧上的临时对象(5.) 和 8.赋值和 9.临时对象析构              


原文链接:https://blog.csdn.net/qq_42120843/article/details/130573471

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值