"Rule of Three" 的执行出错了

Implementation of "Rule of Three" gone wrong

下面是"The rule of three"的错误实现,我试着去理解。

调试程序,我发现调试器在清理 int *k 时出现问题,这可以通过定义 int *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.

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

然后当它被破坏时它会尝试破坏它两次。或者根据堆栈布局等随机因素,它可能根本不会复制 k 然后它会尝试 delete 一个无效的指针。

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

您在 operator = 中通过按值传递隐式地使用此构造函数;但是您未能将 k 分配给该构造函数中的任何值。

这导致交换用无效的k替换有效的k,然后删除无效的k;导致你崩溃。

其实很简单:你的拷贝构造函数不做拷贝。事实上,它没有初始化任何成员,所以这个构造函数创建的任何实例都是垃圾。

对于operator=(Vector2 other)的调用调用了拷贝构造函数来创建other(这就是三分法则的要点),所以other里面全是废话。 然后你将this(又名v1)的有效kother.

的蹩脚k交换

然后,当 v1 的析构函数被调用时,它会在糟糕的 k --> 访问冲突上调用 delete[] k

解决方案

让你的复制构造函数制作一个副本。或者至少,使其正确初始化 k(例如 nullptr)。

可以通过列出事件的确切顺序来得出解决方案,例如:更多的打印输出和测试,什么时候调用什么参数:

开始于:v1 = v2;

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

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

第2步和第3步基本上就是"hiding",把错误从v3转移到v1上。

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