c++智能指针_C ++智能指针

c++智能指针html

This article is a discussion on smart pointers, what they are and why they are important to C++ programmers. Following the primary discussion I present a simple implementation of a reference counted smart pointer and show a simple example of using it. Although this article does not go into detail about how to develop a reference counted smart pointer the example code at the end is very well commented and that should be enough to aid understanding.node

本文讨论了智能指针 ,它们是什么以及为何它们对C ++程序员很重要。 在主要讨论以后,我展现了一个引用计数智能指针的简单实现,并展现了一个使用它的简单示例。 尽管本文没有详细介绍如何开发引用计数的智能指针,但最后的示例代码获得了很好的注释,应该足以帮助理解。 c++

This article is targeted at an intermediate level C++ programmer; however, anyone who develops using C++ (even as a student) would benefit from reading this article and taking to heart the principles it discusses even if you don't follow all of the technical concepts introduced. Not all the terms I use are necessarily explained in this article (I've kept it focused on the core subject); however, anytime a new term is introduced it will be linked to a reference where you can find out more.程序员

本文针对中级C ++程序员。 可是,即便您不遵循全部引入的技术概念,使用C ++开发的任何人(即便是学生)也将从阅读本文并牢记所讨论的原理中受益。 本文并不必定会解释我使用的全部术语(我一直将其重点放在核心主题上)。 可是,不管什么时候引入新术语,都会将其连接到参考文献,您能够在其中找到更多信息。 web

Please note, all the code shared in this article is my own; however, during the development of my smart pointer I used the Boost shared_ptr as a basis for the interface to ensure I had captured all the necessary ingredients to provide a fully working smart pointer. If you have access to Boost then, please, do use the range of high quality, peer reviewed smart pointers provided in preference to the one I discuss here. My example, although fully working (and bug free I hope), is meant for educational purposes rather than use in production code and doesn't, for example, implement support for thread safety or intrusive reference counting.设计模式

请注意,本文共享的全部代码都是我本身的; 可是,在开发智能指针的过程当中,我使用Boost shared_ptr做为接口的基础,以确保我已捕获了全部必要的要素,以提供彻底可用的智能指针。 若是您可使用Boost,请优先使用一系列高质量的,通过同行评审的智能指针 ,而不是我在这里讨论的那种。 个人示例虽然能够正常运行(我但愿它没有错误),但其目的是出于教育目的,而不是在生产代码中使用,而且例如,未实现对线程安全侵入式引用计数的支持数组

What is a smart pointer? That is a very good question... I'm glad you asked. First, though, allow me to introduce you to my cleaner Raii (pronounced Rye). Her job is to clean up after me. I'm pretty messy, I often get things out and forget to put them back but good old Raii follows me around and when I'm done with something she puts it away for me. Raii can be your cleaner too if you ask her. Isn't she nice? Would you like me to introduce you to her? Yeah? Okay, say hello to Raii or Resource Acquisition is Initialisation to give her her full and rather grand title.安全

什么是智能指针? 这是一个很好的问题...很高兴你问。 不过,首先,请容许我向您介绍个人清洁工Raii(发音为Rye)。 她的工做是跟着我打扫卫生。 我很乱,我常常把东西拿出来,忘了把它们放回去,可是好老的Raii跟着我走,当我作完某件事时,她就把它丢给我了。 若是您问Raii,她也能够成为您的清洁工。 她不是很好吗? 您想让我向您介绍她吗? 是啊 好吧,向Raii打招呼或“ 资源获取是初始化”来给她她完整而又宏伟的头衔。 app

Okay I admit it,  Raii isn't a real person; rather, she's a C++ idiom also, sometimes, referred to as a design pattern. Basically, anytime you allocate a resource you immediately initialise a Raii object with this resource and for the lifetime of the Raii object your resource is accessible but as soon as the Raii object is destroyed it automatically cleans up your resource too. So what does this have to do with smart pointers? Well a smart pointer is just a specialised Raii pattern. It specialises in managing the lifetime of heap allocated memory and will dispose of it when it is destroyed.框架

