【c++工程实践】智能指针

一、智能指针简介

smart pointer is an abstract data type that simulates a pointer while providing added features, such as automatic memory management or bounds checking.

智能指针和普通指针的区别在于智能指针实际上是对普通指针加了一层封装机制,这样的一层封装机制的目的是为了使得智能指针可以方便的管理一个对象的生命期。

在C++中,我们知道,如果使用普通指针来创建一个指向某个对象的指针,那么在使用完这个对象之后我们需要自己删除它,例如:

ObjectType* temp_ptr = new ObjectType();
temp_ptr->foo();
delete temp_ptr;

很多材料上都会指出说如果程序员忘记在调用完temp_ptr之后删除temp_ptr,那么会造成一个悬挂指针(dangling pointer),也就是说这个指针现在指向的内存区域其内容程序员无法把握和控制,也可能非常容易造成内存泄漏。

智能指针设计思想:将基本类型指针封装为类对象指针(这个类肯定是个模板,以适应不同基本类型的需求),并在析构函数里编写delete语句删除指针指向的内存空间。

STL一共给我们提供了四种智能指针:auto_ptr、unique_ptr、shared_ptr和weak_ptr(本文章暂不讨论)。 模板auto_ptr是C++98提供的解决方案,C+11已将将其摒弃,并提供了另外两种解决方案。

二、四种智能指针简介

1. auto_ptr

智能指针中很重要的一个概念是所有权问题:智能指针和祼指针都统称为指针,它们共同的目标是通过地址去代表资源,智能指针是否有该资源的所有权,是取决于使用各种智能指针的关键。

The auto_ptr has semantics of strict ownership, meaning that the auto_ptr instance is the sole entity responsible for the object‘s lifetime. If an auto_ptr is copied, the source loses the reference.

 1 int main(int argc, char **argv)
 2 {
 3     int *i = new int;
 4     auto_ptr<int> x(i);
 5     auto_ptr<int> y;
 6
 7     y = x;
 8
 9     cout << x.get() << endl; // Print NULL
10     cout << y.get() << endl; // Print non-NULL address i
11
12     return 0;
13 }

x经过assignment copy以后,所有权发生转移,资源的所有权转移到y,x已经变成nullptr。这时再对x进行操作,很可能发生core。

auto_ptr采用可以采用copy语义来转移指针资源的所有权的同时将原指针置为NULL,这跟通常理解的copy行为是不一致的,而这样的行为要有些场合下不是我们希望看到的.

auto_ptr两个缺陷:

o 复制和赋值会改变资源的所有权,不符合人的直觉。
o 在 STL 容器中无法使用auto_ptr ,因为容器内的元素必需支持可复制(copy constructable)和可赋值(assignable)。

2、unique_ptr

unique_ptr是c++11中用于替代auto_ptr的智能指针。

