C++智能指针

原文链接https://www.bogotobogo.com/cplusplus/boost.php

智能指针是一个对大多数意图和目的像指针一样起作用的对象,但它避免了C++指针固有的大多数问题。最简单来说,智能指针包含作为数据成员的原始指针,并提供一组重载操作符,这些操作符使它在大多数情况下都像指针一样工作。指针可以被撤销,因此,*和->运算符重载以返回预期的地址。  指针可以进行指针算术运算,因此,+, -, ++和--运算符也被适当地重载。

因为智能指针是一个对象,所以它可以包含额外的元数据,并采取常规指针所不能采取的额外步骤。例如,智能指针可能包含允许它识别指向的对象何时被删除的信息,如果是则开始返回NUll。

智能指针还可以通过彼此协作来确定对特定对象的引用数量来帮助对象生存期管理。这被称为引用计数。当引用特定对象的智能指针数量下降到零时,我们知道不再需要该对象,因此可以自动删除它。 这可以使程序员不必担心对象所有权和孤立对象(仍然占用内存但不再需要或被系统中的任何其他对象引用的对象)。

智能指针有他们自己的问题。一方面,它们相对容易实施,但要做到正确却是非常困难的。有很多情况需要处理,而标准C++库提供的std::auto_ptr类被广泛认为在很多情况下都是不够的,现在已经被弃用。(leo注:C++11把shared_ptr加入了标准库)

Table of Contents

1、shared_ptr

2、shared_array

3、scoped_ptr(leo注:该部分略)

4、scoped_array

5、weak_ptr

6、intrusive_ptr(leo注:该部分略)


1、shared_ptr

auto_ptr 具有不寻常的特征:无论是通过拷贝构造函数还是通过拷贝分配操作符复制都将其设置为空,(leo注:复制完原对象指针为空)并且复制指针假定资源的所有权,如下面的示例所示: 

#include <iostream>
#include <memory>

using namespace std;
class A{};

int main()
{
	auto_ptr<A> pA(new A);

	cout << pA.get() << endl;

	auto_ptr<A> pB(pA);

	cout << pA.get() << endl;
	cout << pB.get() << endl;

	return 0;
}
输出:
001B0950
00000000
001B0950

在这个例子中,get()方法返回一个指向auto_ptr对象指向的对象的指针,如果有的话;或者如果它没有指向任何对象,则返回0。

注意,第二个输出是null。因此,在拷贝构造函数中,pAA对象的所有权转移到pB

这种行为和由auto_ptrs管理的资源必须永远不能有一个以上的auto_ptr指向它们的基本要求意味着auto_ptrs不是处理动态分配的资源的最佳方式。

因此,作为auto_ptr的替代,我们有一个引用计数智能指针。它跟踪指向特定资源的对象数量,并在没有指向资源的情况下自动删除资源。

通过用shared_ptr替换auto_ptr,代码几乎相同,它产生了我们想要的输出:

#include <boost/smart_ptr/shared_ptr.hpp>
#include <iostream>
#include <memory>

class A{};

int main()
{
	boost::shared_ptr<A> pA(new A);

	std::cout << pA.get() << std::endl;

	boost::shared_ptr<A> pB(pA);

	std::cout << pA.get() << std::endl;
	std::cout << pB.get() << std::endl;

	return 0;
}
输出:
002C0950
002C0950
002C0950

由于复制boost::shared_ptr正如我们所期望的那样工作,所以它可以在STL容器中使用,而我们不能在STL容器中使用std::auto_ptr

使用share_ptr解决的主要问题是知道删除共享资源的正确时间。下面的示例有两个类, A 和 B。类共享int的实例,并存储一个shared_ptr<int>。 当我们创建每个类的实例时,shared_ptr pTemp将传递给构造函数。换句话说,所有三个shared_ptrs现在都引用了一个int的相同实例。 如果我们使用指针来实现int的这种共享,那么每个类将很难确定何时应该删除它。在这个例子中,直到main()函数的末尾,引用计数为3。 如果所有指针超出范围,则引用计数达到0,从而删除int int的共享实例。

shared_ptr 保存指向资源的内部指针,例如可以与程序中的其他对象共享的动态分配的对象。 我们可以在同一资源上拥有任意数量的ofshared_ptrs。 shared_ptr 确实共享资源,如果我们用一个shared_ptr更改资源,那么其他shared_ptrs也会看到更改。一旦指向资源的最后一个shared_ptr被删除,内部指针将被删除。shared_ptr使用引用计数来确定多少个shared_ptrs指向资源。每次创建资源的新的shared_ptr时,引用计数增加,而每次销毁引用计数减少。 当引用计数达到零时,删除内部指针并释放内存。

#include <boost/smart_ptr/shared_ptr.hpp>;
#include <iostream>
#include <memory>

