String浅拷贝、引用计数和深拷贝

本文详细介绍了浅拷贝、引用计数及深拷贝三种对象复制方式,通过C++代码实例展示了每种方式的特点与应用场景。浅拷贝存在内存多次释放的问题,引用计数能有效解决此问题,而深拷贝则确保每个对象拥有独立内存,但可能消耗更多资源。

1. 浅拷贝

浅拷贝,是指原对象与拷贝对象共用一份实体,仅仅是对象名字不同而已(类似引用,即对原对象起别名),其中任何一个对象改变都会导致其他的对象也跟着它变。

#include <stdio.h>
#include <string.h>
#include <unistd.h>

//g++ -o StringSimple StringSimple.cpp

class String
{
public:
	String(const char* pStr = "")//构造函数
		:_pStr(new char[strlen(pStr)+1])
	{
		if(0 == *pStr)//字符串为空
		{
			*_pStr = '\0';
		}
		else//字符串不为空
		{
			strcpy(_pStr,pStr);
		}
	}
	String(const String& s)//拷贝构造函数
	{
		_pStr = s._pStr;
	}
	String& operator=(String& s)//赋值运算符重载
	{
		if(_pStr != s._pStr)//判断是不是自己给自己赋值
		{
			_pStr = s._pStr;
		}
		return *this;
	}
	~String()//析构函数
	{
		if(NULL == _pStr)
		{
			return;
		}
		else
		{
			delete []_pStr;
			_pStr = NULL;
		}
	}
  char* getStrPoint() {
      return _pStr;
    }
private:
	char* _pStr;

};


int main()
{
    String str1("abcdeaaaaa");
    String str2(str1);
    String str3;
    str3 = str1;

    //三个地址是一样的
    printf(" str1: %lx\n", (long)str1.getStrPoint());
    printf(" str2: %lx\n", (long)str2.getStrPoint());
    printf(" str3: %lx\n", (long)str3.getStrPoint());


    sleep(5);
    return 0;
}
图一

从上面的日志可以看出:内存被多次释放 。可以考虑通过引用计数的方式避免多次释放,只有计数器的值为0时,才可能真正释放内存。

 

2. 引用计数

通过引入计数器cnt,来解决浅拷贝多次释放同一块内存的问题。

#include <stdio.h>
#include <string.h>



class String
{

 public:
  //构造函数
  String(const char* pStr = "")
    :_pStr(new char[strlen(pStr) + 1])
    ,_cnt(new int(0))
    {
      if (0 == *pStr) {
        *_pStr = '\0';
      }
      else //字符串 不为空
      {
        strcpy(_pStr, pStr);
      }
      (*_cnt)++;
      printf("==构造函数==\n");
    }

    String(const String& s) //拷贝构造函数
    {
      _pStr = s._pStr;
      _cnt = s._cnt;
      (*_cnt)++;
      printf("==拷贝构造函数==\n");
    }

    ~String() //析构函数
    {
      if(NULL == _pStr)
      {
        return ;
      }
      else
      {
        if ((--(*_cnt)) == 0)
        {
          delete _cnt;
          delete []_pStr;
          _cnt = NULL;
          _pStr = NULL;
        }
      }
      printf("==析构函数==\n");
    }

    String& operator=(const String& s) //赋值运算符
    {
      //先释放旧数据
      if (NULL != _pStr)
      {
        if ((--(*_cnt)) == 0)
        {
          delete _cnt;
          delete []_pStr;
          _cnt = NULL;
          _pStr = NULL;
          printf("==赋值函数,释放旧内存==\n");
        }
      }
      _pStr = s._pStr;
      _cnt = s._cnt;
      (*_cnt)++;

      printf("==赋值函数==\n");
      return *this;
    }

 private:
  char* _pStr;
  int* _cnt;
};

int main()
{
    String str1("abcdeabbbb");
    String str2(str1);
    String str3;
    str3 = str1;

    return 0;
}

 

3. 深拷贝

每次拷贝都是重新申请一块内存,再把内容拷贝过去,当数据比较大时,这种方式是比较耗内存的,而且也会影响性能。

#include <stdio.h>
#include <string.h>
#include <unistd.h>