好吧,我认可,Raii不是一个真实的人。 相反,她也是C ++ 习惯用法 ,有时也被称为设计模式 。 基本上,不管什么时候分配资源,都当即使用该资源初始化Raii对象,而且在Raii对象的生存期内,您的资源是可访问的,可是一旦Raii对象被破坏,它也会自动清理您的资源。 那么,这与智能指针有什么关系呢? 好吧,智能指针只是Raii的一种特殊模式。 它专门管理堆分配的内存的生存期,并在销毁内存时将其处置。

So, how does that work? Good question, I am so glad you asked! Let's take a look. Firstly, the following is a very simple example of allocating heap memory in a function.

那么,如何运做? 好问题,很高兴你问! 让咱们来看看。 首先,如下是在函数中分配堆内存的很是简单的示例。

 
void foo()
{
   int * pi = new int;
   // Do some work
}

Not much going on there, except did you spot the defect? That's right, the memory isn't deleted when the function ends. See how easy it is to forget? What about this example?

除了您发现缺陷以外,没有什么其余事情了? 没错,函数结束时不会删除内存。 看到忘记有多么容易? 那这个例子呢?

 
void foo()
{
   int * pi = new int;
   // Do some stuff
   delete pi;
}

Great, memory is deleted no defects there right? Wrong! What if "Do some stuff" throws an exception? When an exception is thrown unless there is a catch handler to handle it the function it is thrown from will immediately exit, the function that called it will also immediately exit if that doesn't have a catch handler and so on until an appropriate catch handler is found or the application terminates. This is called Stack Unwinding. Great, makes sense right? You'd hope so since this is ingrained within the C++ standard!

太好了,内存被删除了,那里没有缺陷吧? 错误! 若是“作一些事”抛出异常怎么办? 当引起异常时,除非有一个捕获处理程序来处理该异常,不然抛出该异常的函数将当即退出,若是没有捕获处理程序,则调用该异常的函数也将当即退出,依此类推,直到合适的捕获处理程序为止。找到或应用程序终止。 这称为堆栈展开 。 太好了,对吗? 您但愿如此,由于这在C ++标准中已根深蒂固!

So, can you see the problem (and I'm not talking about the exception causing the program to terminate, let's assume for the sake of this discussion somewhere further down the stack the exception is caught and handled)? That's right, who deletes the memory allocated in the foo() function? Answer -- no one does! Once again we have a memory leak. So how do we handle this? The obvious solution is to catch the exception, deleted the memory and tr-throw it, right? Ok, let's try that.

所以,您能看到问题了吗(我不是在说致使程序终止的异常,为便于讨论,咱们假设在堆栈的更下方某个地方捕获并处理了异常)? 是的,谁删除了foo()函数中分配的内存? 答案-没有人! 再一次,咱们有内存泄漏。 那么咱们该如何处理呢? 显而易见的解决方案是捕获异常,删除内存并把它扔掉,对吧? 好吧,让咱们尝试一下。

 
void foo()
{
   int * pi = new int;
   try
   {
   // Do some stuff
   }
   catch(...)
   {
	   delete pi;
	   throw;
   }
   delete pi;
}

Great, leak plugged... except... well it's a bit messy isn't it? We now need to have the same pointer deleted twice in one block of code. The general rule of thumb is don't duplicate code as it just makes for extra maintenance. Is there a better way? Well, yes... again we turn to the C++ standard and we find those nice people who provide the language (The C++ Standards Committee) have thoughtfully provided a smart pointer called std::auto_ptr (it lives in the <memory> header file). A smart pointer will automatically delete the memory it is managing once it goes out of scope. How? Well, remember how destructors of classes are automatically called when the class is being destroyed? Well, all that happens is we pass the smart pointer a real pointer that is pointing to heap allocated memory (normally via its constructor, hence the idiom Raii) to manage and when it goes out of scope (and, thus, is destroyed) its destructor deletes the memory for us. Neat eh? So, does this help? Let's see.

