实施“三规”错了

时间:2019-03-13 15:54:03

标签: c++ delete-operator rule-of-three

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

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

但是,我不明白该程序导致的错误(访问冲突)是如何出现的。

我确实知道,在复制分配构造函数v1的{​​{1}}不再指向有效的内存地址之后。

int *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;
    }

4 个答案:

答案 0 :(得分:3)

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

对于operator=(Vector2 other)的调用,将调用复制构造函数来创建other(这是三个规则的要点),因此other充满了废话。 然后,将k(又名this)的有效v1k的the脚other交换。

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

解决方案

使您的副本构造函数进行复制。或至少使其正确初始化k(例如,初始化为nullptr)。

答案 1 :(得分:0)

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

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

答案 2 :(得分:0)

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

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

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

答案 3 :(得分:0)

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

开始于:v1 = v2;

  1. v2使用参数other(无论其他参数如何)调用复制构造函数,尤其是:其int* k并不指向有效内存。为简单起见,我们将其称为新的Vector2 v3。
  2. 副本分配构造函数现在通过v3调用。
  3. 然后我们开始交换。

由于在步骤1中未正确初始化v3,因此在副本构造函数中实际上出现了错误。

步骤2和步骤3基本上是“隐藏”,将错误从v3转移到v1上。

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