C++(深拷贝和浅拷贝)

目录

浅拷贝:

示例:

深拷贝:

总结:


C++中类的默认拷贝构造函数均为浅拷贝,在类进行析构时,就会可能导致对同一块内存进行双重释放或者悬空指针问题。为了防止这种情况,就需要进行深拷贝,来进行避免这种情况的发生。

悬空指针:是指已释放的内存区域的指针,但是未进行置空或者函数返回局部变量地址或者超出作用域后未更新指针。使用悬空指针可能会导致程序崩溃、数据损坏或者未定义行为。

移动构造在进行拷贝时,注意将被移动对象的指针进行置空,下面例子为了能够观察其指向地址,所以未对其进行置空。

释放之后未置空:

cpp 复制代码
int* ptr = new int(10);
delete ptr;  // 内存释放,但ptr仍保留原地址
*ptr = 20;   // 危险:解引用悬空指针

函数返回局部变量地址:

cpp 复制代码
int* getLocalPtr() {
    int x = 10;
    return &x;  // 返回局部变量地址(悬空指针)
}
int* ptr = getLocalPtr();  // ptr指向已销毁的栈内存

对象生命周期结束:

cpp 复制代码
int* ptr;
{
    int y = 30;
    ptr = &y;  // ptr指向y的地址
}  // y超出作用域被销毁
*ptr = 40;     // 悬空指针访问

浅拷贝:

只是复制对象的值,所以新旧对象都是共享同一块内存数据,适用于无动态资源管理或者明确需要共享数据的场景。

示例:

cpp 复制代码
#include <iostream>
#include <string>
#include <thread>

class Student {
public:
    Student():_name(""),_age(0),_data(new int(0)) {
        std::cout << "无参构造" << std::endl;
    }
    Student(std::string name, int age,int number):_name(name),_age(age),_data(new int(number)) {
        std::cout << "有参构造" << std::endl;
    }
    Student(const Student &student):_name(student._name),_age(student._age),_data(student._data) {//const可以加可以不加,主要是为了防止对参数进行修改,但是&是必须加
        std::cout << "拷贝构造" << std::endl;
    }
    Student(Student && student) noexcept:_name(std::move(student._name)),_age(student._age),_thread(std::move(student._thread)),_data(student._data){
        std::cout << "移动构造" << std::endl;
    }
    ~Student() {
        std::cout << "析构函数" << std::endl;
        //delete _data;
    }
    
    void GetTarget() {
        std::cout << "获取的target的值:";
        std::cout << _target << std::endl;
        std::cout << std::endl;
    }

    void ModifyTarget(int target) {
        std::cout << "修改target之后的值:";
        _target = target;
        std::cout << _target<<std::endl;
    }

    void GetName() {
        std::cout << _name << std::endl;
    }

    void GetAge() {
        std::cout << _age << std::endl;
    }

    void GetData() {
        std::cout << _data << std::endl;
    }
private:
    std::string _name;
    int _age;
    static int _target;//类内声明,类外初始化
    std::thread _thread;
    int* _data;
};
int Student::_target = 100;

int main() {
    Student student1("小明",16,10);
    Student student2(student1);
    student1.GetName();
    student1.GetAge();
    student1.GetData();
    student2.GetName();
    student2.GetAge();
    student2.GetData();
    return 0;
}

运行结果:

调用拷贝构造时,成员变量_data只是简单的将值赋值给了student2的_data,所以指向了同一块内存,运行结果也可以看出地址相同,当在析构函数中对_data进行delete进行释放时,就会导致双重释放。

cpp 复制代码
#include <iostream>
#include <string>
#include <thread>

class Student {
public:
    Student():_name(""),_age(0),_data(new int(0)) {
        std::cout << "无参构造" << std::endl;
    }
    Student(std::string name, int age,int number):_name(name),_age(age),_data(new int(number)) {
        std::cout << "有参构造" << std::endl;
    }
    Student(const Student &student):_name(student._name),_age(student._age),_data(student._data) {//const可以加可以不加,主要是为了防止对参数进行修改,但是&是必须加
        std::cout << "拷贝构造" << std::endl;
    }
    Student(Student && student) noexcept:_name(std::move(student._name)),_age(student._age),_thread(std::move(student._thread)),_data(student._data){
        std::cout << "移动构造" << std::endl;
    }
    ~Student() {
        std::cout << "析构函数" << std::endl;
        //delete _data;
    }
    