很好,堵漏了...除了...好吧,有点混乱了,不是吗? 如今,咱们须要在一个代码块中删除两次相同的指针。 通常的经验法则是不要重复代码,由于这样作只会带来额外的维护。 有没有更好的办法? 好吧,是的。。。再次,咱们转向C ++标准,咱们发现那些提供该语言的好人(C ++标准委员会)已经精心地提供了一个名为std :: auto_ptr的智能指针(它位于<memory>头文件中) )。 一旦超出范围,智能指针将自动删除它正在管理的内存。 怎么样? 好吧,还记得在销毁类时如何自动调用类的析构函数吗? 好吧,全部发生的事情就是咱们向智能指针传递了一个真正的指针,该指针指向堆分配的内存(一般经过其构造函数 ,所以是成语Raii)进行管理,而且在超出范围(并所以被销毁)时对其进行管理。析构函数会为咱们删除内存。 干净吗? 那么,这有帮助吗? 让咱们来看看。

 
#include <memory>
void foo()
{
	// Note, the constructor of auto_ptr is explicit so you MUST use explicit
	// construction (pi = new int; will cause a compilation error)
   
   std::auto_ptr<int> pi(new int); 
   
   // Do some stuff
}

Fantastic, problem solved! No need for catch handlers, no duplicate code and the auto_ptr will automatically delete the memory allocated to it when it goes out of scope, when the foo() function ends. Great, time for a cup of tea and feet up, right? Um, no. You see auto_ptr has a couple of unfortunate problems that can catch out the unwary programmer. Let's take a look.

太棒了,问题解决了! 不须要catch处理程序,没有重复的代码,而且当foo()函数结束时,auto_ptr会在超出范围时自动删除为其分配的内存。 太好了,该喝杯茶,脚站起来了,对吗? 不。 您会看到auto_ptr 遇到了一些不幸的问题 ,这些问题能够遇上那些粗心的程序员。 让咱们来看看。

Problem one: The auto_ptr type can only be used with scalar heap allocations

问题一:auto_ptr类型只能与标量堆分配一块儿使用

That's right, when you allocate memory using new and delete you have to use different syntax for scalars vs. arrays. Let's take a look.

没错,当您使用new和delete分配内存时,必须对标量和数组使用不一样的语法 。 让咱们来看看。

 
int * pi = new int; // Allocate a scalar
	delete pi; // De-allocate a scalar

	int * pi = new int[10]; // Allocate an array
	delete [] pi; // De-allocate an array

You can't mix up new and delete with new [] and delete []. You must pair them off correctly, otherwise the result is undefined (assume this is bad!). Now auto_ptr is designed specifically to delete scalars.

您不能将new和delete与new []和delete []混用。 您必须正确地将它们配对,不然结果是不肯定的(假设这很糟糕!)。 如今,auto_ptr专为删除标量而设计。

NB. The C++ Standard provides a better solution for allocating dynamic arrays, it's called a vector.

注意 C ++标准为分配动态数组提供了更好的解决方案,称为 向量

Problem two: The auto_ptr type has the concept of ownership, also know as move semantics. Let's take a look.

问题二:auto_ptr类型具备全部权的概念,也称为移动语义。 让咱们来看看。

 
#include <memory>
void foo()
{
   std::auto_ptr<int> pi(new int); 
   
   bar(pi); // this function accepts a std::auto_ptr<int> by value
   
   // Do some stuff using pi
}

The moment you call the bar() function and pass it pi, the ownership of the pointer is passed from the original pi to the one that is within the stack frame of the bar() function. When this function returns ownership is not transferred back to the original pi auto_ptr. What does this mean in simple words? When you assign pi to another auto_ptr ownership is transfered to the new auto_ptr and the original auto_ptr no longer contains a pointer to the memory it was managing, instead it now points to NULL and any attempt to use it after that will result in undefined behaviour (assume this is bad!). This is like giving your mate the money from your wallet and then going to the shops -- you have nothing to pay with so it'll end in tears!