class String
{
public:
	String(const char* pStr = "")//构造函数
		:_pStr(new char[strlen(pStr)+1])
	{
		if(0 == *pStr)//字符串为空
		{
			*_pStr = '\0';
		}
		else//字符串不为空
		{
			strcpy(_pStr,pStr);
		}
	}
	String(const String& s)//拷贝构造函数
    :_pStr(new char[strlen(s._pStr)+1])
	{
    strcpy(_pStr,s._pStr);
	}

	String& operator=(const String& s)//赋值运算符重载
	{
		if(_pStr != s._pStr)//判断是不是自己给自己赋值
		{
      if (NULL != _pStr)
      {
        delete []_pStr;//释放当前对象
      }
      char* temp  = new char[strlen(s._pStr)+1];//先开辟一段新空间
			strcpy(temp,s._pStr);//将原对象的值赋给新空间
			_pStr = temp;//将当前对象指向新开辟的空间
		}
		return *this;
	}
	~String()//析构函数
	{
		if(NULL == _pStr)
		{
			return;
		}
		else
		{
			delete []_pStr;
			_pStr = NULL;
		}
	}
  char* getStrPoint() {
      return _pStr;
    }
private:
	char* _pStr;

};


int main()
{
    String str1("abcdecccc");
    String str2(str1);
    String str3;
    str3 = str1;

    //三个地址是不一样的
    printf(" str1: %lx\n", (long)str1.getStrPoint());
    printf(" str2: %lx\n", (long)str2.getStrPoint());
    printf(" str3: %lx\n", (long)str3.getStrPoint());


    return 0;
}

从运行的结果看,三个对象的地址是不一样的。

总结:

(1)浅拷贝,是指原对象与拷贝对象共用一份实体,存在内存被多次释放的问题;

(2)引用计数,引用计数的方法可以解决浅拷贝多次释放内存的问题;

(3)深拷贝,每个对象都申请自己独立的内存,各个对象间互不影响,当数据比较大时,比较耗内存,也影响性能。

 

水平有限,难免存在疏漏,欢迎指出错误,谢谢!

