提问人:KeyC0de 提问时间:9/2/2019 更新时间:9/2/2019 访问量:86
使用复制构造函数创建对象(三类的简单规则)会产生运行时错误
Creating object with Copy Constructor (Simple Rule of Three Class) yields run-time error
问:
我有下面的简单程序:
#include <iostream>
class Counter
{
private:
size_t m_count;
public:
Counter() :
m_count(0)
{
std::cout << "defctor" << '\n';
}
Counter(size_t count) :
m_count(count)
{
std::cout << "ctor" << '\n';
}
~Counter() {
std::cout << "dtor" << '\n';
}
Counter(const Counter& rhs) :
m_count{0}
{
Counter temp{rhs.m_count};
std::cout << "cctor" << '\n';
std::swap(*this, temp);
}
Counter& operator=(const Counter& rhs)
{
if (this == &rhs) {
return *this;
}
Counter temp{rhs.m_count};
std::swap(*this, temp);
std::cout << "caop" << '\n';
return *this;
}
constexpr int getCount() const noexcept {
return this-> m_count;
}
};
int main() {
Counter x{1};
Counter y;
Counter z{x}; // this fails
}
我试图构建一个简单的三类规则。我在这一行上得到了 UB,它应该调用复制构造函数。输出:Counter z{x};
ctor
defctor
ctor
cctor
ctor
cctor
ctor
cctor
ctor
cctor
ctor
cctor
ctor
cctor
ctor
cctor
ctor
cctor
ctor
cctor
ctor
cctor
ctor
cctor
ctor
cctor
ctor
然后它重复......ctor\ncctor
我已经有一段时间没有使用 C++ 了。我只是找不到错误!
答:
3赞
Max Langhof
9/2/2019
#1
std::swap
仅当已定义移动赋值运算符时才使用移动赋值运算符(一旦您添加了几乎任何其他特殊成员函数,编译器将不会定义一个运算符)。你没有,所以它回退到复制分配。
但是您的复制赋值运算符是根据 定义的。这当然是一个无休止的递归:要交换,你需要赋值,而赋值你需要交换。最终,你会得到一个堆栈溢出。std::swap
您可以直接在复制构造函数中初始化(并直接在复制赋值运算符中修改它)。看起来你在做半个复制和交换的成语,但我不确定你在这里到底想做什么。m_count
是的,在现代 C++ 中,您还应该在适当的地方实现移动构造函数。如果操作正确,这将修复递归。我建议您查看一些示例,说明如何正确实现这些特殊成员函数。std::swap
评论
0赞
Scheff's Cat
9/2/2019
看起来你正在做一半的复制和交换成语提到“货物邪教节目”不是个好机会吗?;-)
0赞
KeyC0de
9/3/2019
@Max 所以你是说使用移动赋值运算符来 ?很有意思,我不知道。这是否等同于cppreferences网站所说的“必须满足MoveAssignable和MoveConstructible的要求”,这意味着必须为类定义移动构造函数和移动赋值运算符。我说得对吗?如果是这样,那对我来说是一个可怕的时刻,可以吝啬这些事情。我认为它是“小写字母”。std::swap
std::move
T
T
1赞
Max Langhof
9/3/2019
@Nikos 是的,确实如此。尝试在不使用它的情况下自己写 - 你会发现它很难:)std::swap
0赞
KeyC0de
9/3/2019
@MaxLanghof 有点奇怪的是,我发现这个网站经常被链接到复制构造和复制分配运算符实现细节(我也咨询过)在最后没有提到“创建一个例外安全复制分配运算符”为了正确使用复制和交换习语,您还必须创建移动构造函数和移动赋值运算符。他们怎么能错过这个!?
1赞
Max Langhof
9/4/2019
@Nikos 哦,我明白你哪里出错了。如果你想实现复制和交换,那么你应该用副本中的一个成员交换每个成员(你链接的页面正确地做到这一点,你的代码没有 - 你应该交换[这并不比一开始就复制它好])。不要创建自己的副本并试图与之交换,这是行不通的。此外,如果有必要,将(如此处所示)回退到复制构造 + 复制分配(但如果可用,请使用移动操作)。m_count
std::swap
2赞
Murat Tunç
9/2/2019
#2
在我看来,您应该像这样更改您的复制构造函数:
Counter(const Counter& rhs)
{
m_count=rhs.m_count;
}
这就足够了。结果是:
ctor
defctor
dtor
dtor
dtor
Press any key to continue ...
评论
0赞
Max Langhof
9/2/2019
最好在此处使用成员初始值设定项列表(即 .Counter(const Counter& rhs) : m_count(rhs.m_count) {}
评论
std::swap(*this, temp);