【C++】拷贝对象时,编译器的偷偷优化

你知道吗?对于连续的”构造+拷贝构造“,编译器其实是会默默做出优化的。

如果你不知道这个知识点的话,那下面这道笔试题就要失分了。

本篇分享一个关于编译器优化的小知识,看完本篇,你就能知道程序里的 构造函数、拷贝构造函数 究竟被调了几次~

题目引入

某笔试题:下面的程序经历了几次构造?几次拷贝构造?

【C++】拷贝对象时,编译器的偷偷优化_第1张图片

答案为:

1次构造,4次拷贝

对此题的分析在本篇末尾。如果你对这道题尚有存疑,那相信看完本篇,你的疑惑将会烟消云散~

传值传参 的优化

我们知道,传值传参会产生拷贝,因为这样能保证 函数内部对参数的操作 不会影响到原始变量的值。

➡️先看一个没做优化的例子:

class W
{
public:
    //构造函数
    W(int w) 
        :_w(w)
    {
        cout << "W()" << endl;
    }
    //拷贝构造函数
    W(const W& w) 
    :_w(w._w)
    {
        cout << "W(const W& w)" << endl;
    }
    //析构函数
    ~W(){
        cout << "~W()" << endl;
    }
private:
    int _w;
};
void func(const W w) {
    
}
int main()
{
    W w1=1;  //构造
    func(w1);   //拷贝构造:把w1的值拷贝一份,传到形参
    return 0;
}

调用情况我们打印出来:

【C++】拷贝对象时,编译器的偷偷优化_第2张图片

可见这种情况下:

W w1=1;
func(w1);

是老老实实调用构造+拷贝构造的,并没有做出优化。

➡️再看一个做了优化的例子:

class W
{
public:
    //构造函数
    W(int w) 
        :_w(w)
    {
        cout << "W()" << endl;
    }
    //拷贝构造函数
    W(const W& w) 
    :_w(w._w)
    {
        cout << "W(const W& w)" << endl;
    }
    //析构函数
    ~W(){
        cout << "~W()" << endl;
    }
private:
    int _w;
};
void func(const W w) {
    
}
int main()
{
    func(W(1));  //一个表达式步骤中,有连续的”构造+拷贝构造“
    return 0;
}

结果:

我们发现:这次没调拷贝构造了!看来编译器做了优化。

原本,这个表达式的执行顺序是:W(1)先构造出一个W,这个W再拷贝构造,传递给形参,参与表达式func()。

func(W(1));

而编译器做出了优化:

将连续的 构造+拷贝构造 优化成一步构造。

动图可以更直观地看出来:

【C++】拷贝对象时,编译器的偷偷优化_第3张图片

传匿名对象 的优化

我们学过的匿名对象,也是可以传参过去的,它的优化和刚刚讲的传值传参的优化 道理是一样的。

class W
{
public:
    W() {
        cout << "W()" << endl;
    }
    W(const W& w) {
        cout << "W(const W& w)" << endl;
    }
    ~W(){
        cout << "~W()" << endl;
    }
};
void f1(W w) {
​
}
int main()
{
    f1(W());   //先构造一个匿名对象,然后作为参数 传值传参过去
    return 0;
}

结果:

依然没调用拷贝构造。

看看程序怎么运行的:

【C++】拷贝对象时,编译器的偷偷优化_第4张图片

还是刚刚讲的,这是因为编译器的优化,将 构造+拷贝构造 直接优化成了 一步构造。

结论:一个连续的表达式步骤中,连续构造一般都会被优化。

传值返回

可以优化的情况:

class W
{
public:
    //构造函数
    W(int w=0) 
        :_w(w)
    {
        cout << "W()" << endl;
    }
    //拷贝构造函数
    W(const W& w) 
    :_w(w._w)
    {
        cout << "W(const W& w)" << endl;
    }
    //析构函数
    ~W(){
        cout << "~W()" << endl;
    }
private:
    int _w;
};
​
W func() {
    W ret;  //1次构造
    return ret;  //1次拷贝构造:将ret的值通过拷贝构造返回
}
​
int main()
{
    W w1=func();  //W w1=……仍是一次拷贝构造。所以按理说是“1构造+2拷贝构造”
    return 0;
}

然而实际上只进行了“1构造+1拷贝构造”:

【C++】拷贝对象时,编译器的偷偷优化_第5张图片

为什么拷贝构造从2次变成了1次?

原来,这也是编译器的优化,它会将两个拷贝构造,优化成一个。

这里用图说明一下:

【C++】拷贝对象时,编译器的偷偷优化_第6张图片

不能优化的情况

上面这种情况和下面的这种要区分,下面这种是不能优化的:

class W
{
public:
    //构造函数
    W(int w=0) 
        :_w(w)
    {
        cout << "W()" << endl;
    }
    //拷贝构造函数
    W(const W& w) 
    :_w(w._w)
    {
        cout << "W(const W& w)" << endl;
    }
    //赋值运算符
    W& operator=(const W& w) {
        cout << "W& operator=(const W& w)" << endl;
        if (this != &w) {
            _w = w._w;
        }
        return *this;
    }
    //析构函数
    ~W(){
        cout << "~W()" << endl;
    }
private:
    int _w;
};
W func() {
    W ret;
    return ret;
}
​
int main()
{
    W w1; 
    w1 = func();  //赋值接收对象
    return 0;
}

这种是分4步进行的:

【C++】拷贝对象时,编译器的偷偷优化_第7张图片

可见,赋值接收对象 不如 拷贝构造的方式接收。后者可以被优化。

总结

1.连续的 构造和拷贝构造 会被优化成 直接调用构造。(分步的就无法优化了)

2.产生的临时变量往往会被优化掉。

题目的解析

我们现在回过头看看一开始那道题:

【C++】拷贝对象时,编译器的偷偷优化_第8张图片

首先,W x;是1次构造。

然后,f(x)会把x的值拷贝给u,是1次拷贝构造。

在函数f(W u)里,v的实例化是1次拷贝构造.

w究竟是拷贝构造还是赋值的呢?因为w原先不存在,所以不是赋值,是1次拷贝构造。

return w;(这里最易错) 原本是要拷贝产生临时变量,再用临时变量拷贝构造出y的。但经过编译器的优化,升级成一步拷贝构造。

所以,一共1构造+4拷贝构造。

【C++】拷贝对象时,编译器的偷偷优化_第9张图片

把这题升级一下:

几次构造?几次拷贝构造?

【C++】拷贝对象时,编译器的偷偷优化_第10张图片

和上题同理,只不过这次return w将连续的三步优化成一步。

答案:1次构造 7次拷贝构造

【C++】拷贝对象时,编译器的偷偷优化_第11张图片

你可能感兴趣的:(c++,开发语言)