c++中的复制(深拷贝/浅拷贝/拷贝构造函数/拷贝赋值运算符/移动语义)

c++中的复制

在C++中,复制指的是将一个对象的内容复制到另一个对象中。复制可以通过拷贝构造函数、拷贝赋值运算符或移动语义来实现。

预备知识

在C++中,拷贝构造函数和赋值操作符可以执行浅拷贝或深拷贝。这两种拷贝方式涉及到对象中成员变量的复制,特别是对于动态分配的资源(比如堆上的内存)的处理。

浅拷贝(Shallow Copy)

  • 浅拷贝只复制对象中成员变量的值,而不会创建新的资源副本。如果对象中有指向堆内存的指针,浅拷贝仅复制指针的值,而不复制指针指向的实际数据。这意味着两个对象将共享同一块内存,这可能会导致问题:
    • 当一个对象的析构函数调用 delete 释放了内存,另一个对象也会受到影响,因为它们共享相同的指针。
    • 当一个对象改变了堆内存中的数据,另一个对象也会受到影响,因为它们指向相同的内存位置。

深拷贝(Deep Copy)

  • 深拷贝创建了一个新的资源副本,而不仅仅是复制指针的值。这意味着每个对象都有自己的独立内存副本,彼此之间不会相互影响。
  • 对于指向堆内存的指针,深拷贝会为每个对象分配新的内存,并将原始数据复制到新的内存中。

下面是一个示例,演示了浅拷贝和深拷贝的区别:

#include <iostream>
#include <cstring>

class MyString {
private:
    char* buffer;
public:
    // 构造函数
    MyString(const char* initialInput) {
        if (initialInput != nullptr) {
            buffer = new char[strlen(initialInput) + 1];
            strcpy(buffer, initialInput);
        } else {
            buffer = nullptr;
        }
    }
    
    // 拷贝构造函数(浅拷贝)
    MyString(const MyString& other) {
        buffer = other.buffer; // 浅拷贝,只复制指针的值
    }
    
    // 拷贝构造函数(深拷贝)
    //MyString(const MyString& other) {
    //    if (other.buffer != nullptr) {
    //        buffer = new char[strlen(other.buffer) + 1];
    //        strcpy(buffer, other.buffer);
    //    } else {
    //        buffer = nullptr;
    //    }
    //}
    
    // 析构函数
    ~MyString() {
        delete[] buffer;
    }
    
    // 打印字符串
    void Print() {
        if (buffer != nullptr) {
            std::cout << buffer;
        } else {
            std::cout << "(null)";
        }
        std::cout << std::endl;
    }
};

int main() {
    MyString str1("Hello");
    MyString str2 = str1; // 调用拷贝构造函数
    
    // 修改str1
    str1.Print(); // 输出 "Hello"
    str2.Print(); // 输出 "Hello"
    
    delete[] str1; // 删除str1的buffer
    str1.Print(); // 输出 "(null)"
    str2.Print(); // 输出 "(null)"
    
    return 0;
}

在上述示例中,我们有一个简化的 MyString 类,它具有一个 char 指针 buffer 来存储字符串。我们定义了两个拷贝构造函数:一个执行浅拷贝,另一个执行深拷贝(被注释掉了)。

当我们使用浅拷贝构造函数时,str1str2 共享相同的 buffer,这意味着当我们删除 str1buffer 后,str2buffer 也指向已经被删除的内存位置。这会导致未定义的行为。而如果我们使用深拷贝构造函数,每个对象都有自己的 buffer,因此删除 str1buffer 不会影响 str2。 因此,深拷贝通常是更安全和可靠的选择,特别是当对象包含指向动态分配内存的指针时。

1. 拷贝构造函数(Copy Constructor)

拷贝构造函数是一种特殊的成员函数,用于创建一个新对象,并将另一个对象的内容复制到新对象中。当对象以值传递的方式传递给函数、通过值返回或者通过赋值操作符进行复制时,拷贝构造函数将被调用。它的声明形式为 ClassName(const ClassName& other),其中 ClassName 是类的名称,other 是传递给拷贝构造函数的另一个同类对象的引用。

