在此C 代码示例中发生了两次复制构造函数的调用
is twice calls to copy constructor happen for this c++ code sample?
用于方法:
Object test(){
Object str("123");
return str;
}
然后,我有两种称呼它的方法:
代码1:
const Object &object=test();
代码2:
Object object=test();
哪个更好?如果没有优化,是否在代码2中进行了两次复制构造函数的调用?
其他有什么区别?
对于Code2,我想:
Object tmp=test();
Object object=tmp;
对于Code1,我想:
Object tmp=test();
Object &object=tmp;
但是TMP将在方法之后是解构器。因此,它必须添加const?
代码1是否没有任何问题?
让我们分析您的功能:
Object test() { Object temp("123"); return temp; }
在这里,您正在构建一个名为 temp
的本地变量,并将其从函数返回。test()
的返回类型是Object
,这意味着您是按值返回的。按值返回本地变量是一件好事,因为它允许进行一种称为返回值优化(RVO)的特殊优化技术。发生的事情是,编译器将不用调用副本或移动构造函数,而是将呼叫的编译器直接构造到呼叫者的地址中。在这种情况下,由于temp
具有名称(是lvalue),因此我们称其为n(amed)rvo。
假设进行了优化,尚未执行副本或移动。这就是您从main
调用函数的方式:
int main() { Object obj = test(); }
Main中的第一行似乎对您特别关注,因为您认为临时性将在完整表达结束时被摧毁。我假设这是引起关注的原因,因为您认为obj
不会被分配给有效的对象,并且用引用const
初始化它是一种使其生存的方法。
您对两件事是正确的:
- 临时性将在完整表达结束时被摧毁
- 用
const
的引用将其初始化将延长其寿命
但是,临时性将被摧毁的事实并不是引起人们关注的原因。因为初始化器是rvalue,所以它的内容可以从。
移动。Object obj = test(); // move is allowed here
分解复制仪,编译器将呼叫到副本或移动构造函数。因此,obj
将被初始化,就像if if'副本或移动构造函数被调用。因此,由于这些编译器的优化,我们几乎没有理由害怕多个副本。
但是,如果我们招待您的其他例子怎么办?相反,我们有合格的obj
为:
Object const& obj = test();
test()
返回Object
类型的prvalue。通常会在包含其包含的完整表达式的末尾破坏此prvalue,但是由于将其初始化为对const
的引用,因此其寿命延长到参考文献。
这个示例与上一个示例之间有什么区别?:
- 您无法修改
obj
的状态 - 它抑制移动语义
第一个子弹点很明显,但如果您不熟悉移动语义,则不是第二点。因为obj
是对const
的引用,所以它不能从中移动,并且编译器无法利用有用的优化。将对const
的引用分配给RVALUE仅在一组狭窄的情况下有用(正如Dabrain指出的那样)。相反,最好是您锻炼价值仪表并在有意义的情况下创建价值类型的对象。
此外,您甚至不需要函数test()
,您可以简单地创建对象:
Object obj("123");
但是,如果您确实需要test()
,则可以利用类型扣除并使用auto
:
auto obj = test();
您的最后一个示例涉及lvalue-reference:
[..],但是
tmp
将在方法之后被破坏。因此,我们必须添加const
?Object &object = tmp;
tmp
的破坏者是未在方法后调用的。考虑到我上面所说的话,将tmp
初始化的临时性将移至tmp
(或将其省用)。tmp
本身不会破坏它,直到它消失了。所以不,无需使用const
。
但是,如果您想通过其他一些变量参考tmp
,则可以参考。否则,如果您知道之后不需要tmp
,则可以从中移动:
Object object = std::move(tmp);
您的两个示例都是有效的 - 1 const引用是指临时对象,但是该对象的寿命延长了,直到参考范围不范围(请参阅http://herbsutter.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/)。第二个示例显然是有效的,大多数现代编译器都会优化额外的复制(如果您使用C 11 Move语义),因此出于实际目的,示例是等效的(尽管在2中,您可以修改值)。
在C 11中,std::string
具有移动构造函数/移动分配运算符,因此代码:
string str = test();
将(最坏的情况)有一个构造函数调用和一个移动分配调用。
即使没有移动语义,它也将通过NRVO(返回值优化)优化(可能)。
基本上不要害怕按价值返回。
编辑:仅仅使其100%清楚发生了什么:
#include <iostream>
#include <string>
class object
{
std::string s;
public:
object(const char* c)
: s(c)
{
std::cout << "Constructorn";
}
~object()
{
std::cout << "Destructorn";
}
object(const object& rhs)
: s(rhs.s)
{
std::cout << "Copy Constructorn";
}
object& operator=(const object& rhs)
{
std::cout << "Copy Assignmentn";
s = rhs.s;
return *this;
}
object& operator=(object&& rhs)
{
std::cout << "Move Assignmentn";
s = std::move(rhs.s);
return *this;
}
object(object&& rhs)
: s(std::move(rhs.s))
{
std::cout << "Move Constructorn";
}
};
object test()
{
object o("123");
return o;
}
int main()
{
object o = test();
//const object& o = test();
}
您可以看到每个构造函数调用和1个destructor调用 - NRVO在此处启动(如预期的),将复制/移动射出。
代码1是正确的。正如我所说,C 标准可以保证临时参考的临时性是有效的。主要用途是富含浓度的多态性行为:
#include <iostream>
class Base { public: virtual void Do() const { std::cout << "Base"; } };
class Derived : public Base { public: virtual void Do() const { std::cout << "Derived"; } };
Derived Factory() { return Derived(); }
int main(int argc, char **argv)
{
const Base &ref = Factory();
ref.Do();
return 0;
}
这将返回"派生"。一个著名的例子是Andrei Alexandrescu的ScopeGuard,但使用C 11,这甚至更简单。
- g++的分段错误(在NaN上使用to_string两次时)
- 蛇在C++不会连续转两次
- 检查一个数组是否包含在另一个数组中,以相反的顺序,至少两次
- 从具有按值捕获的 lambda 移动构造 std::函数时,移动构造函数调用两次
- 我应该如何去缓解两次出现的cin?
- Realloc 两次无法在 Visual Studio 上运行
- 使用 getline(cin, var) 两次在进行字符串比较时会产生错误 (==)
- 为什么转换运算符调用复制构造函数两次,而等效函数只调用它一次
- 为什么在下面的代码中调用复制构造函数两次
- 在此示例中,向量是否复制了两次
- 为什么在此代码代码段中将复制构造函数两次称为两次
- 为什么在 C++ 中,当对象包含在另一个对象中时,复制构造函数被调用两次
- 在此C 代码示例中发生了两次复制构造函数的调用
- 这是复制值两次吗?
- 为什么在这种情况下我的复制构造函数只被调用两次?
- 复制构造函数——用相同的变量按值调用函数两次会导致问题
- 为什么map.insert()方法调用复制构造函数两次?
- 复制构造函数调用了两次
- Destructor被调用两次,而没有复制构造函数或赋值运算符被调用
- 为什么在执行vector.push_back操作时要调用两次复制构造函数