- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
【重学C++】03 | 手撸C++智能指针实战教程 。
大家好,今天是【重学C++】的第三讲,书接上回,第二讲《 02 脱离指针陷阱:深入浅出 C++ 智能指针 》介绍了C++智能指针的一些使用方法和基本原理。今天,我们自己动手,从0到1实现一下自己的 unique_ptr 和 shared_ptr .
智能指针的基本原理是基于RAII设计理论,自动回收内存资源,从根本上避免内存泄漏。在第一讲《 01 C++ 如何进行内存资源管理? 》介绍RAII的时候,就已经给了一个用于封装 int 类型指针,实现自动回收资源的代码实例:
class AutoIntPtr {
public:
AutoIntPtr(int* p = nullptr) : ptr(p) {}
~AutoIntPtr() { delete ptr; }
int& operator*() const { return *ptr; }
int* operator->() const { return ptr; }
private:
int* ptr;
};
我们从这个示例出发,一步步完善我们自己的智能指针.
这个类有个明显的问题:只能适用于int类指针。所以我们第一步要做的,就是把它改造成一个类模版,让这个类适用于任何类型的指针资源。 code show time 。
template <typename T>
class smart_ptr {
public:
explicit smart_ptr(T* ptr = nullptr): ptr_(ptr) {}
~smart_ptr() {
delete ptr_;
}
T& operator*() const { return *ptr_; }
T* operator->() const { return ptr_; }
private:
T* ptr_;
}
我给我们的智能指针类用了一个更抽象,更切合的类名: smart_ptr .
和 AutoIntPtr 相比,我们把 smart_ptr 设计成一个类模版,原来代码中的 int 改成模版参数 T ,非常简单。使用时也只要把 AutoIntPtr(new int(9)) 改成 smart_ptr<int>(new int(9)) 即可.
另外,有一点值得注意, smart_ptr 的构造函数使用了 explicit , explicit 关键字主要用于防止隐式的类型转换。代码中,如果原生指针隐式地转换为智能指针类型可能会导致一些潜在的问题。至于会有什么问题,你那聪明的小脑瓜看完下面的代码肯定能理解了:
void foo(smart_ptr<int> int_ptr) {
// ...
}
int main() {
int* raw_ptr = new int(42);
foo(raw_ptr); // 隐式转换为 smart_ptr<int>
std::cout << *raw_ptr << std::endl; // error: raw_ptr已经被回收了
// ...
}
假设我们没有为 smart_ptr 构造函数加上 explicit ,原生指针 raw_ptr 在传给 foo 函数后,会被隐形转换为 smart_ptr<int> , foo 函数调用结束后,栖构入参的 smart_ptr<int> 时会把 raw_ptr 给回收掉了,所以后续对 raw_ptr 的调用都会失败.
当前我们没有为 smart_ptr 自定义拷贝构造函数/移动构造函数,C++会为 smart_ptr 生成默认的拷贝/移动构造函数。默认的拷贝/移动构造函数逻辑很简单:把每个成员变量拷贝/移动到目标对象中.
按当前 smart_ptr 的实现,我们假设有以下代码:
smart_ptr<int> ptr1{new int(10)};
smart_ptr<int> ptr2 = ptr1;
这段代码在编译时不会出错,问题在运行时才会暴露出来:第二行将 ptr1 管理的指针复制给了 ptr2 ,所以会重复释放内存,导致程序奔溃.
为了避免同一块内存被重复释放。解决办法也很简单:
smart_ptr
占有它。 smart_ptr
析构时才会进行资源回收。 独占资源的所有权,并不是指禁用掉 smart_ptr 的拷贝/移动函数(当然这也是一种简单的避免重复释放内存的方法)。而是 smart_ptr 在拷贝时,代表资源对象的指针不是复制到另外一个 smart_ptr ,而是"移动"到新 smart_ptr 。移动后,原来的 smart_ptr.ptr_ == nullptr , 这样就完成了资源所有权的转移。 这也是C++ unique_ptr 的基本行为。我们在这里先把它命名为 unique_smart_ptr ,代码完整实现如下:
template <typename T>
class unique_smart_ptr {
public:
explicit unique_smart_ptr(T* ptr = nullptr): ptr_(ptr) {}
~unique_smart_ptr() {
delete ptr_;
}
// 1. 自定义移动构造函数
unique_smart_ptr(unique_smart_ptr&& other) {
// 1.1 把other.ptr_ 赋值到this->ptr_
ptr_ = other.ptr_;
// 1.2 把other.ptr_指为nullptr,other不再拥有资源指针
other.ptr_ = nullptr;
}
// 2. 自定义赋值行为
unique_smart_ptr& operator = (unique_smart_ptr rhs) {
// 2.1 交换rhs.ptr_和this->ptr_
std::swap(rhs.ptr_, this->ptr_);
return *this;
}
T& operator*() const { return *ptr_; }
T* operator->() const { return ptr_; }
private:
T* ptr_;
};
自定义移动构造函数 。在移动构造函数中,我们先是接管了 other.ptr_ 指向的资源对象,然后把 other 的 ptr_ 置为 nullptr ,这样在 other 析构时就不会错误释放资源内存.
同时,根据C++的规则,手动提供移动构造函数后,就会自动禁用拷贝构造函数。也就是我们能得到以下效果:
unique_smart_ptr<int> ptr1{new int(10)};
unique_smart_ptr<int> ptr2 = ptr1; // error
unique_smart_ptr<int> ptr3 = std::move(ptr1); // ok
unique_smart_ptr<int> ptr4{ptr1} // error
unique_smart_ptr<int> ptr5{std::move(ptr1)} // ok
自定义赋值函数 。在赋值函数中,我们使用 std::swap 交换了 rhs.ptr_ 和 this->ptr_ ,注意,这里不能简单的将 rhs.ptr_ 设置为 nullptr ,因为 this->ptr_ 可能有指向一个堆对象,该对象需要转给 rhs ,在赋值函数调用结束, rhs 析构时顺便释放掉。避免内存泄漏.
注意赋值函数的入参 rhs 的类型是 unique_smart_ptr 而不是 unique_smart_ptr&& ,这样创建 rhs 使用移动构造函数还是拷贝构造函数完全取决于 unique_smart_ptr 的定义。因为 unique_smart_ptr 当前只保留了移动构造函数,所以 rhs 是通过移动构造函数创建的.
学过第二讲的 shared_ptr , 我们知道它是利用计数引用的方式,实现了多个智能指针共享同一个对象。当最后一个持有对象的智能指针析构时,计数器减为0,这个时候才会回收资源对象.
我们先给出 shared_smart_ptr 的类定义 。
template <typename T>
class shared_smart_ptr {
public:
// 构造函数
explicit shared_smart_ptr(T* ptr = nullptr)
// 析构函数
~shared_smart_ptr()
// 移动构造函数
shared_smart_ptr(shared_smart_ptr&& other)
// 拷贝构造函数
shared_smart_ptr(const shared_smart_ptr& other)
// 赋值函数
shared_smart_ptr& operator = (shared_smart_ptr rhs)
// 返回当前引用次数
int use_count() const { return *count_; }
T& operator*() const { return *ptr_; }
T* operator->() const { return ptr_; }
private:
T* ptr_;
int* count_;
}
暂时不考虑多线程并发安全的问题,我们简单在堆上创建一个int类型的计数器 count_ 。下面详细展开各个函数的实现.
为了避免对 count_ 的重复删除,我们保持:只有当 ptr_ != nullptr 时,才对 count_ 进行赋值.
同样的,使用 explicit 避免隐式转换。除了赋值 ptr_ , 还需要在堆上创建一个计数器.
explicit shared_smart_ptr(T* ptr = nullptr){
ptr_ = ptr;
if (ptr_) {
count_ = new int(1);
}
}
在析构函数中,需要根据计数器的引用数判断是否需要回收对象.
~shared_smart_ptr() {
// ptr_为nullptr,不需要做任何处理
if (ptr_) {
return;
}
// 计数器减一
--(*count_);
// 计数器减为0,回收对象
if (*count_ == 0) {
delete ptr_;
delete count_;
return;
}
}
添加对 count_ 的处理 。
shared_smart_ptr(shared_smart_ptr&& other) {
ptr_ = other.ptr_;
count_ = other.count_;
other.ptr_ = nullptr;
other.count_ = nullptr;
}
添加交换 count_ 。
shared_smart_ptr& operator = (shared_smart_ptr rhs) {
std::swap(rhs.ptr_, this->ptr_);
std::swap(rhs.count_, this->count_);
return *this;
}
对于 shared_smart_ptr ,我们需要手动支持拷贝构造函数。主要处理逻辑是赋值 ptr_ 和增加计数器的引用数.
shared_smart_ptr(const shared_smart_ptr& other) {
ptr_ = other.ptr_;
count_ = other.count_;
if (ptr_) {
(*count_)++;
}
}
这样,我们就实现了一个自己的共享智能指针,贴一下完整代码 。
template <typename T>
class shared_smart_ptr {
public:
explicit shared_smart_ptr(T* ptr = nullptr){
ptr_ = ptr;
if (ptr_) {
count_ = new int(1);
}
}
~shared_smart_ptr() {
// ptr_为nullptr,不需要做任何处理
if (ptr_ == nullptr) {
return;
}
// 计数器减一
--(*count_);
// 计数器减为0,回收对象
if (*count_ == 0) {
delete ptr_;
delete count_;
}
}
shared_smart_ptr(shared_smart_ptr&& other) {
ptr_ = other.ptr_;
count_ = other.count_;
other.ptr_ = nullptr;
other.count_ = nullptr;
}
shared_smart_ptr(const shared_smart_ptr& other) {
ptr_ = other.ptr_;
count_ = other.count_;
if (ptr_) {
(*count_)++;
}
}
shared_smart_ptr& operator = (shared_smart_ptr rhs) {
std::swap(rhs.ptr_, this->ptr_);
std::swap(rhs.count_, this->count_);
return *this;
}
int use_count() const { return *count_; };
T& operator*() const { return *ptr_; };
T* operator->() const { return ptr_; };
private:
T* ptr_;
int* count_;
};
使用下面代码进行验证:
int main(int argc, const char** argv) {
shared_smart_ptr<int> ptr1(new int(1));
std::cout << "[初始化ptr1] use count of ptr1: " << ptr1.use_count() << std::endl;
{
// 赋值使用拷贝构造函数
shared_smart_ptr<int> ptr2 = ptr1;
std::cout << "[使用拷贝构造函数将ptr1赋值给ptr2] use count of ptr1: " << ptr1.use_count() << std::endl;
// 赋值使用移动构造函数
shared_smart_ptr<int> ptr3 = std::move(ptr2);
std::cout << "[使用移动构造函数将ptr2赋值给ptr3] use count of ptr1: " << ptr1.use_count() << std::endl;
}
std::cout << "[ptr2和ptr3析构后] use count of ptr1: " << ptr1.use_count() << std::endl;
}
运行结果:
[初始化ptr1] use count of ptr1: 1
[使用拷贝构造函数将ptr1赋值给ptr2] use count of ptr1: 2
[使用移动构造函数将ptr2赋值给ptr3] use count of ptr1: 2
[ptr2和ptr3析构后] use count of ptr1: 1
这一讲我们从 AutoIntPtr 出发,先是将类进行模版化,使其能够管理任何类型的指针对象,并给该类起了一个更抽象、更贴切的名称—— smart_ptr .
接着围绕着「如何正确释放资源对象指针」的问题,一步步手撸了两个智能指针 —— unique_smart_ptr 和 shared_smart_ptr 。相信大家现在对智能指针有一个较为深入的理解了.
最后此篇关于【重学C++】03|手撸C++智能指针实战教程的文章就讲到这里了,如果你想了解更多关于【重学C++】03|手撸C++智能指针实战教程的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
#include using namespace std; class C{ private: int value; public: C(){ value = 0;
这个问题已经有答案了: What is the difference between char a[] = ?string?; and char *p = ?string?;? (8 个回答) 已关闭
关闭。此题需要details or clarity 。目前不接受答案。 想要改进这个问题吗?通过 editing this post 添加详细信息并澄清问题. 已关闭 7 年前。 此帖子已于 8 个月
除了调试之外,是否有任何针对 c、c++ 或 c# 的测试工具,其工作原理类似于将独立函数复制粘贴到某个文本框,然后在其他文本框中输入参数? 最佳答案 也许您会考虑单元测试。我推荐你谷歌测试和谷歌模拟
我想在第二台显示器中移动一个窗口 (HWND)。问题是我尝试了很多方法,例如将分辨率加倍或输入负值,但它永远无法将窗口放在我的第二台显示器上。 关于如何在 C/C++/c# 中执行此操作的任何线索 最
我正在寻找 C/C++/C## 中不同类型 DES 的现有实现。我的运行平台是Windows XP/Vista/7。 我正在尝试编写一个 C# 程序,它将使用 DES 算法进行加密和解密。我需要一些实
很难说出这里要问什么。这个问题模棱两可、含糊不清、不完整、过于宽泛或夸夸其谈,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开,visit the help center . 关闭 1
有没有办法强制将另一个 窗口置于顶部? 不是应用程序的窗口,而是另一个已经在系统上运行的窗口。 (Windows, C/C++/C#) 最佳答案 SetWindowPos(that_window_ha
假设您可以在 C/C++ 或 Csharp 之间做出选择,并且您打算在 Windows 和 Linux 服务器上运行同一服务器的多个实例,那么构建套接字服务器应用程序的最明智选择是什么? 最佳答案 如
你们能告诉我它们之间的区别吗? 顺便问一下,有什么叫C++库或C库的吗? 最佳答案 C++ 标准库 和 C 标准库 是 C++ 和 C 标准定义的库,提供给 C++ 和 C 程序使用。那是那些词的共同
下面的测试代码,我将输出信息放在注释中。我使用的是 gcc 4.8.5 和 Centos 7.2。 #include #include class C { public:
很难说出这里问的是什么。这个问题是含糊的、模糊的、不完整的、过于宽泛的或修辞性的,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开它,visit the help center 。 已关
我的客户将使用名为 annoucement 的结构/类与客户通信。我想我会用 C++ 编写服务器。会有很多不同的类继承annoucement。我的问题是通过网络将这些类发送给客户端 我想也许我应该使用
我在 C# 中有以下函数: public Matrix ConcatDescriptors(IList> descriptors) { int cols = descriptors[0].Co
我有一个项目要编写一个函数来对某些数据执行某些操作。我可以用 C/C++ 编写代码,但我不想与雇主共享该函数的代码。相反,我只想让他有权在他自己的代码中调用该函数。是否可以?我想到了这两种方法 - 在
我使用的是编写糟糕的第 3 方 (C/C++) Api。我从托管代码(C++/CLI)中使用它。有时会出现“访问冲突错误”。这使整个应用程序崩溃。我知道我无法处理这些错误[如果指针访问非法内存位置等,
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 我们不允许提问寻求书籍、工具、软件库等的推荐。您可以编辑问题,以便用事实和引用来回答。 关闭 7 年前。
已关闭。此问题不符合Stack Overflow guidelines 。目前不接受答案。 要求我们推荐或查找工具、库或最喜欢的场外资源的问题对于 Stack Overflow 来说是偏离主题的,因为
我有一些 C 代码,将使用 P/Invoke 从 C# 调用。我正在尝试为这个 C 函数定义一个 C# 等效项。 SomeData* DoSomething(); struct SomeData {
这个问题已经有答案了: Why are these constructs using pre and post-increment undefined behavior? (14 个回答) 已关闭 6
我是一名优秀的程序员,十分优秀!