C++ 在C++';为什么operator=不调用copyctor?
下面的“最小”示例应说明的使用 现在,该怎么办?我假设行C++ 在C++';为什么operator=不调用copyctor?,c++,copy-constructor,C++,Copy Constructor,下面的“最小”示例应说明的使用 现在,该怎么办?我假设行a=b.get_new()将制作硬拷贝,即分配一个新字符串。原因:operator=()按照此设计模式中的典型情况,按值获取其参数,该值调用复制构造函数,该构造函数将生成深度复制。到底发生了什么事 std ctor called std ctor called string ctor called, address:0x433d0b0 operator= called 0x433d0b0 复制ctor从未被调用过,因此,复制是软的-两个指
a=b.get_new()代码>将制作硬拷贝,即分配一个新字符串。原因:operator=()
按照此设计模式中的典型情况,按值获取其参数,该值调用复制构造函数,该构造函数将生成深度复制。到底发生了什么事
std ctor called
std ctor called
string ctor called, address:0x433d0b0
operator= called
0x433d0b0
复制ctor从未被调用过,因此,复制是软的-两个指针相等。为什么不调用copy-ctor?不清楚为什么希望使用copy-ctor。get_new()函数在返回值时不会创建C对象的新副本。这是一个优化,任何C++编译器都实现它。< P> C++允许优化返回类实例的函数中的“复制复制”结构。
在get_new
中,直接返回从\u str
成员新构造的对象,然后将其用作赋值的源。这被称为“返回值优化”(RVO)
注意,虽然编译器可以自由地优化复制构造,但仍然需要检查复制构造是否可以合法调用。例如,如果有一个非友元函数返回实例,而不是一个成员函数,并且复制构造函数是私有的,那么即使在使函数可访问之后,该副本可能最终被优化掉,也会出现编译器错误。这些副本将被删除
没有副本,因为b.get_new()
正在构造它的“临时”C
对象,其位置正好是operator=
的参数。编译器能够管理这一点,因为所有内容都在一个翻译单元中,因此它有足够的信息来执行此类转换
您可以使用标志-fno elide constructors
消除clang和gcc中的构造省略,然后输出如下所示:
std ctor called
std ctor called
string ctor called, address:0x1b42070
copy ctor called
copy ctor called
operator= called
0x1b420f0
std ctor called. Address: 0x1cdf010
std ctor called. Address: 0x1cdf070
string ctor called, address:0x1cdf070
operator= called
0x1cdf070
通过返回值优化消除第一个副本。使用RVO,函数构造最终直接返回到返回值所在位置的对象
我不确定是否有一个特殊的名称来省略第二份。这是从get_new()
的返回值复制到operator=()
的参数中
正如我之前所说的,同时删除两个副本会导致get_new()
将其对象直接构造到空间中,以便参数operator=()
请注意,两个指针相等,如:
std ctor called
std ctor called
string ctor called, address:0xc340d0
operator= called
0xc340d0
本身并不表示错误,这不会导致双重自由;由于该副本已被省略,因此不会有该对象的其他副本保留对所分配字符串的所有权,因此不会有其他可用副本
但是,您的代码确实包含一个与三个规则无关的错误:get_new()
正在传递一个指针,指向对象自己的str
成员及其创建的显式对象(在输出中的“string-ctor-called,address:0xc340d0”行)正在获取已由原始对象(b
)管理的str
对象的所有权。这意味着b
和在get_new()
中创建的对象都试图管理相同的字符串,这将导致双重释放(如果实现了析构函数)
要查看此更改,请更改默认构造函数以显示它创建的str
:
C()
: str(new std::string("default constructed"))
{
std::cout << "std ctor called. Address: " << str << std::endl;
}
所以打印的最后两个指针相同没有问题。问题在于打印第二个和第三个指针。修复获取新内容()
:
将输出更改为:
std ctor called. Address: 0xec3010
std ctor called. Address: 0xec3070
string ctor called, address:0xec30d0
operator= called
0xec30d0
并用双自由度解决任何潜在问题。允许省略。@chris允许返回值优化吗?我怎么能强迫别人叫它呢?@Johannes你不能,它是语言的一部分。您必须对类(尤其是复制构造函数)进行编码,以便可以省略它。在您的例子中,这意味着get_new()
必须将新分配的字符串传递给它调用的ctor。它不会被返回,因此不会返回RVO,但也不允许复制参数by value。对于GCC,有类似于-fno elide copies
的东西。这是否意味着对于三元规则,您永远不能依靠操作符=
进行硬拷贝?RVO非常特定于从函数返回值,与三元规则无关。哦,的确,Wikipedia使用const引用实现operator=
,然后显式调用copy-ctor。似乎不适用于这种优化。谢谢。相反,我尝试了维基百科对operator=
的实现。“现在可以了。@Johannes,如果您遇到了双重自由的问题,而这与您最初在这里实现三的规则时遇到的任何问题无关。问题出在get_new()
中,您将str
传递到正在创建的新对象中。C(std::string*)
构造函数对传入的字符串拥有所有权,但由于在get_new()
中传入了一个已经拥有的字符串,最终得到了一个由两个对象拥有的字符串。get_str()
的正确实现如下:C get_new(){return C(new std::string(*str))}
True,尽管这只是一个简单的测试,没有任何free
s/delete
s。在我最初的方法中,我有一个共享指针。get_new
应该是这样工作的。可能是3规则的一个坏例子。@Johannes好吧,如果你使用的是共享的\u ptr
,问题不是双重免费,而是当你想要一份深度副本时无法获得,我认为问题仍然存在
std ctor called. Address: 0x1cdf010
std ctor called. Address: 0x1cdf070
string ctor called, address:0x1cdf070
operator= called
0x1cdf070
C get_new() {
return C(new std::string(*str));
}
std ctor called. Address: 0xec3010
std ctor called. Address: 0xec3070
string ctor called, address:0xec30d0
operator= called
0xec30d0