class classA
{
	boost::shared_ptr<int> ptA;
public:
	classA(boost::shared_ptr<int> p) : ptA(p) {}
	void setValue(int n) {
		*ptA = n;
	}
};

class classB
{
	boost::shared_ptr<int> ptB;
public:
	classB(boost::shared_ptr<int> p) : ptB(p) {}
	int getValue() const {
		return *ptB;
	}
};

int main()
{
	boost::shared_ptr<int> pTemp(new int(2013));
	classA a(pTemp);
	classB b(pTemp);

	a.setValue(2014);
	std::cout << "b.getValue() = " << b.getValue() << std::endl;

	return 0;
}
输出:
b.getValue() = 2014

shared_ptr还允许我们确定资源将如何被销毁。对于大多数动态分配的对象,使用delete。 然而,一些资源需要更复杂的清理。 在这种情况下,我们可以将自定义的deleter函数或函数对象提供给shared_ptr析构函数deleter确定如何销毁资源。当引用计数达到零并且资源准备好被销毁时,shared_ptr调用定制deleter函数。这个功能使一个shared_ptr能够管理几乎任何类型的资源。 

对于动态分配的数组,我们不应该使用它们(leo注:上述shared_ptr和auto_ptr智能指针)中的任何一个,因为它们在析构函数中使用delete,而不是delete[]。我们可以用vector代替。如果我们坚持使用Boost,我们可以使用boost::shared_array 或 boost::scoped_array

2、shared_array

指向array对象的指针,这些对象的生命周期是由多个所有者共享的。

3、scoped_ptr(leo注:该部分略)

指向同一个所有者的单个对象的指针。

4、scoped_array

指向一个拥有一个所有者的对象数组的指针。

5、weak_ptr

不拥有或自动销毁它所引用的对象的指针(假设它的生存期由shared_ptr管理)。 我们可以把它看作是一个shared_ptr 的观察者

weak_ptr指向由shared_ptr管理的资源,而不承担任何责任。 当weak_ptr引用shared_ptr时,shared_ptr的引用计数不增加。 这意味着可以删除一个shared_ptr的资源,而仍然有一个weak_ptr指向它。 当最后一个shared_ptr被销毁时,该资源被删除,并且任何剩余的weak_ptr被设置为NULL 如下面的例子所示,对于weak_ptr的一种用法是避免循环引用引起的内存泄漏

weak_ptr 不能直接访问它所指向的资源,我们必须从weak_ptr创建shared_ptr来访问资源。有两种方法可以做到这一点。

  1. 我们可以将weak_ptr传递到 shared_ptr 构造函数。 它创建了一个由weak_ptr指向的,指向的资源shared_ptr,并适当地增加引用计数。如果资源已经被删除,则shared_ptr构造函数将抛出一个boost::bad_weak_ptr异常。
  2. 我们也可以调用weak_ptr成员函数lock(), 它返回一个指向weak_ptr的资源的shared_ptr。如果weak_ptr指向一个已删除的资源,锁将返回一个空的shared_ptr。当一个空的shared_ptr不被认为是错误时,应该使用lock()。一旦你有了一个shared_ptr的资源,我们就可以访问它。 在下面的例子中显示了这种方法。

weak_ptr 应该用于任何需要观察资源但不想承担任何管理责任的情况。该示例显示了在循环引用数据中使用weak_ptrs,其中两个对象在内部相互引用。

在下面的例子中,我们定义类SingerSong。每个类都有指向另一个类的实例的指针。这就创建了两个类之间的循环引用。请注意,我们同时使用weak_ptrshared_ptr来保持对每个类的交叉引用。 

SingerSong类定义了析构函数,每个析构函数都显示一条消息,以指示何时销毁任一类的实例。每个类还定义一个成员函数来打印歌曲的标题和歌手的名字。因为我们不能直接通过weak_ptr访问资源,所以首先我们从weak_ptr数据成员lock()创建shared_ptr。如果weak_ptr引用的资源不存在,则对lock()函数的调用返回一个shared_ptr,该shared_ptr指向NULL,并且条件失败。否则,新的shared_ptr包含一个有效的指针指向weak_ptr的资源,并且我们可以访问该资源。如果条件为真(当songPtrsingerPtr都不为NULL时),我们打印引用计数,以显示它随着新的shared_ptr的创建而增加,然后我们打印Song和Singer名称的标题。当函数退出时,shared_ptr销毁,使引用计数减少1。 

下面是代码:

Singer.h

#ifndef SINGER_H
#define SINGER_H
#include <string>
using std::string;

#include "boost/shared_ptr.hpp"
#include "boost/weak_ptr.hpp" 

class Song;

