【问题标题】:Copy constructor + Shallow & deep copy复制构造函数 + 浅拷贝和深拷贝
【发布时间】:2020-08-02 18:11:19
【问题描述】:

我想问的是,当我没有显式编写任何复制构造函数时,编译器会自动生成默认执行浅拷贝的复制构造函数,对吗? 因此,在 main() 程序中,当我更改整数 a、b 和指针 p 的值时,只有 p 的值发生了变化,而复制的对象中 a 和 b 的值保持不变。为什么 a & b 的值也没有改变?我的代码是:

#include <iostream>
#include <string.h>
using namespace std;

class Dummy {
    private:

        int a, b;
        int *p;
    public:
        Dummy() {
            p = new int;
        }
        void setData(int x, int y, int z) {
            a = x;
            b = y;
            *p = z;

        }
        void showData() {
            cout << "a = " << a << " b = " << b;
            cout << " p = " << *p << endl;
        }

        ~Dummy() {
            delete p;
        }
};

int main() {

    Dummy d1;
    d1.setData(3, 4, 5);
    Dummy d2 = d1;
    d1.showData();
    d2.showData();
    d1.setData(6, 7, 8);
    d1.showData();
    d2.showData();
    return 0;
}

我的程序的输出是:

a = 3 b = 4 p = 5
a = 3 b = 4 p = 5
a = 6 b = 7 p = 8
a = 3 b = 4 p = 8

我的意思是,当我更改对象 d1 的值时,对象 d2 的指针发生了变化,那么为什么对象 d2 的 a & b 的值也没有变化?

我也在析构函数中使用 delete 关键字来删除动态分配的指针:

~Dummy() {
            delete p;
        }

但它反而使我的程序崩溃了。这是为什么呢?

【问题讨论】:

  • The rule of three/five/zero三法则
  • @overhaul_ 因为你复制了它们。显然,如果您复制某些内容然后更改原件,则副本不会更改。
  • @overhaul_ 这就是copy这个词的正常含义。
  • @overhaul_ 当初学者无法理解指针时,几乎总是因为它们混淆了两个不同的东西,指针的值和它所指向的东西的值。这就是你正在做的事情。您的代码复制了指针,但随后更改了它指向的值。这就是为什么您会看到 adp 的行为有所不同的原因。
  • @overhaul_ 是的,因为您的两个对象具有相同的指针值,您最终会删除相同的指针两次。这通常会导致崩溃。为避免这种情况,您应该遵循 David C Rankin 给出的关于三规则的参考。

标签: c++ pointers copy-constructor deep-copy shallow-copy


【解决方案1】:

你完全搞错了 - The idea of shallow copy。实际上,c++ 本身并没有内置任何称为 deep copy 的东西。所以,调用shallow copya bit wrong。仅仅使用这些词shallow copy 也创造了很多confusion

现在,让我解释一下,当cpp 执行initialization using assignment 时会发生什么。 cppc(复制结构时)有一个名为 bitwise copy 的概念。在这个概念中,all the member variables of one object(struct object/class object - you can say either) is identically copied to another object。现在,它是totally wrong ideaboth objects point to same memory location。实际上,both object 有他们的own memory location,当然,their variables 占据了different memory spaces。为了你,我写了一些关于记忆的测试。如果您只看到测试及其输出,您将完全理解:

#include <iostream>
#include <string.h>

using namespace std;


class Dummy {
    int a, b;
    int *p;
public:
    Dummy() {
        p = new int;
    }
    void setData(int x, int y, int z) {
        a = x;
        b = y;
        *p = z;
    }
    void showData() {
        cout << "a = " << a << " b = " << b;
        cout << " p = " << *p << endl;
        cout << endl; // an extra new line for readability of output
    }
    void showMemory() {
        cout << "addr(a) = " << &a << " addr(b) = " << &b;
        cout << " addr(p) = " << &p << endl;
    }
    ~Dummy() {
        *p = 100;
        delete p;
    }
};
// testing memory
void memoryTest() {
    cout << "testing d1:" << endl;

    Dummy d1;
    d1.setData(3, 4, 5);
    
    cout << "addr(d1) = " << &d1 << endl;
    d1.showMemory();
    cout << endl ;


    cout << "testing d2:" << endl;

    Dummy d2 = d1;
    cout << "addr(d2) = " << &d2 << endl;
    d2.showMemory();
}

int main() {
    // memoryTest
    memoryTest();

    return 0;
}

测试的输出是:

testing d1:
addr(d1) = 0x6dfed4
addr(a) = 0x6dfed4 addr(b) = 0x6dfed8 addr(p) = 0x6dfedc

testing d2:
addr(d2) = 0x6dfec8
addr(a) = 0x6dfec8 addr(b) = 0x6dfecc addr(p) = 0x6dfed0

这清楚地表明,d1d2 这两个对象占用的内存是完全不同的。

  1. 现在,您可能还有另一个问题:那么,为什么,当我写 *p=8 时,它会同时影响 d1d2

当您分配 Dummy d2 = d1; 时,我们可能会说发生了如下情况(虽然,应用按位复制时实际上并没有发生,只是为了清楚起见):

d2.p = d1.p

所以,我们知道,d1.pd2.p 包含相同的内存位置(注意:d1.p 是一个指针。因此,它不包含任何整数,而是包含一个 int 的内存地址)。

所以,当你写*p = 8时,你是在告诉程序去p指向的内存位置并将那个内存位置的值更改为8。(注意,这里你没有改变d1.p, d1.p 仍然包含相同的内存位置。相反,您只是将该内存位置的内容从 5 更改为 8)。这就是为什么当您调用d2.p 时,您会得到更改后的值。因为,d2.p 包含与d1.p 相同的内存位置。

  1. 现在,可能还有一个问题:为什么当你在析构函数中释放p 时你的代码会崩溃?

现在,让我先问你,你能释放一个内存已经释放的东西吗?您可以编写代码,但行为未定义。它可能会使您的程序崩溃,也可能什么都不做。

好吧,在Dummy destructor 你写了delete p;。现在,d2d1 将首先被销毁。假设d2 被销毁first。所以,当d2'sdestroyer 被调用时,p 就是freed。然后,d1's destroyer 将被调用,它也会尝试free p。但是p 已经被释放了。在你的情况下,程序会因为这个原因而崩溃。

希望,你现在一切都清楚了。

如果我对上面描述的内容有任何不清楚的地方,请提出问题,我也会尽力回答。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-19
    • 2013-03-09
    • 2012-04-12
    • 1970-01-01
    • 1970-01-01
    • 2015-01-13
    相关资源
    最近更新 更多