    void GetTarget() {
        std::cout << "获取的target的值:";
        std::cout << _target << std::endl;
        std::cout << std::endl;
    }

    void ModifyTarget(int target) {
        std::cout << "修改target之后的值:";
        _target = target;
        std::cout << _target<<std::endl;
    }

    void GetName() {
        std::cout << _name << std::endl;
    }

    void GetAge() {
        std::cout << _age << std::endl;
    }

    void GetData() {
        std::cout << _data << std::endl;
    }
private:
    std::string _name;
    int _age;
    static int _target;//类内声明,类外初始化
    std::thread _thread;
    int* _data;
};
int Student::_target = 100;

int main() {
    Student student1("小明",16,10);
    Student student2(std::move(student1));
    student1.GetName();
    student1.GetAge();
    student1.GetData();
    student2.GetName();
    student2.GetAge();
    student2.GetData();
    return 0;
}

运行结果:

移动构造时,进行浅拷贝也会导致公用同一块内存。

深拷贝:

复制指针指向的实际数据,并为新对象分配独立的内存,这样每个对象拥有完全独立的资源,就能够避免共享同一块内存的问题,从而避免在析构时导致双重释放。

示例:

cpp 复制代码
#include <iostream>
#include <string>
#include <thread>

class Student {
public:
    Student():_name(""),_age(0),_data(new int(0)) {
        std::cout << "无参构造" << std::endl;
    }
    Student(std::string name, int age,int number):_name(name),_age(age),_data(new int(number)) {
        std::cout << "有参构造" << std::endl;
    }
    Student(const Student &student):_name(student._name),_age(student._age),_data(new int(*student._data)) {//const可以加可以不加,主要是为了防止对参数进行修改,但是&是必须加
        std::cout << "拷贝构造" << std::endl;
    }
    Student(Student && student) noexcept:_name(std::move(student._name)),_age(student._age),_thread(std::move(student._thread)),_data(new int(*student._data)){
        std::cout << "移动构造" << std::endl;
    }
    ~Student() {
        std::cout << "析构函数" << std::endl;
        delete _data;
    }
    
    void GetTarget() {
        std::cout << "获取的target的值:";
        std::cout << _target << std::endl;
        std::cout << std::endl;
    }

    void ModifyTarget(int target) {
        std::cout << "修改target之后的值:";
        _target = target;
        std::cout << _target<<std::endl;
    }

    void GetName() {
        std::cout << _name << std::endl;
    }

    void GetAge() {
        std::cout << _age << std::endl;
    }

    void GetData() {
        std::cout << _data << std::endl;
    }
private:
    std::string _name;
    int _age;
    static int _target;//类内声明,类外初始化
    std::thread _thread;
    int* _data;
};
int Student::_target = 100;

int main() {
    Student student1("小明",16,10);
    Student student2(student1);
    student1.GetName();
    student1.GetAge();
    student1.GetData();
    student2.GetName();
    student2.GetAge();
    student2.GetData();
    return 0;
}

运行结果:

cpp 复制代码
#include <iostream>
#include <string>
#include <thread>

class Student {
public:
    Student():_name(""),_age(0),_data(new int(0)) {
        std::cout << "无参构造" << std::endl;
    }
    Student(std::string name, int age,int number):_name(name),_age(age),_data(new int(number)) {
        std::cout << "有参构造" << std::endl;
    }
    Student(const Student &student):_name(student._name),_age(student._age),_data(student._data) {//const可以加可以不加,主要是为了防止对参数进行修改,但是&是必须加
        std::cout << "拷贝构造" << std::endl;
    }
    Student(Student && student) noexcept:_name(std::move(student._name)),_age(student._age),_thread(std::move(student._thread)),_data(std::move(new int(*student._data))){
        std::cout << "移动构造" << std::endl;
    }
    ~Student() {
        std::cout << "析构函数" << std::endl;
        delete _data;
    }
    
    void GetTarget() {
        std::cout << "获取的target的值:";
        std::cout << _target << std::endl;
        std::cout << std::endl;
    }

    void ModifyTarget(int target) {
        std::cout << "修改target之后的值:";
        _target = target;
        std::cout << _target<<std::endl;
    }

    void GetName() {
        std::cout << _name << std::endl;
    }

    void GetAge() {
        std::cout << _age << std::endl;
    }

