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

总结:

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

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

相关推荐
努力学习的小廉33 分钟前
我爱学算法之—— 分治-归并
c++·算法·1024程序员节
九皇叔叔36 分钟前
Java循环结构全解析:从基础用法到性能优化
java·开发语言·性能优化
仰泳的熊猫43 分钟前
LeetCode:200. 岛屿数量
数据结构·c++·算法·leetcode
sulikey44 分钟前
Qt 入门简洁笔记:从框架概念到开发环境搭建
开发语言·前端·c++·qt·前端框架·visual studio·qt框架
zzzsde1 小时前
【C++】stack和queue:优先级队列的使用及底层原理
开发语言·c++
让我们一起加油好吗1 小时前
【数论】费马小定理
c++·算法·数论·1024程序员节·费马小定理·逆元
是苏浙1 小时前
零基础入门C语言之操作符详解2
c语言·开发语言
m0_748233642 小时前
单调队列【C/C++】
c语言·c++·算法·1024程序员节
总有刁民想爱朕ha2 小时前
银河麒麟v10批量部署Python Flask项目小白教程
开发语言·python·flask·银河麒麟v10
yi碗汤园2 小时前
【一文了解】八大排序-插入排序、希尔排序
开发语言·算法·unity·c#·1024程序员节