“三法则”的实施出了问题

Implementation of "Rule of Three" gone wrong

提问人:Imago 提问时间:3/13/2019 更新时间:1/11/2020 访问量:134

问:

下面是“三法则”的错误实现,我试图理解。

调试程序时,我发现调试器在清理时遇到了问题,可以通过在复制构造函数中定义或简单地将其设置为合理的值来解决。int *kint *k = nullptr

但是,我不明白程序产生的错误(访问违规)是如何产生的。

我确实知道,在复制赋值构造函数不再指向有效的内存地址之后。v1int *k

class Vector2 {
public:
    std::string name = "default";
    int* k;

    Vector2(int s, std::string n) : k(new int[s]), name(n) {
    }

    Vector2(const Vector2 &other)  {
        std::cout<< "Copy constructor: " << name << std::endl;
    }

    ~Vector2() {
        std::cout << "Deleting: " << name << std::endl;
        delete[] k;
    }

    void swap(Vector2& other) {
        using std::swap;
        swap(k, other.k);
    }

    Vector2& operator=(Vector2 other) {
        std::cout << "Copy assignment constructor: " << name << std::endl;
        swap(other);
        return *this;
    }
};


int main() {
        Vector2 v1 = Vector2(2, "v1");
        Vector2 v2 = Vector2(4, "v2");
        v1 = v2;
        std::cout << &v1 << " " << &v2 << std::endl;
        std::cout << &v1.k << " " << &v2.k << std::endl;
        return 0;
    }

下面是上述程序的控制台输出:

Copy constructor: default
Copy assignment constructor: v1
Deleting: default
0000001B5611FA28 0000001B5611FA78
0000001B5611FA50 0000001B5611FAA0
Deleting: v2
Deleting: v1
16:18:42: The program has unexpectedly finished.
c++ 删除运算符 三分法则

评论

3赞 Neijwiert 3/13/2019
从 C++11 及以后,它是五法则
0赞 463035818_is_not_an_ai 3/13/2019
从 C++98 及以后,零法则是你的朋友;)
1赞 Algirdas Preidžius 3/13/2019
你的复制构造函数,真的做了任何复制吗?
0赞 Tharwen 3/14/2019
您还需要通过引用将 Vector2 传递到复制赋值运算符中。理想情况下作为常量参考,但我不确定这是否绝对必要
1赞 PaulMcKenzie 3/14/2019
您必须有一个有效的、没有错误的复制构造函数,以及一个有效的、没有错误的析构函数,才能使 3 法则正常工作。你的复制构造函数不会复制任何东西 -- bug。底线是,复制构造函数不能是一个基本上是空的“存根”函数——要么完全实现它,要么将其声明为 -ed。您不能使用存根复制构造函数可靠地运行程序,或者实际上将 3 个函数中的任何一个作为稍后要填充的存根。delete

答:

0赞 Zan Lynx 3/13/2019 #1

构造使用复制构造函数,该构造函数不会创建指向值的新副本。您的复制构造函数甚至可能不会复制,因为它是 POD 类型,因此不一定是默认构造或默认复制。otheroperator=k

然后,当它被破坏时,它会试图摧毁它两次。或者,根据堆栈布局等随机因素,它可能根本不会复制,然后尝试指向无效指针。kdelete

评论

4赞 1201ProgramAlarm 3/14/2019
它不会被销毁两次。新构造的对象不会初始化 ,当析构函数尝试删除此未初始化的值时,会导致崩溃。k
0赞 Zan Lynx 3/14/2019
@1201ProgramAlarm 你试过吗,因为我得到了一份副本。 或者以某种方式使用相同的指针。但你在技术上是对的,我更新了我的答案。
0赞 Imago 3/14/2019
@1201ProgramAlarm,我也这么认为 - 正如所写的那样 - k 不指向有效的内存地址。但是,在哪一步它变得无效?
2赞 Zan Lynx 3/14/2019
它不会覆盖 .它创建一个全新的副本,因为函数参数不是引用。v1operator=
2赞 Zan Lynx 3/14/2019
或者,OK。 当用v1swapkother.k
0赞 UKMonkey 3/14/2019 #2

您的问题在Vector2(const Vector2 &other)

您可以通过传递值来隐式使用此构造函数;但是您未能将 k 赋值给该构造函数中的任何值。operator =

这会导致交换将有效的 k 替换为无效的 k,然后删除无效的 k;导致您的崩溃。

3赞 sebrockm 3/14/2019 #3

这实际上很简单:您的复制构造函数不会创建副本。实际上,它不会初始化任何成员,因此该构造函数创建的任何实例都充满了废话。

对于复制构造函数的调用被调用来创建(这是三分法则的重点),所以充满了废话。 然后你把 (aka ) 的有效性换成 的蹩脚的 .operator=(Vector2 other)otherotherkthisv1kother

然后,当调用析构函数 of 时,它会调用一个蹩脚的 --> 访问冲突。v1delete[] kk

溶液

使复制构造函数 制作副本。或者至少,让它正确初始化(例如 to )。knullptr

0赞 Imago 3/14/2019 #4

可以通过布置事件的确切顺序来得出解决方案,例如:更多的打印输出和测试,在以下情况下调用哪些参数:

起步时间:v1 = v2;

  1. v2使用参数 other(无论 other 是什么)调用 copy 构造函数,特别是:ITS 不指向有效内存。为简单起见,我们将其称为新的 Vector2 v3。int* k
  2. 复制分配构造函数现在使用 v3 调用。
  3. 然后我们开始交换。

该错误实际上出现在复制构造函数中,因为在步骤 1 中未正确初始化。v3

第 2 步和第 3 步基本上是“隐藏”,将错误从 转移到 .v3v1

现在有趣的问题是,实际上是如何生成的?不是默认构造函数!v3