在调用bar()函数并将其传递给pi的那一刻,指针的全部权就从原始pi传递到bar()函数的堆栈框架内的指针 。 当此函数返回时,全部权不会转移回原始的pi auto_ptr。 简单来讲这是什么意思? 当您将pi分配给另外一个auto_ptr时,全部权转移到了新的auto_ptr上,而原始的auto_ptr再也不包含指向它正在管理的内存的指针,而是如今指向NULL,以后任何使用它的尝试都将致使未定义的行为(假设这很糟糕!)。 这就像从钱包里给伴侣钱,而后去商店同样-您无钱可付,因此流泪了!

In fact, this is one of the more obvious examples, where it's clear to see what's happening. Imagine the auto_ptr was a member of another class and this was passed by value to another function, unless you're written your own copy-constructor and/or assignment operator (note, you should always implement them in pairs) to perform a safe deep-copy you'll hit the same problem. The auto_ptr member will move ownership to the new copy and the current auto_ptr member will no longer point to valid memory. It's fair to say that auto_ptr can be very dangerous indeed!

实际上,这是最明显的例子之一,很明显能够看到发生了什么。 想象一下auto_ptr是另外一个类的成员,而且已经过值将其传递给另外一个函数,除非您编写了本身的复制构造函数和/或赋值运算符 (请注意,您应始终成对实现它们)以执行安全的深刻操做-复制您将遇到相同的问题。 auto_ptr成员将全部权移至新副本,而当前的auto_ptr成员将再也不指向有效内存。 能够公平地说,auto_ptr确实很是危险!

Ok, so what do we do now? It's clearly too dangerous to use heap allocation in C++ so we'll all just become .Net managed code programmers right? Eeeek, no! Arrrrr! Quick... it's time for me to introduce our saviour, the reference counted smart pointer. Now, straight off the bat let me say that there is no default implementation of a reference counted smart pointer in C++ (yet) but the one that comes with Boost is ubiquitously use and looks to become part of the next C++ Standard, C++0X.

好的,那咱们如今该怎么办? 在C ++中使用堆分配显然太危险了,所以咱们都将成为.Net托管代码程序员,对吗? Eeeek,不! 啊! 很快...是时候介绍个人救星了,参考计数智能指针。 如今,让我直言不讳地说,C ++中尚未默认实现对引用计数的智能指针的实现(可是),但随Boost一块儿使用的却无处不在,而且看起来已成为下一个C ++标准C ++的一部分。 0X

So what is a reference counted smart pointer? Well, unlike auto_ptr a reference counted smart pointer doesn't transfer ownership. You can copy it as many times as you like and each smart pointer will contain the same pointer to the same object.

那么什么是引用计数智能指针? 好吧,与auto_ptr不一样,引用计数的智能指针不会转移全部权。 您能够根据须要屡次复制它,而且每一个智能指针将包含指向同一对象的相同指针。

How does it work? Well, as well as containing a pointer to the memory it's managing a reference counted smart pointer also contains a pointer to a counter and when you copy it the counter is incremented. The copy will point to the same object being managed and the same counter and when this goes out of scope it will decrement the counter but NOT delete the memory being managed unless the counter indicates this is the last reference. Let me say that again. Every time a copy of the smart pointer is made the counter is incremented and every time a copy goes out of scope the counter it is decremented.

它是如何工做的? 好吧,除了包含指向内存的指针外,它还管理着一个引用计数的智能指针,它还包含一个指向计数器的指针,当您复制它时,计数器会递增。 副本将指向被管理的同一对象和相同的计数器,而且当超出范围时,它将减小该计数器,但不会删除正在管理的内存,除非该计数器指示这是最后一个引用。 让我再说一遍。 每次建立智能指针的副本时,计数器都会递增,而且每次副本超出范围时,计数器都会递减。

