题目描述
以下代码共调用多少次拷贝构造函数?
Widget f(Widget u)
{
Widget v(u);
Widget w = v;
return w;
}
int main()
{
Widget x;
Widget y = f(f(x));
}
在做这道题之前,要先铺垫一些前置知识!
前导一
先看以下代码,调用了多少次拷贝构造呢?
class Weight
{
public:
//构造函数
Weight()
{
cout << "Weight()" << endl;
}
//拷贝构造
Weight(const Weight& w)
{
cout << "Weight(const Weight& w)" << endl;
}
};
void f(Weight u) //传值传参
{
;
}
int main()
{
Weight w;
f(w);
return 0;
}
因为 f
函数的形参部分是传值传参,所以会调用一次拷贝构造
前导二
以下代码,调用了多少次拷贝构造呢?
class Weight
{
public:
//构造函数
Weight()
{
cout << "Weight()" << endl;
}
//拷贝构造
Weight(const Weight& w)
{
cout << "Weight(const Weight& w)" << endl;
}
};
Weight f(Weight u) // 传值传参
{
Weight v(u); // 用u拷贝构造了v
Weight w = v; // 用v拷贝构造了w
return w;
}
int main()
{
Weight x;
f(x);
return 0;
}
首先传值传参会调用一次拷贝构造;
接着用 u
拷贝构造了 v
,那么又会调用一次拷贝构造;
然后用 v
拷贝构造了 w
,又会调用一次拷贝构造;
最后返回了 w
,那么又会调用一次拷贝构造;
所以总共是 4 次拷贝构造:
前导三
我们对前导二的代码修改一下
class Weight
{
public:
//构造函数
Weight()
{
cout << "Weight()" << endl;
}
//拷贝构造
Weight(const Weight& w)
{
cout << "Weight(const Weight& w)" << endl;
}
};
Weight f(Weight& u) // 引用传参
{
Weight v(u); // 用u拷贝构造了v
Weight w = v; // 用v拷贝构造了w
return w;
}
int main()
{
Weight x;
f(x);
return 0;
}
把 f
函数形参部分用了 引用传参,那么就只会调用 3 次拷贝构造
前导四
我们对前导三的代码修改一下
class Weight
{
public:
//构造函数
Weight()
{
cout << "Weight()" << endl;
}
//拷贝构造
Weight(const Weight& w)
{
cout << "Weight(const Weight& w)" << endl;
}
};
Weight& f(Weight& u) // 引用传参
{
Weight v(u); // 用u拷贝构造了v
Weight w = v; // 用v拷贝构造了w
return w; // 拿引用作为返回值
}
int main()
{
Weight x;
f(x);
return 0;
}
我们把 f
函数的返回值改成了 引用作返回值,那么就只会调用 2 次拷贝构造
匿名对象
这里我们要提一个新的概念:匿名对象
比如下面这段代码:
class Weight
{
public:
//构造函数
Weight()
{
cout << "Weight()" << endl;
}
//拷贝构造
Weight(const Weight& w)
{
cout << "Weight(const Weight& w)" << endl;
}
//析构函数
~Weight()
{
cout << "~Weight()" << endl;
}
};
int main()
{
// 匿名对象,声明周期只在这一行
Weight();
return 0;
}
Weight()
是一个匿名对象,它会默认去调用 拷贝构造+析构函数,并且它的声明周期只在这一行里面,到了下一行时,就不存在了,因为已经被析构了。
前导一
既然知道了匿名对象,我们来看下面这段代码。调用了多少次拷贝构造呢?
class Weight
{
public:
//构造函数
Weight()
{
cout << "Weight()" << endl;
}
//拷贝构造
Weight(const Weight& w)
{
cout << "Weight(const Weight& w)" << endl;
}
//析构函数
~Weight()
{
cout << "~Weight()" << endl;
}
};
Weight f(Weight u)
{
Weight v(u);
Weight w = v;
return w;
}
int main()
{
f(Weight());
return 0;
}
为什么是 3 次呢?
因为这里涉及到编译器的优化,它把 Weight()
的拷贝构造 和 f
函数形参部分的构造 合二为一 了,所以只有 3 次拷贝构造,但是却有 4 次析构。
这里理解起来可能有点抽象,我再举个例子吧
前导二
我们再来看以下代码
class Weight
{
public:
//构造函数
Weight()
{
cout << "Weight()" << endl;
}
//拷贝构造
Weight(const Weight& w)
{
cout << "Weight(const Weight& w)" << endl;
}
//析构函数
~Weight()
{
cout << "~Weight()" << endl;
}
};
Weight f(Weight u)
{
Weight v(u);
Weight w = v;
return w;
}
int main()
{
Weight x;
Weight ret = f(x);
return 0;
}
运行结果:
为什么会是 4 次呢?
因为在一个表达式中,如果出现连续步骤的构造+拷贝构造,或者拷贝构造+拷贝构造,那么胆大的编译器可能会进行优化,合二为一。
也就是说,下面的第 4 次构造和第 5 次构造合为一次了:
前导三
上面说了,如果出现连续步骤的 构造+拷贝构造,或者 拷贝构造+拷贝构造,那么胆大的编译器可能会进行优化,合二为一。
那么如何来证实这句话是不是真的呢?如果不出现不连续的情况,那么编译器是不是就不会进行 合二为一 呢?
我们可以来举例验证一下
class Weight
{
public:
//构造函数
Weight()
{
cout << "Weight()" << endl;
}
//拷贝构造
Weight(const Weight& w)
{
cout << "Weight(const Weight& w)" << endl;
}
//赋值重载
Weight& operator=(const Weight& w)
{
cout << "Weight& operator=(const Weight& w)" << endl;
return *this;
}
//析构函数
~Weight()
{
cout << "~Weight()" << endl;
}
};
Weight f(Weight u)
{
Weight v(u);
Weight w = v;
return w;
}
int main()
{
Weight x;
Weight ret;
ret = f(x);
return 0;
}
运行结果:
可以看到,在这种情况下,编译器优化的步骤就会被阻断。
这个实验只想说明,对于编译器合二为一的优化,只有在连续步骤的 构造+拷贝构造,或者 拷贝构造+拷贝构造 的情况下,才会进行。
所以 Weight ret = f(x);
这种写法是最优的,因为只会调用 4 次拷贝构造;
而 Weight ret; ret = f(x);
这种写法会调用 4 次拷贝构造 + 1 次赋值。
回到开头
那么我们的前导知识已经补充完了,再来回到文章开头,以下代码共调用多少次拷贝构造函数?
Widget f(Widget u)
{
Widget v(u);
Widget w = v;
return w;
}
int main()
{
Widget x;
Widget y = f(f(x));
}
分析过程如下:
f(x)
:用 w
拷贝构造了临时对象,然后用这个临时对象作为表达式的返回值又去拷贝构造,那么这个时候,连续的步骤会被优化成 1 次。
也就是说 4 和 5 会被优化成一次;8 和 9 会被优化成一次;
所以程序最终调用了 7 次拷贝构造。
总结
总结一下,如果程序中出现连续步骤的 构造+拷贝构造,或者 拷贝构造+拷贝构造,那么编译器可能会进行优化,合二为一。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/80849.html