class Singer
{
public:
	Singer::Singer(const string &SingerName;);
	~Singer();
	void printSongTitle() ;
        string name; 
	boost::weak_ptr<Song> weakSongPtr;   
	boost::shared_ptr<Song> sharedSongPtr; 
};
#endif
        

Song.h

#ifndef SONG_H
#define SONG_H

#include <string>
using std::string;

#include "boost/shared_ptr.hpp"
#include "boost/weak_ptr.hpp" 

class Singer; // forward declaration

class Song
{
public:
	Song(const string &SongTitle;) ;
        ~Song();
	void printSingerName();
        string title; 
        boost::weak_ptr<Singer> weakSingerPtr;   
        boost::shared_ptr<Singer> sharedSingerPtr; 
};
#endif
        

Singer.cpp

#include <string>
using namespace std;

#include "boost/shared_ptr.hpp"
#include "boost/weak_ptr.hpp" 

#include "Song.h"
#include "Singer.h"

Singer::Singer(const string &SingerName;) : name(SingerName) 
{
	cout << "Singer constructor: " << name << endl;
}

Singer::~Singer() 
{
	cout << "Singer destructor: " << name << endl; 
}

void Singer::printSongTitle() 
{
	// if weaksongPtr.lock() returns a non-empty shared_ptr
	if (boost::shared_ptr<Song> songPtr = weakSongPtr.lock()) { 
		cout << "Reference count for song " << songPtr->title            
		<< " is " << songPtr.use_count() << "." << endl;              
		cout << "Singer " << name << " wrote the song " << songPtr->title << "\n\n";
	} 
	else // weaksongPtr points to NULL
	        cout << "This Singer has no song." << endl;
}
        

Song.cpp

#include <string>
using namespace std;

#include "boost/shared_ptr.hpp"
#include "boost/weak_ptr.hpp" 

#include "Song.h"
#include "Singer.h"

Song::Song(const string &SongTitle;) : title(SongTitle) 
{
	cout << "Song constructor: " << title << endl;
}
Song::~Song() 
{
	cout << "Song destructor: " << title << endl;
}
void Song::printSingerName() 
{
	// if weakSingerPtr.lock() returns a non-empty shared_ptr
	if (boost::shared_ptr<Singer> singerPtr = weakSingerPtr.lock()  ) {
		// show the reference count increase and print the Singer's name
		cout << "Reference count for Singer " << singerPtr->name 
		     << " is " << singerPtr.use_count() << "." << endl;    
		cout << "The Song " << title << " was written by "       
		     << singerPtr->name << "\n" << endl;   
		} 
 	else // weakSingerPtr points to NULL
 		cout << "This Song has no Singer." << endl;
}
        

main.cpp

#include <iostream>
#include <string>

#include "boost/shared_ptr.hpp"
#include "boost/weak_ptr.hpp"

#include "Singer.h"
#include "Song.h"

using namespace std;

int main()
{
	cout << "Creating a Song and an Singer ..." << endl;
	boost::shared_ptr<Song> SongPtr( new Song( "The Boys" ) );
	boost::shared_ptr<Singer> SingerPtr(new Singer( "Girls Generation" ) ); 

	cout << "\nReferencing the Song and Singer to each other..." << endl;
	SongPtr->weakSingerPtr = SingerPtr;
	SingerPtr->weakSongPtr = SongPtr;  

 	cout << "\nSetting the shared_ptr data members to create the memory leak..." << endl;
        
	SongPtr->sharedSingerPtr = SingerPtr;
	SingerPtr->sharedSongPtr = SongPtr; 
	
	cout << "Reference count for SongPtr and SingerPtr should be one, but ... " << endl;
	cout << "Reference count for Song " << SongPtr->title << " is "
	     << SongPtr.use_count() << endl;
	cout << "Reference count for Singer " << SingerPtr->name << " is "
	     << SingerPtr.use_count() << "\n" << endl;

	cout << "\nAccess the Singer's name and the Song's title through "
        << "weak_ptrs." << endl;
	SongPtr->printSingerName(); 
	SingerPtr->printSongTitle();
 
	cout << "Reference count for each shared_ptr shoulb be back to one:" << endl;
	cout << "Reference count for Song " << SongPtr->title << " is "
	     << SongPtr.use_count() << endl;
        cout << "Reference count for Singer " << SingerPtr->name << " is "
             << SingerPtr.use_count() << "\n" << endl;
 
 	// the shared_ptrs go out of scope, the Song and Singer are destroyed
	cout << "The shared_ptrs are going out of scope." << endl;
 
	return 0;
}

 在 main()函数, 我们看到了由类Singer和Song之间的循环引用引起的内存泄漏。代码行:

   boost::shared_ptr<Song> SongPtr( new Song( "The Boys" ) );
     boost::shared_ptr<Singer> SingerPtr(new Singer( "Girls Generation" ) );