When the counter reaches 0 that means the current copy going out of scope is the last one to reference the pointer being managed so it can safely delete the memory pointed to by the pointer without fear that another smart pointer will try to use it. It's like a bus driver who counts all the people getting on the bus and all the people getting off and when the bus is empty he can park up and have a nice cup of tea.

当计数器达到0时,表示当前副本超出范围是引用被管理指针的最后一个副本,所以它能够安全地删除该指针所指向的内存,而没必要担忧另外一个智能指针会尝试使用它。 这就像一个公交车司机,他统计着全部上车和下车的人,当公交车空了时,他能够停下来喝一杯茶。

Still not convinced for the case for using reference counted smart pointers over raw pointers? Think you're too good for them? You never forget to release memory, right? Wow, you're a tough audience! Okay, what's wrong with this then?

对于使用引用计数的智能指针而不是原始指针的状况,仍不确信? 认为您对他们太好了? 您永远不会忘记释放记忆,对吗? 哇,你真是个硬汉! 好的,那怎么了?

 
class foo
{
public:
	foo() : pi1_(new int), pi2_(new int) {}
	~foo() { delete pi1_; delete pi2_; }
private:
   int * pi1_;
   int * pi2_;
}

Nothing wrong there, right? Wrong! What if the allocation of pi2_ fails? That's ok foo's destructor will delete pi1_ right? Um no! You see destructors aren't called if the construction fails due to exception percolation. Ok, shall we try again? Sure, how about this?

那里没事吧? 错误! 若是pi2_分配失败怎么办? 好的,foo的析构函数将删除pi1_对吗? 不! 您会看到,若是因为异常渗滤而致使构造失败,则不会调用析构函数。 好,咱们再试一次吗? 固然能够吗?

 
class foo
{
public:
	foo()
	{
		try
		{
			pi1_ = new int;
			pi2_ = new int;
		}
		catch(...)
		{
			delete pi1_;
			delete pi2_;
			throw;
		}
	}
	~foo() { delete pi1_; delete pi2_; }
	
private:
   int * pi1_;
   int * pi2_;
}

