当前位置: 首页 > article >正文

浅谈C++深、浅拷贝

什么是浅拷贝?

C++ 类如果没有自定义拷贝构造函数或者自定义赋值操作符而使用拷贝构造或者赋值操作符,这时候就会涉及到浅拷贝的问题。

浅拷贝导致的问题

浅拷贝遇到需要动态申请的内存时,只会将新的变量指针指向被拷贝的内存,导致两个变量同时管理一个内存,进而产生一系列问题:

1. 双重释放问题

原对象和被拷贝出来的对象离开生存空间时,双双调用析构函数释放同一块内存,会导致double free 问题;
double free 示例

#include<iostream>
#include<string>
#include<cstring>

class Person
{
public:
    Person(const char *name)
    {
        if (name)
        {
            name_ = new char(strlen(name) + 1);
            strcpy(name_,name);
        }
    };

    //打印名字
    void PrintName(){if(name_){std::cout << "name:" << name_ << std::endl;}};
    ~Person()
    {  
        delete[] name_;//释放内存
        std::cout << "Person destructor." << std::endl;
    };

    //需要动态申请的内存变量,但是没有自定义copy 构造函数和赋值运算符
private:
    char *name_;
};
int main(int argc, char const *argv[])
{
    Person person1("alang");

    person1.PrintName();
    Person person2(person1);//调用的是编译器自动生成的copy构造
    person2.PrintName();
    
    std::cout << "接下来开始调用析构函数回收内存" << std::endl;
    return 0;
}

程序输出

name:alang
name:alang
接下来开始调用析构函数回收内存
Person destructor.
free(): double free detected in tcache 2
Aborted

2. 程序逻辑错误

当拷贝对象之一离开自己的生存空间时,会调用析构函数,会将同时管理的内存释放掉,导致其余对象去操作一个已经被释放的内存,同时也会存在双重释放问题
示例1:较上面只有main函数的更改

int main(int argc, char const *argv[])
{
    Person person1("alang");

    //大括号表示新的生存空间
    {
        Person person2(person1);//调用的是编译器自动生成的copy构造
        person2.PrintName();
    }

    person1.PrintName();
        
    std::cout << "接下来开始调用析构函数回收内存" << std::endl;
    return 0;
}

最直接的示例是函数使用值传递的方式传递对象时,调用的是copy构造函数来传参的,这样导致函数运行结束之后,浅拷贝的内存被释放掉
这里的另一解决方案是:最好不要以值传递的形式传递函数形参,而是引用传递。
示例2:较上面只有main函数的更改

void dosomething(Person p){}
int main(int argc, char const *argv[])
{
    Person person1("alang");
    dosomething(person1);
    person1.PrintName();
    
    std::cout << "接下来开始调用析构函数回收内存" << std::endl;
    return 0;
}

拒绝浅拷贝,使用深拷贝

在需要对动态内存操作时,不要使用编译器自动生成的拷贝构造函数和赋值运算符

这种情况下我们有两种方案:

  1. 直接禁用编译器自动生成的函数,直接不允许赋值操作;
  2. 自定义拷贝构造函数和赋值操作符。

这里给出两种方案的示例:

  • 禁用,不是很推荐,因为这样丧失了程序的灵活性
Person(const Person &person) = delete;
Person& operator=(const Person &person) = delete;
  • 完善自定义拷贝构造函数和自定义赋值操作符

#include<iostream>
#include<string>
#include<cstring>

class Person
{
public:

    Person():name_(nullptr){std::cout << "Person default constructor." << std::endl;};
    Person(const char *name)
    {
        if (name)
        {
            name_ = new char(strlen(name) + 1);
            strcpy(name_,name);
        }
    };

    //深拷贝构造函数
    Person(const Person &person)
    {
        if (person.name_)
        {
            this->name_ = new char(strlen(person.name_) + 1);
            strcpy(this->name_,person.name_);
        }
    };

    //深拷贝赋值运算符
    Person& operator=(const Person &person)
    {
        if (person.name_)
        {
            this->name_ = new char(strlen(person.name_) + 1);
            strcpy(this->name_,person.name_);
        }
        return *this;
    }

    //打印名字
    void PrintName(){if(name_){std::cout << "name:" << name_ << std::endl;}};
    ~Person()
    {  
        delete[] name_;//释放内存
        std::cout << "Person destructor." << std::endl;
    };

    //需要动态申请的内存变量,但是没有自定义copy 构造函数和赋值运算符
private:
    char *name_;
};

int main(int argc, char const *argv[])
{
    Person person1("alang");
    person1.PrintName();
    Person person2;
    person2 = person1;
    person2.PrintName();
    return 0;
}

Effective C++条款之二

如果不使用编译器自动生成的初始化函数,请明确拒绝它(使用delete)

如果class内动态配置有内存,请为此class声明一个copy constructor 和一个assignment运算符


http://www.kler.cn/a/376030.html

相关文章:

  • VScode通过ssh连接服务器(使用私钥时的易忽视点)
  • Android版本适配策略
  • Manus在虚拟现实仿真模拟中的应用案例分享
  • 配置BGP与IGP交互和路由自动聚合示例
  • ISME Comm | 西南大学时伟宇团队在功能基因水平揭示植被演替过程中磷限制对土壤微生物碳代谢潜力的抑制作用机制
  • Linux版更新流程
  • RPC和API关系
  • 2024三掌柜赠书活动第三十四期:破解深度学习
  • OpenMV的无人驾驶智能小车模拟系统
  • 使用 Q3D 计算并联和串联 RLCG 值
  • 【Python开发】大模型应用开发项目整理
  • 数据库物化视图的工作原理与Java实现
  • TPP-PEG-N3叠氮-聚乙二醇-四苯基吡嗪,功能话聚乙二醇,PEG分子量可定制
  • ms-swift+llamacpp+ollama微调部署MiniCPM-V教程
  • Yocto中MACHINE 和 DISTRO是输入,IMAGE 是他们组合的产物
  • Web3 与人工智能的跨界合作:重塑数字经济的新引擎
  • TikTok账号优化与批量管理:住宅IP与内容策略的全面指南
  • Python中的SQL数据库管理:SQLAlchemy教程
  • 安全成为大模型的核心;大模型安全的途径:大模型对齐
  • FPGA图像处理仿真:生成数据源的方法
  • Diving into the STM32 HAL-----HAL_GPIO
  • vscode 模板代码片段快捷配置
  • Unreal5从入门到精通之Sequencer关卡序列的用法
  • 什么是护网(HVV)需要什么技术?(内附护网超全资料包)
  • CSS3新增背景属性(四)
  • 1007:计算(a+b)×c的值