拷贝构造函数用于创建一个新对象,并将另一个对象的内容复制到新对象中。它在以下情况下被调用:

  • 当对象以值传递的方式传递给函数
  • 当对象通过值返回
  • 当对象通过赋值操作符进行复制

拷贝构造函数在C++中是默认提供的。如果你没有显式地定义拷贝构造函数,编译器会自动生成一个默认的拷贝构造函数。这个默认的拷贝构造函数执行的是浅拷贝操作,即简单地复制对象的每个成员变量的值。

默认的拷贝构造函数的形式为 ClassName(const ClassName&),其中 ClassName 是类的名称。它通常与赋值操作符的形式相似,但它的目的是将一个现有对象的内容复制到新创建的对象中,而不是修改已有对象的内容。

虽然默认的拷贝构造函数在许多情况下可以正常工作,但在特定情况下,你可能需要自定义拷贝构造函数来执行更复杂的操作,比如深拷贝或资源管理。在这种情况下,你可以通过显式地定义自己的拷贝构造函数来覆盖默认行为。

下面是拷贝构造函数的例子:

#include <iostream>

class MyClass {
private:
    int value;
public:
    // 拷贝构造函数
    MyClass(const MyClass& other) {
        std::cout << "Copy constructor called" << std::endl;
        value = other.value; // 复制另一个对象的值
    }

    // 构造函数
    MyClass(int val) : value(val) {}

    // 打印值
    void printValue() {
        std::cout << "Value: " << value << std::endl;
    }
};

int main() {
    MyClass obj1(10);
    MyClass obj2 = obj1; // 调用拷贝构造函数

    obj1.printValue(); // 输出 "Value: 10"
    obj2.printValue(); // 输出 "Value: 10"

    return 0;
}

2. 拷贝赋值运算符(Copy Assignment Operator)

拷贝赋值运算符用于将一个对象的内容复制到另一个已经存在的对象中。它通常被重载为类成员函数,并采用形式如 ClassName& operator=(const ClassName& other) 的声明,其中 ClassName 是类的名称,other 是要复制的对象的引用。拷贝赋值运算符常用于实现对象的赋值操作,例如 obj1 = obj2

#include <iostream>

class MyClass {
private:
    int value;
public:
    // 构造函数
    MyClass(int val) : value(val) {}

    // 拷贝赋值运算符
    MyClass& operator=(const MyClass& other) {
        std::cout << "Copy assignment operator called" << std::endl;
        if (this != &other) { // 避免自我赋值
            value = other.value; // 复制另一个对象的值
        }
        return *this;
    }

    // 打印值
    void printValue() {
        std::cout << "Value: " << value << std::endl;
    }
};

int main() {
    MyClass obj1(10);
    MyClass obj2(20);

    obj2 = obj1; // 调用拷贝赋值运算符

    obj1.printValue(); // 输出 "Value: 10"
    obj2.printValue(); // 输出 "Value: 10"

    return 0;
}

在C++中,如果你没有显式地定义拷贝赋值运算符(operator=),编译器会为你生成一个默认的拷贝赋值运算符。这个默认的拷贝赋值运算符执行的是浅拷贝(member-wise copy),它将一个对象的每个成员变量的值复制到另一个对象中。默认的拷贝赋值运算符的声明形式为 ClassName& operator=(const ClassName&),其中 ClassName 是类的名称。

和默认的拷贝构造函数一样,虽然默认的拷贝赋值运算符在许多情况下可以正常工作,但在特定情况下你可能需要自定义拷贝赋值运算符来执行更复杂的操作,比如深拷贝、资源管理或其他逻辑。在这种情况下,你可以通过显式地定义自己的拷贝赋值运算符来覆盖默认行为。

需要注意的是,如果类中包含了动态分配的资源(比如指针),默认的拷贝赋值运算符执行的是浅拷贝,可能会导致资源重复释放或悬挂指针等问题。因此,当类需要进行资源管理时,通常需要显式地定义拷贝赋值运算符,以确保正确地管理资源。

3. 移动语义(Move Semantics)

