对象被优化以后才是高效的C++编程

函数调用过程中对象背后调用的方法

示例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),实际运行结果可能有所差异,在分析时尽量不考虑编辑器的优化!加粗样式

三条对象优化的规则

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

Test Getobject(Test t);优化为Test Getobject(Test &t);
这样可以省去一个形参t的拷贝构造调用,形参没有构建新的对象,出作用域也不用析构了!

  1. 函数返回对象的时候,应该优先返回一个临时对象,而不要返回一个定义过的对象。
Test GetObject(Test& t)
{
	int val = t.getData();
	// 直接返回临时对象
	return Test(val);
}

事实上,VS2022编译器的RVO/NRVO已经把这步给优化了,但由于其他编译器不一定有优化,在写代码的时候还是需要注意一下!

  1. 接收返回值是对象的函数调用的时候,优先按初始化的方式接收,不要按赋值的方式接收
    在这里插入图片描述
int main()
{
    Test t1;
    Test t2 = Getobject(t1);
    return 0;
}
  • 优先引用传递、优先返回临时对象、优先初始化方式接收。利用这三条规则,示例3代码11个函数调用可被优化为4个函数调用!(Test(int) Test(int) ~Test() ~Test())
  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值