智能指针雏形
- **Day7-1 智能指针雏形:独占语义与共享语义**
- **1. 独占语义与共享语义**
- **1.1 Circle 类:示例类**
- **2. 拷贝构造:独占语义(Unique Ownership)**
- **2.1 代码解析**
- **3. 拷贝构造:共享语义(Shared Ownership)**
- **3.1 代码解析**
- **4. 智能指针 std::unique_ptr 和 std::shared_ptr**
- **4.1 代码解析**
- **5. 移动语义(Move Semantics)**
- **5.1 代码解析**
- **总结**
Day7-1 智能指针雏形:独占语义与共享语义
1. 独占语义与共享语义
在 C++ 中,拷贝构造的语义可以分为独占语义和共享语义。
- 独占语义(Unique Ownership):每个对象拥有一块独立的内存,拷贝时进行深拷贝。
- 共享语义(Shared Ownership):多个对象共享同一块内存,拷贝时进行浅拷贝,通常需要引用计数来管理资源。
C++ 标准库提供了 std::unique_ptr
和 std::shared_ptr
来实现这两种语义。
1.1 Circle 类:示例类
Circle
类是一个普通的对象类,包含构造函数、析构函数、成员变量和方法。
// Circle.h
#pragma once
#include <iostream>
#include <cmath>
using namespace std;
//关键字
//const
const int global_a = 10;
//static
static int s_b = 10;
//extern
extern int SIZE = 10;
//三者的用途:const 修饰常量,static 修饰全局变量,extern 修饰外部变量
class Circle
{
public:
Circle(double r = 0)
: _r(r)
{
cout << "Circle(double r = 0)" << endl;
}
Circle(double r, char* name)
: _r(0), _name(new char[strlen(name) + 1])
{
strcpy_s(_name, strlen(name) + 1, name);
cout << "Circle(double r, char* name)" << endl;
}
~Circle()
{
cout << "~Circle()" << endl;
delete[] _name;
}
double getRadius() const { return _r; }
void setRadius(double r) { _r = r; }
double getArea() const { return M_PI * _r * _r; }
private:
double _r;
char* _name;
};
2. 拷贝构造:独占语义(Unique Ownership)
UniqueClass
实现了独占语义,即对象的拷贝会创建新的独立对象,保证每个对象都拥有独立的内存。
// ShareClass.h
class UniqueClass
{
int times = 0;
public:
UniqueClass(int a)
: _data(new int(a))
{
}
~UniqueClass()
{
delete _data;
_data = nullptr;
}
// 深拷贝构造函数
//UniqueClass(const UniqueClass& rhs)
// :_data(new int(*rhs.data))
//{
// cout << "Call desctructor";
// times++;
// cout << times << endl;
//}
//在这段代码中,rhs 不是指针,而是一个 UniqueClass 类型的引用。
// *rhs._data 是对 rhs 对象的 _data 成员指针进行解引用。
UniqueClass(const UniqueClass& rhs)
: _data(new int(*rhs._data)) // 修复了错误的冒号和成员变量名称
{
cout << "Call constructor";
times++;
cout << times << endl;
}
/*
1. UniqueClass(const UniqueClass & rhs) 是 UniqueClass 的拷贝构造函数。它接受一个 UniqueClass 类型的常量引用 rhs 作为参数。
2. : _data(new int(*rhs._data)) 是成员初始化列表的一部分。它的作用是初始化 _data 成员变量。
3. rhs._data 是对 rhs 对象的 _data 成员指针的访问。
4. * rhs._data 是对 rhs._data 指针的解引用,获取指针指向的整数值。
5. new int(*rhs._data) 创建了一个新的 int 对象,并将 * rhs._data 的值复制到这个新的 int 对象中。然后将新创建的 int 对象的指针赋值给 _data 成员变量。
这样做的目的是在拷贝构造函数中为新对象分配一个新的内存空间,并将原对象的 _data 成员
指针指向的值复制到新对象的 _data 成员指针指向的内存中。这确保了每个 UniqueClass 对象
都有自己独立的 _data 内存空间,避免了多个对象共享同一块内存,从而防止潜在的内存管理问题。
*/
/*传入的参数可以修改,修改后不可以再使用,最好当成右值使用*/
//移动构造函数的写法(C++11 移动构造函数,接受右值)
// 拷贝指针,并断掉源对象对内存的引用(置空)
// 如果传入参数本身是右值,没有任何副作用,因为调用结束,它就不在了
// 但传入的如果是个左值转换来的右值,那要注意,充当了右值,右值的语义就是临时的,
// 转瞬即时的,所以这个左值也应看成已逝的事物,不可以再使用
UniqueClass(UniqueClass&& rhs)
:_data(rhs._data)
{
rhs._data = nullptr;
}
// C++11 移动赋值运算符,接受右值,和移动构造同理
UniqueClass& operator=(UniqueClass&& rhs)
{
_data = rhs._data;
rhs._data = nullptr;
}
private:
int* _data;
};
2.1 代码解析
- 拷贝构造函数:创建新对象时,为
_data
申请新的内存,保证对象的独占性。 - 移动构造函数:从右值拷贝指针,并将原对象的指针置空,避免重复释放内存。
- 移动赋值运算符:清理当前对象的内存,然后从右值拷贝指针,并清空右值的指针。
3. 拷贝构造:共享语义(Shared Ownership)
SharedClass
采用引用计数管理共享的内存。
class SharedClass
{
public:
static int count; // 静态引用计数
int* _data;
SharedClass(int a) : _data(new int(a)) {}
// 拷贝构造,拷贝指针,增加引用计数
SharedClass(const SharedClass& r)
: _data(r._data)
{
count++;
}
// 拷贝赋值,拷贝指针,增加引用计数
SharedClass& operator=(const SharedClass& r)
{
if (this != &r)
{
_data = r._data;
count++;
}
return *this;
}
// 析构时注意减少引用计数,归0时释放堆内存
~SharedClass()
{
--count;
if (count == 0)
{
delete _data;
}
}
};
int SharedClass::count = 1;
3.1 代码解析
- 引用计数
count
:确保多个对象共享同一块内存。 - 拷贝构造函数:增加引用计数。
- 析构函数:当
count == 0
时,释放_data
。
4. 智能指针 std::unique_ptr 和 std::shared_ptr
C++ 提供 std::unique_ptr
和 std::shared_ptr
,分别对应独占语义和共享语义。
void testSmartPointer()
{
std::unique_ptr<int> unique1 = std::make_unique<int>(10);
// std::unique_ptr<int> unique2 = unique1; // 错误:无法复制 unique_ptr
std::shared_ptr<int> share1 = std::make_shared<int>(10);
std::shared_ptr<int> share2 = share1;
cout << "share2 use_count() = " << share2.use_count() << endl;
}
4.1 代码解析
std::unique_ptr
禁止拷贝,确保对象独占内存。std::shared_ptr
可以拷贝,使用引用计数管理内存。
5. 移动语义(Move Semantics)
void testMoveSemantic()
{
UniqueClass u1(10);
cout << "u1.data = " << u1._data << endl;
cout << "*(u1)._data = " << * (u1)._data << endl;
//UniqueClass u2 = (UniqueClass&&)u1;
//等价于
UniqueClass u2 = std::move(u1);// move将uc1这个左值转为右值,即(UniqueClass&&)uc1;
cout << "u2.data = " << u2._data << endl;
cout << "*(u2)._data = " << *(u2)._data << endl;
if (u1._data = nullptr)
{
cout << "u1 is gone " << endl;
}
}
5.1 代码解析
std::move(u1)
将u1
变为右值,调用移动构造函数。u1._data = nullptr
,原对象u1
失效。
总结
方式 | 语义 | 内存管理 | 适用场景 |
---|---|---|---|
std::unique_ptr | 独占 | 不能拷贝 | 资源独占,如文件句柄 |
std::shared_ptr | 共享 | 计数管理 | 共享资源,如缓存 |
拷贝构造 | 独占/共享 | 深拷贝/浅拷贝 | 具体需求 |
移动构造 | 独占 | 资源转移 | 避免临时对象开销 |
通过这些方法,我们可以更高效地管理 C++ 中的资源分配和释放,避免内存泄漏和重复释放。
深拷贝(独占)
- 拷贝的是 值,拷贝后 新对象与源对象完全独立。
- 析构一方 不影响 另一方。
浅拷贝(共享)
- 拷贝的是 指针/引用,拷贝后 新对象与源对象共享同一份内存。
- 析构一方后 另一方会受到影响。
为了防止 重复 delete,需要引入 引用计数,只有当 没有任何对象引用这块堆内存时,才释放它。
std::shared_ptr
实现的思路:
std::shared_ptr
通过 内部的引用计数 记录有多少个 shared_ptr
共享同一块内存,只有当 引用计数归零时,才释放资源。
#include <iostream>
#include <memory>
using namespace std;
void sharedPtrDemo()
{
shared_ptr<int> p1 = make_shared<int>(10);
{
shared_ptr<int> p2 = p1;
cout << "引用计数: " << p1.use_count() << endl;
} // p2 作用域结束,引用计数减少
cout << "引用计数: " << p1.use_count() << endl;
}