C++ 中的深拷贝 VS 浅拷贝

Jinku Hu 2023年1月30日 2021年4月29日
  1. 在 C++ 中默认使用浅拷贝的拷贝构造器
  2. 在 C++ 中使用自定义拷贝构造器来实现深度拷贝行为
C++ 中的深拷贝 VS 浅拷贝

本文将演示如何在 C++ 中使用深拷贝 VS 浅拷贝的多种方法。

在 C++ 中默认使用浅拷贝的拷贝构造器

C++ 类通常由几个操作定义,这些操作统称为复制控制,由用户显式指定或由编译器隐式指定。这些成员函数表示为:复制构造函数复制赋值运算符移动构造函数移动赋值运算符析构函数。复制构造函数和移动构造函数实现从相同类型的另一个对象初始化该对象时发生的操作。虽然,当这些函数由编译器隐式地综合时,某些类类型可能行为不正确。例如,管理动态内存的类将共享需要手动分配的数据成员。因此,程序员负责显式实现上述成员函数。

在这种情况下,我们演示了一个名为 Person 的类中具有两个 std::string 数据成员的复制构造函数的情况,其中一个成员是使用 new 运算符分配的。下面的示例代码显示了未明确定义复制构造函数,并且我们用另一个 Person 对象初始化了 Person 对象时会发生的情况。请注意,初始化之后,P1 存储了字符串-Buddy/Rich,并且在语句 Person P2 = P1; 中调用了复制构造函数之后,P2 具有相同的值。在 P1 对象上执行 renamePerson 函数之后,还将修改 P2 对象的 surname 数据成员。

#include <iostream>
#include <string>
#include <utility>
#include <vector>

using std::cout; using std::endl;
using std::vector; using std::string;

class Person {
public:
    Person() = default;
    Person(string n, string s) {
        name = std::move(n);
        surname = new string(std::move(s));
    }

    ~Person() {
        delete surname;
    }

    void renamePerson(const string &n, const string &s) {
        name.assign(n);
        surname->assign(s);
    };

    string& getName() { return name; };
    string& getSurname() { return *surname; };

    void printPerson() {
        cout << name << " " << *surname;
    }

private:
    string name;
    string *surname{};
};

int main()
{
    Person P1("Buddy", "Rich");
    Person P2 = P1;

    P1.printPerson();
    cout << endl;
    P2.printPerson();
    cout << endl;

    P1.renamePerson("Heinz", "Lulu");

    P1.printPerson();
    cout << endl;
    P2.printPerson();
    cout << endl;

    exit(EXIT_SUCCESS);
}

输出:

Buddy Rich
Buddy Rich
Heinz Lulu
Buddy Lulu

在 C++ 中使用自定义拷贝构造器来实现深度拷贝行为

另一方面,当我们为 Person 类实现自定义复制构造函数时,它的行为正确,并且不会在 P1.renamePerson("Heinz", "Lulu") 语句之后修改 P2 对象。在先前的代码片段中,P2 对象的 surname 成员指向与 P1 对象相同的字符串,并且 renamePerson 修改了两个对象。这次,P2 在动态内存上分配了自己的 surname 成员,并且不与 P1 对象共享。

#include <iostream>
#include <string>
#include <utility>
#include <vector>

using std::cout; using std::endl;
using std::vector; using std::string;

class Person {
public:
    Person() = default;
    Person(string n, string s) {
        name = std::move(n);
        surname = new string(std::move(s));
    }
    Person(Person &p) {
        name = p.name;
        surname = new string(*p.surname);
    }

    ~Person() {
        delete surname;
    }

    void renamePerson(const string &n, const string &s) {
        name.assign(n);
        surname->assign(s);
    };

    string& getName() { return name; };
    string& getSurname() { return *surname; };

    void printPerson() {
        cout << name << " " << *surname;
    }

private:
    string name;
    string *surname{};
};

int main()
{
    Person P1("Buddy", "Rich");
    Person P2 = P1;

    P1.printPerson();
    cout << endl;
    P2.printPerson();
    cout << endl;

    P1.renamePerson("Heinz", "Lulu");

    P1.printPerson();
    cout << endl;
    P2.printPerson();
    cout << endl;

    exit(EXIT_SUCCESS);
}

输出:

Buddy Rich
Buddy Rich
Heinz Lulu
Buddy Rich
Author: Jinku Hu
Jinku Hu avatar Jinku Hu avatar

Founder of DelftStack.com. Jinku has worked in the robotics and automotive industries for over 8 years. He sharpened his coding skills when he needed to do the automatic testing, data collection from remote servers and report creation from the endurance test. He is from an electrical/electronics engineering background but has expanded his interest to embedded electronics, embedded programming and front-/back-end programming.

LinkedIn

相关文章 - C++ Class