移动语义是C++11引入的一个重要概念,旨在提高对象的传递和赋值的效率。移动操作是一种资源转移操作,将一个对象的资源所有权从一个对象转移到另一个对象,而不是复制资源。移动语义通过移动构造函数和移动赋值运算符来实现。移动构造函数的声明形式为 ClassName(ClassName&& other),移动赋值运算符的声明形式为 ClassName& operator=(ClassName&& other),其中 ClassName 是类的名称,other 是右值引用。
这里牵扯到右值引用,如果不动的小伙伴可以参考这篇文章:cpp中的右值引用(&&)及其相关拓展知识

#include <iostream>

class MyResource {
public:
    MyResource() { std::cout << "Resource acquired" << std::endl; }
    ~MyResource() { std::cout << "Resource released" << std::endl; }
};

class MyClass {
private:
    MyResource* resource;
public:
    // 构造函数
    MyClass() : resource(new MyResource()) {}

    // 移动构造函数
    MyClass(MyClass&& other) noexcept : resource(other.resource) {
        std::cout << "Move constructor called" << std::endl;
        other.resource = nullptr; // 避免资源重复释放
    }

    // 移动赋值运算符
    MyClass& operator=(MyClass&& other) noexcept {
        std::cout << "Move assignment operator called" << std::endl;
        if (this != &other) { // 避免自我赋值
            delete resource; // 释放当前对象的资源
            resource = other.resource; // 转移资源所有权
            other.resource = nullptr; // 避免资源重复释放
        }
        return *this;
    }

    // 打印资源状态
    void printResourceStatus() {
        if (resource != nullptr) {
            std::cout << "Resource exists" << std::endl;
        } else {
            std::cout << "No resource" << std::endl;
        }
    }
};

int main() {
    MyClass obj1;
    MyClass obj2;

    obj2 = std::move(obj1); // 调用移动赋值运算符

    obj1.printResourceStatus(); // 输出 "No resource"
    obj2.printResourceStatus(); // 输出 "Resource exists"

    return 0;
}

从C++11开始,标准库引入了移动语义和右值引用的概念,以支持资源管理的优化和性能提升。

默认情况下,如果你没有显式地定义移动构造函数和移动赋值运算符,编译器不会为你自动生成这些函数,因此你需要自己编写它们来实现移动语义。 移动构造函数的形式为 ClassName(ClassName&& other),移动赋值运算符的形式为 ClassName& operator=(ClassName&& other),其中 ClassName 是类的名称。

移动语义允许在对象之间转移资源所有权而不是复制资源,这对于大型对象或包含大量资源(比如动态分配的内存或文件句柄)的对象来说,可以显著提高性能和效率。因此,在需要对资源进行移动而不是复制时,你应该自己编写移动构造函数和移动赋值运算符来利用移动语义。

  • 41
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++,每个类都有构造函数和析构函数。构造函数是一种特殊的成员函数,用于在创建对象时初始化对象的状态。析构函数是类的一个成员函数,用于在对象被销毁时释放资源。 C++也有拷贝构造函数拷贝构造函数是一种特殊的构造函数,用于创建一个新对象并将其初始化为另一个对象的副本。拷贝构造函数通常用于对象的值传递和返回值,以及对象拷贝赋值操作。 下面是一个示例代码,展示了如何定义和使用拷贝构造函数: ``` #include <iostream> using namespace std; class MyClass { private: int x; int y; public: // 构造函数 MyClass(int x, int y) { this->x = x; this->y = y; } // 拷贝构造函数 MyClass(const MyClass& obj) { this->x = obj.x; this->y = obj.y; } int getX() { return x; } int getY() { return y; } }; int main() { MyClass obj1(10, 20); MyClass obj2 = obj1; // 使用拷贝构造函数进行对象拷贝 cout << obj1.getX() << endl; // 输出:10 cout << obj2.getX() << endl; // 输出:10 obj1.setX(30); cout << obj1.getX() << endl; // 输出:30 cout << obj2.getX() << endl; // 输出:10 return 0; } ``` 在上面的示例代码,我们首先定义了一个 MyClass 类,包含了一个构造函数和一个拷贝构造函数。然后,我们创建了两个 MyClass 对象 obj1 和 obj2,其 obj2 是通过 obj1 的拷贝构造函数创建的。最后,我们修改了 obj1 的 x 属性,发现 obj2 的 x 属性并未受到影响,说明 obj1 和 obj2 是独立的两个对象

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值