一、解答
在您提供的代码中,表达式 Foo a30 = Foo(123);
会直接调用 Foo
类中接受一个 int
类型参数的构造函数。这里是构造函数的调用过程:
-
Foo(123)
创建了一个临时的Foo
对象,使用的是接受int
参数的构造函数。这个构造函数内部会执行cout << "Foo(int)" << endl;
,从而输出 "Foo(int)"。 -
随后,这个临时对象会被用来初始化
a30
。然而,在这个特定的场景下,并不会发生拷贝构造或移动构造,因为a30
是直接被初始化的,没有涉及到临时对象的拷贝或移动。这得益于C++17引入的保证复制省略(Guaranteed Copy Elision, GCE),也称作强制复制省略,它确保了在这种情况下不会发生不必要的拷贝或移动操作。
因此,对于表达式 Foo a30 = Foo(123);
,只会调用一次构造函数,即接受 int
参数的构造函数。
如果您的编译器支持C++17或更高版本,并且您启用了相应的优化,那么上述行为是确定的。如果您使用的是更早的C++标准,编译器可能会执行拷贝构造或移动构造,尽管现代编译器通常会在这种场景下进行优化以省略不必要的操作。在C++11和C++14中,这种行为被称为拷贝省略(Copy Elision),但它不是强制的,而是编译器优化的结果。
总结来说,在C++17及以后的版本中,Foo a30 = Foo(123);
只会调用接受 int
参数的构造函数一次。
二、C++17引入的保证复制省略(Guaranteed Copy Elision, GCE),也称作强制复制省略
从C++发展历史看来,c++ 17引入了一个规则,要求在满足一定的条件下避免对象的复制,这在以前是可选的。C++17中一个叫做强制复制省略的技术就是基于这个观点的。
至少包括以下两项内容:
1. 返回值优化(RVO),即通过将返回值所占空间的分配地点从被调用端转移至调用端的手段来避免拷贝操作。返回值优化包括具名返回值优化(NRVO)与无名返回值优化(URVO),两者的区别在于返回值是具名的局部变量还是无名的临时对象。
2. 右值拷贝优化,当某一个类类型的临时对象被拷贝赋予同一类型的另一个对象时,通过直接利用该临时对象的方法来避免拷贝操作。
我们先来看看临时对象拷贝的代码:
#include <iostream>
class BigObject
{
public:
BigObject()
{
std::cout << "constructor. " << std::endl;
}
~BigObject()
{
std::cout << "destructor. " << std::endl;
}
BigObject(const BigObject& other)
{
std::cout << "copy constructor. " << std::endl;
}
};
BigObject Foo()
{
BigObject local_obj;
return local_obj;
}
int main()
{
BigObject obj = Foo();
return 0;
}
禁用“复制省略”优化
复制省略优化后
constructor.
destructor.
分析:代码在第22行通过默认构造函数创建了一个对象local_obj,在Foo()函数返回的时候,通过调用拷贝构造函数生成了一个临时的对象,这里就叫做temp_obj吧。这个temp_obj就是Foo()函数返回时生成的临时对象。当函数返回后,local_obj的生命周期结束了,调用析构函数销毁了。然后在28行,obj对象又被temp_obj对象进行拷贝初始化[尽管这里是用等于号,但这里不用赋值拷贝构造函数的,因为obj是定义的同时初始化的],又调用了一次拷贝构造函数。接着temp_obj对象析构,最后obj对象析构。