<think>我们正在讨论C++中的深拷贝浅拷贝。根据之前的内容,深拷贝浅拷贝主要与类中是否包含指针成员(尤其是动态分配的资源)有关。下面我将详细解释C++中深拷贝浅拷贝的各种情况,并按照要求进行格式化引用。 ### C++中深拷贝浅拷贝的具体情况 #### 1. **基本数据类型:总是深拷贝** 当对象仅包含基本数据类型(如`int`, `double`, `char`等)时,赋值操作拷贝构造函数执行的是深拷贝。每个对象都有自己独立的内存空间。 **示例**: ```cpp int a = 10; int b = a; // 深拷贝:b拥有自己的内存,值为10 a = 20; // b的值仍为10 ``` 内存模型:$addr_a \neq addr_b$,$value_b \leftarrow value_a$。 这种情况下,由于没有指针,拷贝是安全的,不会引起资源冲突[^1][^3]。 #### 2. **不含指针的类对象:默认浅拷贝(按值复制,等同于深拷贝)** 当类中没有指针成员且未管理任何动态资源时,编译器生成的默认拷贝构造函数赋值运算符执行成员级别的复制(member-wise copy)。由于所有成员都是基本类型或具有安全拷贝行为的对象,这种复制实际上是深拷贝。 **示例**: ```cpp class Point { public: int x, y; Point(int x_, int y_) : x(x_), y(y_) {} }; Point p1(1, 2); Point p2 = p1; // 默认浅拷贝(按成员复制) // 但由于成员是基本类型,行为等同于深拷贝 ``` 修改`p1.x`不会影响`p2.x`(独立内存)[^3]。 #### 3. **含指针但未自定义拷贝操作:默认浅拷贝(危险)** 当类中含有指针成员且指向动态分配的资源时,如果未自定义拷贝构造函数赋值运算符,编译器会生成默认的浅拷贝版本。这会导致多个对象共享同一块动态内存,从而引发问题。 **风险案例**: ```cpp class ShallowArray { int* data; int size; public: ShallowArray(int sz) : size(sz), data(new int[sz]) {} ~ShallowArray() { delete[] data; } // 未定义拷贝构造赋值运算符 → 使用默认浅拷贝 }; int main() { ShallowArray arr1(10); ShallowArray arr2 = arr1; // 浅拷贝:arr2.data == arr1.data } // 析构时:arr1arr2都会尝试释放同一内存 → 崩溃! 💥 ``` 此处浅拷贝导致**双重释放**(double free)[^2][^3]。 #### 4. **自定义深拷贝:显式管理资源** 当类管理动态资源(如堆内存、文件句柄等)时,必须自定义拷贝构造函数赋值运算符以实现深拷贝,确保每个对象拥有独立的资源副本。 **深拷贝实现示例**: ```cpp class DeepArray { int* data; int size; public: DeepArray(int sz) : size(sz), data(new int[sz]) {} // 深拷贝构造函数 DeepArray(const DeepArray& other) : size(other.size), data(new int[other.size]) { for (int i = 0; i < size; ++i) data[i] = other.data[i]; } // 深拷贝赋值运算符 DeepArray& operator=(const DeepArray& other) { if (this != &other) { // 1. 防止自赋值 delete[] data; // 2. 释放旧资源 size = other.size; data = new int[size]; // 3. 分配新内存 for (int i = 0; i < size; ++i) data[i] = other.data[i]; // 4. 复制数据 } return *this; } ~DeepArray() { delete[] data; } }; ``` 深拷贝的核心步骤: - **分配新内存**:在拷贝构造函数赋值运算符中为当前对象分配独立内存。 - **复制数据**:将原对象的数据复制到新内存中。 - **释放旧资源**:在赋值运算符中先释放当前对象原有的资源(防止内存泄漏)[^2][^3]。 #### 5. **标准库容器:自动深拷贝** C++标准库容器(如`std::vector`, `std::string`, `std::map`等)内部已实现深拷贝。当复制这些容器时,它们会自动复制所有元素到新内存中。 **示例**: ```cpp std::vector<int> v1 = {1, 2, 3}; std::vector<int> v2 = v1; // 深拷贝:v2拥有独立内存 v1[0] = 99; // v2[0]仍为1 ``` 因此,在自定义类中使用标准库容器管理资源可避免手动实现深拷贝[^1]。 #### 6. **智能指针:所有权决定拷贝行为** 使用智能指针管理资源时,拷贝行为取决于指针类型: - **`std::unique_ptr`**:禁止拷贝(移动语义转移所有权)。 - **`std::shared_ptr`**:允许浅拷贝引用计数共享所有权)。 **`shared_ptr`浅拷贝示例**: ```cpp class SharedData { std::shared_ptr<int> data; public: SharedData(int val) : data(std::make_shared<int>(val)) {} // 使用默认拷贝构造:多个对象共享同一数据 }; SharedData d1(42); SharedData d2 = d1; // 浅拷贝:d1.datad2.data指向同一地址 *d1.data = 100; // *d2.data也变为100 ``` `shared_ptr`的浅拷贝是安全的(引用计数避免双重释放),但需注意数据共享可能带来的逻辑问题[^1]。 ### 总结表格 | **场景** | **拷贝类型** | **是否需要自定义** | **风险** | |------------------------------------|--------------|-------------------|------------------------| | 基本数据类型 | 深拷贝 | 否 | 无 | | 不含指针的类 | 浅拷贝(安全)| 否 | 无 | | 含指针的类(未自定义) | 浅拷贝 | 是(必须) | 双重释放/数据竞争 | | 含指针的类(自定义深拷贝) | 深拷贝 | 是 | 无(正确实现时) | | 标准库容器(`vector`, `string`等) | 深拷贝 | 否 | 无 | | `std::shared_ptr`管理的类 | 浅拷贝 | 否 | 数据共享(逻辑风险) | > **关键结论**: > **深拷贝浅拷贝的根本区别在于是否复制指针指向的实际资源**。当类管理动态资源时,必须显式实现深拷贝以避免未定义行为[^1][^2][^3]。 §§相关问题§§ 1. 如何通过`std::unique_ptr`实现安全的资源管理? 2. 在继承体系中如何正确实现深拷贝? 3. 移动构造函数如何避免不必要的深拷贝? 4. 拷贝交换(Copy-and-Swap)惯用法如何简化赋值运算符的实现? 5. 使用`std::shared_ptr`时如何实现深拷贝? </think>### C++中深拷贝浅拷贝的具体情况分析 在C++中,深拷贝浅拷贝的行为主要取决于**对象的成员类型****是否自定义拷贝操作**。以下是各种情况的详细分析: #### 1. **基本数据类型:总是深拷贝** ```cpp int a = 10; int b = a; // 深拷贝:b拥有独立内存 a = 20; // b仍为10(不受影响) ``` - **特点**:所有基本类型(`int`, `double`, `char`等)的拷贝都是深拷贝 - **内存模型**:$ \text{addr}_a \neq \text{addr}_b $,$ \text{value}_b = \text{value}_a $[^3] #### 2. **不含指针的类对象:默认浅拷贝(安全)** ```cpp class Point { public: int x, y; Point(int x_, int y_) : x(x_), y(y_) {} }; Point p1(1, 2); Point p2 = p1; // 默认浅拷贝(按成员复制) p1.x = 10; // p2.x仍为1(值独立) ``` - **原理**:编译器生成默认拷贝构造函数,执行成员级复制 - **安全性**:成员无指针时安全,行为等同于深拷贝[^3] #### 3. **含指针但未自定义拷贝操作:危险浅拷贝** ```cpp class ShallowArray { int* data; int size; public: ShallowArray(int sz) : size(sz), data(new int[sz]) {} ~ShallowArray() { delete[] data; } // 未定义拷贝构造/赋值运算符 → 使用默认浅拷贝 }; ShallowArray arr1(5); ShallowArray arr2 = arr1; // 浅拷贝:arr2.data == arr1.data ``` - **风险**: - 双重释放:析构时两次释放同一内存(崩溃) - 数据竞争:修改`arr1`会影响`arr2`[^2] - **内存模型**:$ \text{addr}_{\text{arr1.data}} = \text{addr}_{\text{arr2.data}} $ #### 4. **自定义深拷贝:显式管理资源** ```cpp class DeepArray { int* data; int size; public: // 深拷贝构造函数 DeepArray(const DeepArray& other) : size(other.size), data(new int[other.size]) { std::copy(other.data, other.data + size, data); } // 深拷贝赋值运算符 DeepArray& operator=(const DeepArray& other) { if (this != &other) { // 避免自赋值 delete[] data; // 释放旧资源 size = other.size; data = new int[size]; // 分配新内存 std::copy(other.data, other.data + size, data); } return *this; } }; ``` - **关键步骤**: 1. 检查自赋值(`if (this != &other)`) 2. 释放旧资源(`delete[] data`) 3. 分配新内存(`new int[size]`) 4. 复制数据(`std::copy`)[^2][^3] #### 5. **标准库容器:自动深拷贝** ```cpp std::vector<int> v1 = {1, 2, 3}; std::vector<int> v2 = v1; // 深拷贝:v2拥有独立内存 v1[0] = 99; // v2[0]仍为1 ``` - **原理**:`std::vector`, `std::string`等容器内部已实现深拷贝 - **优势**:避免手动管理内存[^1] #### 6. **智能指针:共享所有权的浅拷贝** ```cpp class SharedData { std::shared_ptr<int> data; // 共享指针 public: SharedData(int val) : data(std::make_shared<int>(val)) {} // 使用默认拷贝构造函数(浅拷贝但安全) }; SharedData d1(42); SharedData d2 = d1; // 浅拷贝但安全(引用计数管理) ``` - **特点**: - 拷贝时共享所有权(引用计数+1) - 析构时自动释放资源(引用计数为0时)[^1] ### 总结表格 | **场景** | 拷贝类型 | 是否需要自定义 | 风险 | 示例 | |------------------------------|----------|----------------|--------------------|--------------------------| | 基本数据类型 | 深拷贝 | 否 | 无 | `int a = b;` | | 不含指针的类对象 | 浅拷贝 | 否 | 无 | `Point p2 = p1;` | | 含指针类(未自定义) | 浅拷贝 | 是 | 双重释放/数据竞争 | `ShallowArray arr2=arr1;`| | 含指针类(自定义深拷贝) | 深拷贝 | 是 | 无(正确实现时) | `DeepArray arr2=arr1;` | | 标准库容器(vector,string等)| 深拷贝 | 否 | 无 | `vector<int> v2=v1;` | | `std::shared_ptr`管理的类 | 浅拷贝 | 否 | 数据共享(逻辑风险)| `SharedData d2=d1;` | > **核心原则**: > **当类包含指针成员并管理动态资源时,必须自定义深拷贝构造函数深拷贝赋值运算符**,否则会导致未定义行为[^1][^2][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值