    void GetData() {
        std::cout << _data << std::endl;
    }
private:
    std::string _name;
    int _age;
    static int _target;//类内声明,类外初始化
    std::thread _thread;
    int* _data;
};
int Student::_target = 100;

int main() {
    Student student1("小明",16,10);
    Student student2(std::move(student1));
    student1.GetName();
    student1.GetAge();
    student1.GetData();
    student2.GetName();
    student2.GetAge();
    student2.GetData();
    return 0;
}

运行结果:

在初始列表中重新分配了一块内存空间,然后将指针指向的地址存放的值赋值给新开辟的内存空间。这样就能保证值是相同的,但是指向的内存空间地址不同,避免了双重释放问题。

也可以在函数体中进行一个浅拷贝或者深拷贝操作:

cpp 复制代码
#include <iostream>
#include <string>
#include <thread>

class Student {
public:
    Student():_name(""),_age(0),_data(new int(0)) {
        std::cout << "无参构造" << std::endl;
    }
    Student(std::string name, int age,int number):_name(name),_age(age),_data(new int(number)) {
        std::cout << "有参构造" << std::endl;
    }
    Student(const Student &student):_name(student._name),_age(student._age) {//const可以加可以不加,主要是为了防止对参数进行修改,但是&是必须加
        std::cout << "拷贝构造" << std::endl;
        //_data = student._data;//浅拷贝
        _data = new int();深拷贝,先进性申请一块内存
        *_data = *student._data;再进行解引用赋值
    }
    Student(Student && student) noexcept:_name(std::move(student._name)),_age(student._age),_thread(std::move(student._thread)){
        std::cout << "移动构造" << std::endl;
        //_data = std::move(student._data);
        _data = new int();
        *_data = *(student._data);
    }
    ~Student() {
        std::cout << "析构函数" << std::endl;
        delete _data;
    }
    
    void GetTarget() {
        std::cout << "获取的target的值:";
        std::cout << _target << std::endl;
        std::cout << std::endl;
    }

    void ModifyTarget(int target) {
        std::cout << "修改target之后的值:";
        _target = target;
        std::cout << _target<<std::endl;
    }

    void GetName() {
        std::cout << _name << std::endl;
    }

    void GetAge() {
        std::cout << _age << std::endl;
    }

    void GetData() {
        std::cout << _data << std::endl;
    }
private:
    std::string _name;
    int _age;
    static int _target;//类内声明,类外初始化
    std::thread _thread;
    int* _data;
};
int Student::_target = 100;

int main() {
    Student student1("小明",16,10);
    Student student2(student1);
    student1.GetName();
    student1.GetAge();
    student1.GetData();
    student2.GetName();
    student2.GetAge();
    student2.GetData();
    return 0;
}

总结:

特性 浅拷贝 深拷贝
拷贝内容 仅复制指针地址 复制指针指向的数据并分配新内存
内存所有权 共享内存 独立内存
风险 双重释放、悬空指针 无共享风险
性能 高效(仅复制指针) 较低(需分配新内存并复制数据)
适用场景 无动态资源的对象 含指针或动态资源的对象

浅拷贝和深拷贝根据实际的情况来进行使用。

相关推荐
l1t2 小时前
DeepSeek辅助编写的利用quick_xml把xml转为csv的rust程序
xml·开发语言·人工智能·rust·解析器·quick-xml
逐雨~2 小时前
9.11QT作业
开发语言
烟锁池塘柳02 小时前
【已解决,亲测有效】解决使用Python Matplotlib库绘制图表中出现中文乱码(中文显示为框)的问题的方法
开发语言·python·matplotlib
周小码2 小时前
llama-stack实战:Python构建Llama应用的可组合开发框架(8k星)
开发语言·python·llama
UrSpecial3 小时前
Linux线程
linux·开发语言·c++
郝学胜-神的一滴3 小时前
深入浅出 C++20:新特性与实践
开发语言·c++·程序人生·算法·c++20
汉克老师3 小时前
第十四届蓝桥杯青少组C++选拔赛[2023.1.15]第二部分编程题(2 、寻宝石)
c++·蓝桥杯·蓝桥杯c++·c++蓝桥杯·蓝桥杯选拔赛
大锦终3 小时前
【Linux】进程间通信
linux·运维·服务器·c++
闪电麦坤953 小时前
C/C++项目练习:命令行记账本
开发语言·c++