shared_ptrs创建到每个类的实例。weak_ptr数据成员设置为:

   SongPtr->weakSingerPtr = SingerPtr;
     SingerPtr->weakSongPtr = SongPtr; 

每个类的shared_ptr数据成员都设置在下面的行中。 

   SongPtr->sharedSingerPtr = SingerPtr;
     SingerPtr->sharedSongPtr = SongPtr; 

SingerSong两类的实例现在相互参照。 然后,我们打印对shared_ptr的引用计数,以显示每个实例都由两个我们在main()函数和每个实例的数据成员中创建的shared_ptrs引用。(leo注:原文这句话被截为两段)

   cout << "Reference count for Song " << SongPtr->title << " is "
	  << SongPtr.use_count() << endl;
     cout << "Reference count for Singer " << SingerPtr->name << " is "
	  << SingerPtr.use_count() << "\n" << endl;

回想一下,weak_ptr不会影响引用计数。然后,我们调用每个类的成员函数来打印存储在weak_pt数据成员中的信息: 

     SongPtr->printSingerName(); 
     SingerPtr->printSongTitle();

函数还显示了在函数调用期间创建另一个shared_ptr的事实。 最后,我们再次打印引用计数,以表明在printSingerNameprintSongTitle成员函数中创建的附加shared_ptrs在函数完成时被销毁。

   cout << "Reference count for Song " << SongPtr->title << " is "
          << SongPtr.use_count() << endl;
     cout << "Reference count for Singer " << SingerPtr->name << " is "
          << SingerPtr.use_count() << "\n" << endl;

输出:

     Creating a Song and an Singer ...
     Song constructor: The Boys
     Singer constructor: Girls Generation

     Referencing the Song and Singer to each other...

     Setting the shared_ptr data members to create the memory leak...
     Reference count for SongPtr and SingerPtr should be one, but ...
     Reference count for Song The Boys is 2
     Reference count for Singer Girls Generation is 2

     Access the Singer's name and the Song's title through weak_ptrs.
     Reference count for Singer Girls Generation is 3.
     The Song The Boys was written by Girls Generation

     Reference count for song The Boys is 3.
     Singer Girls Generation wrote the song The Boys

     Reference count for each shared_ptr shoulb be back to one:
     Reference count for Song The Boys is 2
     Reference count for Singer Girls Generation is 2

     The shared_ptrs are going out of scope.

main()函数结束时, 我们创建的指向SingerSong的实例的shared_ptr,超出了范围,被破坏了。注意,输出不显示类SingerSong的析构函数。该程序有内存泄漏,换句话说,SingerSong的实例不会因为shared_ptr数据成员而被破坏。当songPtrmain()的末尾被销毁时,Song类的实例的引用计数变为一,因为Singer的实例仍然具有Song的实例的shared_ptr,所以它不会被删除。当singerPtr超出范围并被销毁时,Singer类的实例的引用计数也变为一,因为Song的实例仍然具有Singer类的实例的shared_ptr。 两个实例都未被删除,因为每个引用计数仍然是一个。

现在,注释掉以下代码行:

     //SongPtr->sharedSingerPtr = SingerPtr;
     //SingerPtr->sharedSongPtr = SongPtr; 

这样可以防止代码为类SingerSong设置shared_ptr数据成员。然后,我们将有一个新的输出:

   Creating a Song and an Singer ...
     Song constructor: The Boys
     Singer constructor: Girls Generation

     Referencing the Song and Singer to each other...

     Setting the shared_ptr data members to create the memory leak...
     Reference count for SongPtr and SingerPtr should be one, but ...
     Reference count for Song The Boys is 1
     Reference count for Singer Girls Generation is 1

     Access the Singer's name and the Song's title through weak_ptrs.
     Reference count for Singer Girls Generation is 2.
     The Song The Boys was written by Girls Generation

     Reference count for song The Boys is 2.
     Singer Girls Generation wrote the song The Boys

     Reference count for each shared_ptr shoulb be back to one:
     Reference count for Song The Boys is 1
     Reference count for Singer Girls Generation is 1

     The shared_ptrs are going out of scope.
     Singer destructor: Girls Generation
     Song destructor: The Boys

注意,每个实例的初始引用计数现在是一个而不是两个,因为我们没有设置shared_ptr数据成员。输出的最后两行表明,在 main()的末尾,类SingerSong的实例被销毁。 我们通过使用weak_ptr数据成员来消除内存泄漏,而不是使用shared_ptr数据成员。weak_ptr不影响引用计数,但是仍然允许我们在需要时通过创建资源的临时shared_ptr来访问资源。当main() 中创建的 shared_ptr被销毁时,引用计数变为SingerSong类的实例被适当删除。

6、intrusive_ptr(leo注:该部分略)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值