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