使用复制构造函数创建对象(三类的简单规则)会产生运行时错误

Creating object with Copy Constructor (Simple Rule of Three Class) yields run-time error

提问人:KeyC0de 提问时间:9/2/2019 更新时间:9/2/2019 访问量:86

问:

我有下面的简单程序:

#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++ 了。我只是找不到错误!

C++ 运行时错误 复制构造函数 定义行为 三法则

评论

3赞 tkausl 9/2/2019
除了复制之外,您还期望做什么?std::swap(*this, temp);
0赞 alon 9/2/2019
你为什么要做 Counter z{x};而不是计数器 z(x);
3赞 KeyC0de 9/2/2019
@alon为什么不呢?我已经习惯了将 init 作为初始化内容的更好方法。
2赞 Lightness Races in Orbit 9/2/2019
@alon 因为这就是我们现在所做的
2赞 n. m. could be an AI 9/2/2019
您几乎应该始终使用零规则

答:

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::swapstd::moveTT
1赞 Max Langhof 9/3/2019
@Nikos 是的,确实如此。尝试在不使用它的情况下自己写 - 你会发现它很难:)std::swap
0赞 KeyC0de 9/3/2019
@MaxLanghof 有点奇怪的是,我发现这个网站经常被链接到复制构造和复制分配运算符实现细节(我也咨询过)在最后没有提到“创建一个例外安全复制分配运算符”为了正确使用复制和交换习语,您还必须创建移动构造函数和移动赋值运算符。他们怎么能错过这个!?
1赞 Max Langhof 9/4/2019
@Nikos 哦,我明白你哪里出错了。如果你想实现复制和交换,那么你应该用副本中的一个成员交换每个成员(你链接的页面正确地做到这一点,你的代码没有 - 你应该交换[这并不比一开始就复制它好])。不要创建自己的副本并试图与之交换,这是行不通的。此外,如果有必要,将(如此处所示)回退到复制构造 + 复制分配(但如果可用,请使用移动操作)。m_countstd::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) {}