Great, no more leaks right? Right! Except... um, now if pi1_ throws you'll try and delete pi2_ as well and since that currently contains an uninitialised value we'll try and delete an invalid pointer and corrupt the heap (this is really bad!). Ok, so we can get around this by initialising both pointers to be NULL in the constructor initialisation list (it's safe to delete NULL) but look what a mess we now have. Imagine if the class was more complex than just these 2 pointers!?

太好了,没有更多的泄漏了吗? 对! 除了...嗯,如今,若是pi1_抛出,您也将尝试删除pi2_,因为当前包含未初始化的值,咱们将尝试删除无效的指针并破坏堆(这确实很糟糕!)。 好的,因此咱们能够经过在构造函数初始化列表中将两个指针都初始化为NULL来解决此问题(删除NULL是安全的),可是如今看起来咱们一团糟。 想象一下,若是类比这两个指针还要复杂!

 
class foo
{
public:
	foo() : pi1_(0), pi2_(0)
	{
		try
		{
			pi1_ = new int;
			pi2_ = new int;
		}
		catch(...)
		{
			delete pi1_;
			delete pi2_;
			throw;
		}
	}
	~foo() { delete pi1_; delete pi2_; }
	
private:
   int * pi1_;
   int * pi2_;
}

Ok, this solves the problem but what a mess? Also, what happens if the re-throw was accidentally omitted from the catch block? Well, construction wouldn't fail (since we blocked its failure) and the destructor will still be called. Meanwhile you'd probably (but not necessarily, sometimes we want to block failure) then try to use a class that wasn't correctly constructed and make a right old jolly mess!

好的,这解决了问题,可是有什么麻烦呢? 另外,若是意外将重掷从接球区中遗漏了怎么办? 好了,构造不会失败(由于咱们阻止了失败),而且析构函数仍然会被调用。 同时,您可能会(但不必定,有时咱们想阻止失败)而后尝试使用未正确构建的类,并使正确的旧状况变得混乱!

Did someone scream function try block at me? Okay, I'll humour you, let's try again.

有人尖叫功能试图阻止我吗? 好吧,我会逗你的,让咱们再试一次。

 
class foo
{
public:
	foo()
	try: pi1_(new int), pi2_(new int)
	{
	}
	catch(...)
	{
		delete pi1_;
		delete pi2_;
	}
	
	~foo() { delete pi1_; delete pi2_; }
private:
   int * pi1_;
   int * pi2_;
}

Fantastic, a solution that doesn't use smart pointers! Two things though. First, how many C++ programmers even know about function try blocks? Actually not very many! The syntax can be quite confusion for someone who isn't well versed in them. Second, the catch block in this example never re-throws (just like I discussed above) so this constructor can't fail then right? Oh if only the C++ standard were that simple and consistent. The answer is (come on you knew I was going to say this right?) yes it most certainly can!

太棒了,不使用智能指针的解决方案! 不过有两件事。 首先,有多少C ++程序员甚至知道函数try块? 其实不是不少! 对于不熟悉这些语法的人,语法可能会很混乱。 其次,此示例中的catch块永远不会从新抛出(就像我上面讨论的那样),所以该构造函数不会失败,对吧? 哦,若是只有C ++标准是如此简单和一致。 答案是(来吧,您知道我会说对吗?)是的,固然能够!

You see with a constructor try block if an exception is thrown during construction it will always (yes, that's right, always) percolate it even if you don't re throw it yourself. Actually, that's a good thing normally, but not always. You might have a valid case to not re-throw, the exception might not necessarily mean construction failure. The only way to solve this is to nest yet another try block. Wow, what a tangled web we weave.

您会看到使用构造函数try块,若是在构造过程当中抛出异常,即便您本身不抛出异常,该异常也会始终(是的,这是正确的,始终是)渗透该异常。 实际上,一般这是一件好事,但并不是老是如此。 您可能有一个有效的案例能够不从新抛出该异常,但该异常不必定意味着构建失败。 解决此问题的惟一方法是嵌套另外一个try块。 哇,编织的网真复杂。

Ok, let's look at the very simple way to solve this using smart pointers.

好的,让咱们看一下使用智能指针解决此问题的很是简单的方法。

 
class foo
{
public:
	foo() : pi1_(new int), pi2_(new int) {}

private:
	// These are reference counted (soon to be discussed below)
   smart_ptr<int> pi1_;
   smart_ptr<int> pi2_;
}

Wow, now that is simple. Each smart pointer is allocated a memory to manage and when the class goes out of scope each will delete that memory. If the construction fails any memory allocated will be correctly deleted and if the smart pointer was never constructed because the object being constructed before it throws an exception it'll never try and delete memory that it was never constructed to start with. Simple eh?

哇,这很简单。 每一个智能指针都分配有一个要管理的内存,而且当类超出范围时,每一个智能指针都将删除该内存。 若是构造失败,则将正确删除分配的全部内存,而且因为从未构造智能指针,由于构造的对象在抛出异常以前就不会构造智能指针,所以它将永远不会尝试删除从未构造过的内存。 简单吗?

What I'd like to do now is to introduce you to my very own hand crafted reference counted smart pointer. Now it's important to note that although the following code is a fully functional, it is really for educational purposes only. It has not been tested in a production environment (I wrote it just for this article) and if you do decide to heed the advice and use smart pointers please do refer to the Boost implementation as your first port of call.

我如今想作的是向您介绍我本身手工制做的参考计数智能指针。 如今,重要的是要注意,尽管如下代码具备完整的功能,但实际上仅用于教育目的。 它还没有在生产环境中进行过测试(我只是为本文编写),若是您决定遵从建议并使用智能指针,请务必将Boost实现做为您的第一个调用端口。

So, without further ado, here she is in all her majestic glory (*cough*)...

所以,事不宜迟,这就是她全部的庄严荣耀(*咳嗽*)...

 
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Basic smart pointer for scalar and array types
// Note: this class is not thread safe
// evilrix 2009
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// This functor will delete a pointer to a scalar object
template <typename ptrT>
struct delete_scalar_policy
 {
    void operator()(ptrT * & px) { delete px; px = 0; }
 };
 
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// This functor will delete a pointer to an array of object
template <typename ptrT>
struct delete_array_policy
 {
    void operator()(ptrT * & px) { delete[] px; px = 0; }
 };
 
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// This is the smart pointer template, which takes pointer type and
// a destruct policy, which it uses to destruct object(s) pointed to
// when the reference counter for the object becomes zero.
template <
    typename ptrT,
    template <
    typename
    > class delete_policy
 >
class smart_ptr
 {
private:
    typedef delete_policy<ptrT> delete_policy_;
    typedef void (smart_ptr::*safe_bool_t)();
    typedef int refcnt_t;
 
public:
    //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    // Make a nice typedef for the pointer type
    typedef ptrT ptr_type;
 
    //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    // dc_tor, c_tor and cc_tor
    smart_ptr() : px_(0), pn_(0) {}
 
    explicit smart_ptr(ptr_type * px) :
       px_(px), pn_(0) {
       pn_ = new int(1);
    }
 
    smart_ptr(smart_ptr const & o) :
       px_(o.px_), pn_(o.pn_) {
       ++*pn_;
    }
 
    //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    // d_tor, deletes the pointer using the destruct policy when the
    // reference counter for the object reaches zero
    ~smart_ptr()
    {
       try
       {
          if(pn_ && 0 == --*pn_)
          {
             delete_policy_()(px_);
             delete pn_;
          }
       }
       catch(...)
       {
          // Ignored. Prevent percolation during stack unwinding.
       }
    }
 
    //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    // Assignment operator copies an existing pointer smart_ptr, but
    // in doing so will 'reset' the current pointer
    smart_ptr & operator = (smart_ptr const & o)
    {
       if(&o != this && px_ != o.px_)
       {
          reset(o.px_);
          pn_ = o.pn_;
          ++*pn_;
       }
 
       return *this;
    }
 
    //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    // Performs a safe swap of two smart pointer.
    void swap(smart_ptr & o)
    {
       refcnt_t * pn = pn_;
       ptr_type * px = px_;
 
       pn_ = o.pn_;
       px_ = o.px_;
 
       o.pn_ = pn;
       o.px_ = px;
    }
 
    //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    // Resets the current smart pointer. If a new pointer is provided
    // The reference counter will be set to one and the pointer will
    // be stored, if no pointer is provided the reference counter and
    // pointer wil be set to 0, setting this as a null pointer.
    void reset(ptr_type * px = 0)
    {
       smart_ptr o(px);
       swap(o);
    }
 
    //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    // Returns a reference to the object pointed too
    ptr_type & operator * () const { return *px_; }
 
    //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    // Invokes the -> operator on the pointer pointed too
    // NB. When you call the -> operator, the compiler  automatically
    //     calls the -> on the entity returned. This is a special,
    //     case, done to preserve normal indirection semantics.
    ptr_type * operator -> () const { return px_; }
 
    //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    // Get the pointer being managed
    ptr_type * get() const { return px_; }
 
    //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    // Conversion to bool operator to facilitate logical pointer tests.
    // Returns a value that will logically be true if get != 0 else
    // and value that is logically false. We don't return a real
    // bool to prevent un-wanted automatic implicit conversion for
    // instances where it would make no semantic sense, rather we
    // return a pointer to a member function as this will always
    // implicitly convert to true or false when used in a boolean
    // context but will not convert, for example, to an int type.
    operator safe_bool_t () const { return px_ ? &smart_ptr::true_ : 0; }
 
private:
    //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    // A dummy member function used to represent a logically true
    // boolean value, used by the conversion to bool operator.
    void true_(){};
 
private:
    //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    // Poiners to the object being managed and the reference counter
    ptr_type * px_;
    refcnt_t * pn_;
 
    //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
 };
 
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Facility class to simplify the creation of a smart pointer that
// implements a 'delete scalar policy'.
template <typename ptrT>
struct scalar_smart_ptr
 {
    typedef smart_ptr<ptrT, delete_scalar_policy> type;
private: scalar_smart_ptr();
 };
 
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Facility class to simplify the creation of a smart pointer that
// implements a 'delete array policy'.
template <typename ptrT>
struct array_smart_ptr
 {
    typedef smart_ptr<ptrT, delete_array_policy> type;
private: array_smart_ptr();
 };
 
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

You'll see that I have fully documented the code with in-line comments so I won't be repeating myself here regarding the specifics of how she's implemented. Instead, let's take a look at her in action.

您会看到我已经用内联注释彻底记录了代码,所以在这里我将再也不重复她的实现方式。 相反,让咱们看一下她的动做。

 
#include <devtools/smart_ptr.hpp>

using namespace devtools;

int main()
{
	// This smart pointer will manage a pointer to a scaler
	scalar_smart_ptr<int>::type pi_scalar(new int);

	// This smart pointer will manage a pointer to an array
	array_smart_ptr<int>::type pi_array(new int[10]);
}

Pretty simple eh? In fact, it's as simple to use as auto_ptr except it solves both problems one and two discussed above, it can handle pointers to scalars and arrays and it can be copied as many times as you like without fear of the current pointer losing ownership.

很简单吧? 实际上,它与auto_ptr同样简单,除了能够解决上述一个和两个问题,它能够处理标量和数组的指针,而且能够根据须要复制任意屡次,而没必要担忧当前指针会失去全部权。

So, what have we learnt? Well, heap memory management isn't as simple in C++ as one would hope and that the tools provided by the (current) C++ standard are wholly inadequate. Trying to write code without using smart pointers if you are allocating heap memory is a recipe to disaster (or at the very least a debugging nightmare waiting to happen). Finally, we discovered there is a solution in the form of the reference counted smart pointer and although the current C++ standard has no such concept right now it is coming but, meanwhile, you can use the excellent ones provided by Boost or roll your own -- it's not really that complicated.

那么,咱们学到了什么? 嗯,C ++中的堆内存管理并不像人们但愿的那么简单,并且(当前)C ++标准提供的工具还远远不够。 若是您正在分配堆内存,则尝试在不使用智能指针的状况下编写代码会致使灾难(或者至少在等待调试恶梦的状况下发生)。 最终,咱们发现有一个以引用计数的智能指针形式存在的解决方案,尽管当前的C ++标准如今尚未这样的概念,可是您可使用Boost提供的出色的解决方案,也能够本身滚动- -并非那么复杂。

I hope you enjoyed reading this article and that it has convinced you to always use the Raii pattern and smart pointers in your code. if you liked this article please don't forget to vote "yes" for it.

我但愿您喜欢阅读本文,并说服了您始终在代码中使用Raii模式和智能指针。 若是您喜欢这篇文章,请不要忘记对其投同意票。

My thanks to:

个人感谢:

  • TruthHunter for identifying and reporting a small error in the destructor where  I wasn't checking pn_ was null before dereferencing it. Fixed.

    TruthHunter 用于在取消引用以前在我未检查pn_的析构函数中识别并报告一个小错误。 固定。
  • Rulsan Burakov for identifying a typo for one of the typedefs. Fixed.

    Rulsan Burakov用于识别其中一个typedef的错字。 固定。

翻译自: https://www.experts-exchange.com/articles/1959/C-Smart-pointers.html

c++智能指针