1 auto_ptr<string> p1(new string ("auto") ; //#1
2 auto_ptr<string> p2;                       //#2
3 p2 = p1;                                   //#3

在语句#3中,p2接管string对象的所有权后,p1的所有权将被剥夺。前面说过,这是好事,可防止p1和p2的析构函数试图刪同—个对象

但如果程序随后试图使用p1,这将是件坏事,因为p1不再指向有效的数据。

下面来看使用unique_ptr的情况:

1 unique_ptr<string> p3 (new string ("auto");   //#4
2 unique_ptr<string> p4;                       //#5
3 p4 = p3;                                      //#6

编译器认为语句#6非法,避免了p3不再指向有效数据的问题。因此,unique_ptr比auto_ptr更安全。

本质上来说,就是unique_ptr禁用了copy,而用move替代。unique_ptr禁止左值复制,不过可以通过std::move将左值转换为右值属性

1 unique_ptr<string> ps1, ps2;
2 ps1 = demo("hello");
3 ps2 = move(ps1);
4 ps1 = demo("alexia");
5 cout << *ps2 << *ps1 << endl;

3、shared_ptr

unique_ptr是独占资源的,shared_ptr可以实现共享资源。

When using unique_ptr, there can be at most one unique_ptr pointing at any one resource. When that unique_ptr is destroyed, the resource is automatically reclaimed. Because there can only be one unique_ptr to any resource, any attempt to make a copy of a unique_ptr will cause a compile-time error. For example, this code is illegal:

1 unique_ptr<T> myPtr(new T);       // Okay
2 unique_ptr<T> myOtherPtr = myPtr; // Error: Can‘t copy unique_ptr

However, unique_ptr can be moved using the new move semantics:

unique_ptr<T> myPtr(new T);                  // Okay
unique_ptr<T> myOtherPtr = std::move(myPtr); // Okay, resource now stored in myOtherPtr

Similarly, you can do something like this:

1 unique_ptr<T> MyFunction() {
2     unique_ptr<T> myPtr(/* ... */);
3
4     /* ... */
5
6     return myPtr;
7 }

This idiom means "I‘m returning a managed resource to you. If you don‘t explicitly capture the return value, then the resource will be cleaned up. If you do, then you now have exclusive ownership of that resource." In this way, you can think of unique_ptr as a safer, better replacement for auto_ptr.

shared_ptr, on the other hand, allows for multiple pointers to point at a given resource. When the very last shared_ptr to a resource is destroyed, the resource will be deallocated. For example, this code is perfectly legal:

1 shared_ptr<T> myPtr(new T);       // Okay
2 shared_ptr<T> myOtherPtr = myPtr; // Sure!  Now have two pointers to the resource.

Internally, shared_ptr uses reference counting to track how many pointers refer to a resource, so you need to be careful not to introduce any reference cycles.

简单来说,shared_ptr通过引用计数refCount保证有多个ptr共同拥有资源的所有权,当所有的资源都释放所有权refCount=0,释放该资源。

shared_ptr的简单实现:

 1 template <typename V>
 2 class SmartPtr {
 3 private:
 4     int * refcnt;
 5     V * v;
 6 public:
 7     SmartPtr(V* ptr): v(ptr) {
 8         refcnt = new int(1);
 9     }
10
11     SmartPtr(const SmartPtr& ptr) {
12         this->v = ptr.v;
13         this->refcnt = ptr.refcnt;
14         *refcnt += 1;
15     }
16
17     ~SmartPtr() {
18         cout << "to delete a smart pointer" << endl;
19         *refcnt -= 1;
20
21         if (*refcnt == 0) {
22             delete v;
23            delete refcnt;
24        }
25     }
26 };
27
28 int main() {
29     A * ptrA = new A();
30     SmartPtr<A> sp1(ptrA);
31     SmartPtr<A> sp2 = sp1;
32
33     return 0;
34 }

这个例子中中需要注意的点是引用计数是所有管理同一个指针的智能指针所共享的,所以在这个例子中,sp1和sp2的引用计数指向的是相同的一个整数。

我们看一下这个例子的输出:

# g++ -o smart myShare.cpp
# ./smart
create object of A
to delete a smart pointer
to delete a smart pointer
destroy an object A

可以看到,这个和shared_ptr一样可以正确地delete指针。

4、weak_ptr

weak_ptr和Raw Pointer很类似,只标记指向该资源,没有该资源的所有权。同时weak_ptr扩展了Raw Pointer的功能。

std::weak_ptrs are typically created from std::shared_ptrs. They point to the same place as the std::shared_ptrs initializ‐
ing them, but they don’t affect the reference count of the object they point to:

auto spw = // after spw is constructed,
std::make_shared<Widget>(); // the pointed-to Widget‘s
// ref count (RC) is 1. (See
// Item 21 for info on
// std::make_shared.)
…
std::weak_ptr<Widget> wpw(spw); // wpw points to same Widget
// as spw. RC remains 1
…
spw = nullptr; // RC goes to 0, and the
// Widget is destroyed.
// wpw now dangles

std::weak_ptrs that dangle are said to have expired. You can test for this directly,

if (wpw.expired()) … // if wpw doesn‘t point
// to an object…

有两种方法从weak_ptr获取shared_ptr:

One form is std::weak_ptr::lock, which returns a std::shared_ptr. The std::shared_ptr is null
if the std::weak_ptr has expired:

std::shared_ptr<Widget> spw1 = wpw.lock(); // if wpw‘s expired,
// spw1 is null
auto spw2 = wpw.lock(); // same as above,
// but uses auto

The other form is the std::shared_ptr constructor taking a std::weak_ptr as an
argument. In this case, if the std::weak_ptr has expired, an exception is thrown:

std::shared_ptr<Widget> spw3(wpw); // if wpw‘s expired,
// throw std::bad_weak_ptr

As a final example of std::weak_ptr’s utility, consider a data structure with objects
A, B, and C in it, where A and C share ownership of B and therefore hold
std::shared_ptrs to it:

There are three choices:
? A raw pointer. With this approach, if A is destroyed, but C continues to point to
B, B will contain a pointer to A that will dangle. B won’t be able to detect that, so B
may inadvertently dereference the dangling pointer. That would yield undefined
behavior.
? A std::shared_ptr. In this design, A and B contain std::shared_ptrs to each
other. The resulting std::shared_ptr cycle (A points to B and B points to A) will
prevent both A and B from being destroyed. Even if A and B are unreachable from
other program data structures (e.g., because C no longer points to B), each will
have a reference count of one. If that happens, A and B will have been leaked, for
all practical purposes: it will be impossible for the program to access them, yet
their resources will never be reclaimed.
? A std::weak_ptr. This avoids both problems above. If A is destroyed, B’s
pointer back to it will dangle, but B will be able to detect that. Furthermore,
though A and B will point to one another, B’s pointer won’t affect A’s reference
count, hence can’t keep A from being destroyed when std::shared_ptrs no
longer point to it.

(符号 &(reference),表示".....的地址"("address of"),因此成为地址操作符(adress operator),又称引用操作符(reference operator)

符号 *(dereference),表示".....所指向的值"("value pointed to by"))

三、智能指针总结

在选择具体指针类型的时候,通过问以下几个问题就能知道使用哪种指针了。

  • 指针是否需要拥有资源的所有权?

如果指针变量需要绑定资源的所有权,那么会选择unique_ptr或shared_ptr。它们可以通过RAII完成对资源生命期的自动管理。如果不需要拥有资源的所有权,那么会选择weak_ptr和raw pointer,这两种指针变量在离开作用域时不会对其所指向的资源产生任何影响。

  • 如果指针拥有资源的所有权(owning pointer),那么该指针是否需要独占所有权?

独占则使用unique_ptr(人无我有,人有我丢),否则使用shared_ptr(你有我有全都有)。这一点很好理解。

  • 如果不拥有资源的所有权(non-owning pointer),那么指针变量是否需要在适当的时候感知到资源的有效性?

如果需要则使用weak_ptr,它可以在适当的时候通过weak_ptr::lock()获得所有权,当拥有所有权后便可以得知资源的有效性。如不需要,则使用祼指针。这通常是程序员知道在祼指针的作用域内它是有效的,并且使用祼指针效率更高,例如

1 auto p = make_shared<int>(1);
2 auto result = f(p.get());

这样会衍生出另外一个问题,为何unique_ptr不能和weak_ptr配合?
这是因为unique_ptr是独占所有权,也就是说资源的生命期等于指针变量的生命期,那么程序员可以很容易通过指针变量的生命期来判断资源是否有效,这样weak_ptr就不再有必要了。而相对来说,shared_ptr则不好判断,特别是多线程环境下。

另外,很多人说weak_ptr的作用是可以破除循环引用,这个说法是对的,但没有抓住本质(祼指针也可以破除,那为何要用weak_ptr?)。写出循环引用的原因是因为程序员自己没有理清楚资源的所有权问题。

原文地址:https://www.cnblogs.com/ym65536/p/9201245.html

时间: 2024-10-09 09:35:39

【c++工程实践】智能指针的相关文章

c++ STL 工程实践的15条建议

STL是c++非常重要的一部分,它是很多大神的杰作,高效,稳定,可扩展性好,虽然STL确实存在难以调试,内存碎片的问题(现在机器的内存越来越大,内存碎片的问题基本不太可能成为系统瓶颈,但只要你使用恰当,它能显著提高生产力,并使代码更短,更易维护. 而在工程中,如果不养成好的使用习惯,也容易造成一些问题! 而自己在工程实践中,总结了一些建议 1.  vector, map, set, list, queue, dequeue, stack 等容器存放的对象必须是可拷贝的,尤其注意深拷贝的情况,必须

Chromium和WebKit的智能指针实现原理分析

C++不像Java一样,由虚拟机负责对象分配和释放.也就是说,开发人员使用C++编写代码时,要自己负责对象分配和释放.WebKit和Chromium都是使用C++开发的,因此它们也面临上述问题.在解决对象释放问题时,要做到在对象不需要时自动释放,因为手动释放会带来忘记释放或者释放后又继续使用的隐患.智能指针是实现对象自动释放的有效技术手段.本文就分析Chromium和WebKit的智能指针的实现. 老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注! 在现实中,

C++智能指针简单剖析

导读 最近在补看<C++ Primer Plus>第六版,这的确是本好书,其中关于智能指针的章节解析的非常清晰,一解我以前的多处困惑.C++面试过程中,很多面试官都喜欢问智能指针相关的问题,比如你知道哪些智能指针?shared_ptr的设计原理是什么?如果让你自己设计一个智能指针,你如何完成?等等--.而且在看开源的C++项目时,也能随处看到智能指针的影子.这说明智能指针不仅是面试官爱问的题材,更是非常有实用价值. 下面是我在看智能指针时所做的笔记,希望能够解决你对智能指针的一些困扰. 目录

webkit智能指针 - RefPtr, PassRefPtr

历史 2005年之前,Webkit中很多对象都采用引用计数的方式.它们通过继承RefCounted]类模板来实现这种模式.RefCounted主要是实现了ref()和deref()两个函数.在需要引用对象时要调用ref()增加引用计数,在不再需要对象时,要调用deref()函数减少引用计数.ref()和deref()需要成对出现.这和使用new/delete一样,多调用.少调用.没调用的问题总是时有发生.如果能由编译器自动完成ref, deref的调用,C/C++编程的bug至少也可以减少一半以

(转)剖析C++标准库智能指针(std::auto_ptr)

不可否认,资源泄露(resource leak)曾经是C++程序的一大噩梦.垃圾回收 机制(Garbage Collection)一时颇受注目.然而垃圾自动回收机制并不能 满足内存管理的即时性和可视性,往往使高傲的程序设计者感到不自在. 况且,C++实现没有引入这种机制.在探索中,C++程序员创造了锋利的 "Smart Pointer".一定程度上,解决了资源泄露问题. 也许,经常的,你会写这样的代码: //x拟为class: // class x{ // public: // int

【转】C++智能指针简单剖析

原文链接:http://www.cnblogs.com/lanxuezaipiao/p/4132096.html 导读 最近在补看 <C++ Primer Plus>第六版,这的确是本好书,其中关于智能指针的章节解析的非常清晰,一解我以前的多处困惑.C++面试过程中,很多面试官都喜欢问智能指针相关的 问题,比如你知道哪些智能指针?shared_ptr的设计原理是什么?如果让你自己设计一个智能指针,你如何完成?等等…….而且在看开源的C++项目 时,也能随处看到智能指针的影子.这说明智能指针不仅

智能指针学习笔记

1. 介绍 本文介绍智能指针的使用.智能指针是c++ 中管理资源的一种方式,用智能指针管理资源,不必担心资源泄露,将c++ 程序员 从指针和内存管理中解脱出来,再者,这也是c++发展的趋势(这话不是我说的,见<Effective c++>和<c++实践编程>),应该认真学习一下. 智能指针中,最有名的应该数auto_ptr,该智能指针已经被纳入标准库,只需要包含<memory>头文件即可以使用,另外,TR1文档定义的shared_ptr和weak_ptr也已经实现(我用

智能指针和前置声明之间的小问题

对比Go等其他语言的工程,C++工程让人痛苦的一件事情就是当工程稍微庞大一点,编译时间就蹭蹭蹭往上爬.一般来说看过Effective C++这本书或者其他类似书籍的人都知道要解决编译时长的问题,就要解决好和头文件之间的依赖关系.所以在任何必要的时候要首先考虑使用前置声明而不是之间include头文件.也就是说,在定义类的时候成员变量如果是自定义类型,可以考虑将其声明为指针类型或者是配合智能指针.函数传参时也是一样,使用指针或者引用. 对于一个C工程来说,因为没有智能指针和引用的概念,所以都是直接

智能指针的简单总结

1. 智能指针背后的设计思想 我们先来看一个简单的例子: void remodel(std::string & str) { std::string * ps = new std::string(str); ... if (weird_thing()) throw exception(); str = *ps; delete ps; return; } 当出现异常时(weird_thing()返回true),delete将不被执行,因此将导致内存泄露. 如何避免这种问题?有人会说,这还不简单,直