在C ++中实现智能指针的最佳方法是什么?

时间:2022-09-02 11:16:30

I've been evaluating various smart pointer implementations (wow, there are a LOT out there) and it seems to me that most of them can be categorized into two broad classifications:

我一直在评估各种智能指针实现(哇,有很多),在我看来,大多数可以分为两大类:

1) This category uses inheritance on the objects referenced so that they have reference counts and usually up() and down() (or their equivalents) implemented. IE, to use the smart pointer, the objects you're pointing at must inherit from some class the ref implementation provides.

1)此类别对引用的对象使用继承,以便它们具有引用计数,并且通常实现up()和down()(或它们的等价物)。 IE,要使用智能指针,您指向的对象必须从ref实现提供的某些类继承。

2) This category uses a secondary object to hold the reference counts. For example, instead of pointing the smart pointer right at an object, it actually points at this meta data object... Who has a reference count and up() and down() implementations (and who usually provides a mechanism for the pointer to get at the actual object being pointed to, so that the smart pointer can properly implement operator ->()).

2)此类别使用辅助对象来保存引用计数。例如,不是将智能指针指向对象,而是实际指向此元数据对象...谁具有引用计数和up()和down()实现(并且通常为指针提供机制)获取指向的实际对象,以便智能指针可以正确实现operator - >())。

Now, 1 has the downside that it forces all of the objects you'd like to reference count to inherit from a common ancestor, and this means that you cannot use this to reference count objects that you don't have control over the source code to.

现在,1有一个缺点,它强制你想引用的所有对象计数从一个共同的祖先继承,这意味着你不能使用它来引用你无法控制源代码的计数对象至。

2 has the problem that since the count is stored in another object, if you ever have a situation that a pointer to an existing reference counted object is being converted into a reference, you probably have a bug (I.E., since the count is not in the actual object, there is no way for the new reference to get the count... ref to ref copy construction or assignment is fine, because they can share the count object, but if you ever have to convert from a pointer, you're totally hosed)...

2有一个问题,因为计数存储在另一个对象中,如果你有一个情况,指向现有引用计数对象的指针正被转换为引用,你可能有一个错误(IE,因为计数不在实际的对象,没有办法让新引用得到计数...引用ref复制构造或赋值是好的,因为它们可以共享count对象,但如果你必须从指针转换,你'完全被冲洗了)...

Now, as I understand it, boost::shared_pointer uses mechanism 2, or something like it... That said, I can't quite make up my mind which is worse! I have only ever used mechanism 1, in production code... Does anyone have experience with both styles? Or perhaps there is another way thats better than both of these?

现在,正如我所理解的那样,boost :: shared_pointer使用机制2,或类似的东西......那就是说,我不能完全决定哪个更糟糕!我只使用机制1,在生产代码中......有没有人有这两种风格的经验?或许还有另一种方法比这两种方式更好?

9 个解决方案

#1


"What is the best way to implement smart pointers in C++"

“在C ++中实现智能指针的最佳方法是什么?”

  1. Don't! Use an existing, well tested smart pointer, such as boost::shared_ptr or std::tr1::shared_ptr (std::unique_ptr and std::shared_ptr with C++ 11)
  2. 别!使用现有的,经过良好测试的智能指针,例如boost :: shared_ptr或std :: tr1 :: shared_ptr(std :: unique_ptr和std :: shared_ptr与C ++ 11)

  3. If you have to, then remember to:
    1. use safe-bool idiom
    2. 使用safe-bool成语

    3. provide an operator->
    4. 提供运营商 - >

    5. provide the strong exception guarantee
    6. 提供强有力的例外保证

    7. document the exception requirements your class makes on the deleter
    8. 记录您的类对删除器的异常要求

    9. use copy-modify-swap where possible to implement the strong exception guarantee
    10. 尽可能使用copy-modify-swap来实现强大的异常保证

    11. document whether you handle multithreading correctly
    12. 记录您是否正确处理多线程

    13. write extensive unit tests
    14. 写广泛的单元测试

    15. implement conversion-to-base in such a way that it will delete on the derived pointer type (policied smart pointers / dynamic deleter smart pointers)
    16. 以这样的方式实现转换为base,它将在派生的指针类型上删除(policied smart pointers / dynamic deleter smart pointers)

    17. support getting access to raw pointer
    18. 支持访问原始指针

    19. consider cost/benifit of providing weak pointers to break cycles
    20. 考虑提供破坏周期的弱指针的成本/利益

    21. provide appropriate casting operators for your smart pointers
    22. 为您的智能指针提供合适的铸造操作符

    23. make your constructor templated to handle constructing base pointer from derived.
    24. 使您的构造函数模板化以处理从派生构造基指针。

  4. 如果必须,请记住:使用safe-bool idiom提供运算符 - >提供强大的异常保证文档,您的类对删除器使用copy-modify-swap的异常要求,尽可能实现强大的异常保证文件你处理多线程正确编写广泛的单元测试实现转换为base这样一种方式,它将删除派生指针类型(policied智能指针/动态删除智能指针)支持获取原始指针考虑成本/利益提供弱指向break周期的指针为你的智能指针提供了适当的转换操作符,使你的构造函数模板化来处理从derived构造的基指针。

And don't forget anything I may have forgotten in the above incomplete list.

不要忘记我在上面不完整的清单中遗忘的任何事情。

#2


Just to supply a different view to the ubiquitous Boost answer (even though it is the right answer for many uses), take a look at Loki's implementation of smart pointers. For a discourse on the design philosophy, the original creator of Loki wrote the book Modern C++ Design.

只是为无处不在的Boost答案提供不同的视图(即使它是许多用途的正确答案),看看Loki的智能指针实现。对于设计哲学的讨论,Loki的原始创作者编写了“现代C ++设计”一书。

#3


I've been using boost::shared_ptr for several years now and while you are right about the downside (no assignment via pointer possible), I think it was definitely worth it because of the huge amount of pointer-related bugs it saved me from.

我已经使用boost :: shared_ptr好几年了,虽然你对下行没有正确(没有可能通过指针进行分配),但我认为这绝对是值得的,因为它节省了大量指针相关的错误。

In my homebrew game engine I've replaced normal pointers with shared_ptr as much as possible. The performance hit this causes is actually not so bad if you are calling most functions by reference so that the compiler does not have to create too many temporary shared_ptr instances.

在我的自制游戏引擎中,我尽可能地用shared_ptr替换了普通指针。如果您通过引用调用大多数函数,那么这种性能下降实际上并没有那么糟糕,因此编译器不必创建太多临时的shared_ptr实例。

#4


Boost also has an intrusive pointer (like solution 1), that doesn't require inheriting from anything. It does require changing the pointer to class to store the reference count and provide appropriate member functions. I've used this in cases where memory efficiency was important, and didn't want the overhead of another object for each shared pointer used.

Boost还有一个侵入式指针(如解决方案1),不需要继承任何东西。它确实需要将指针更改为类来存储引用计数并提供适当的成员函数。我已经在内存效率很重要的情况下使用它,并且不希望使用每个共享指针的另一个对象的开销。

Example:

class Event {
public:
typedef boost::intrusive_ptr<Event> Ptr;
void addRef();
unsigned release();
\\ ...
private:
unsigned fRefCount;
};

inline void Event::addRef()
{
  fRefCount++;
}
inline unsigned Event::release(){
    fRefCount--;
    return fRefCount;
}

inline void intrusive_ptr_add_ref(Event* e)
{
  e->addRef();
}

inline void intrusive_ptr_release(Event* e)
{
  if (e->release() == 0)
  delete e;
}

The Ptr typedef is used so that I can easily switcth between boost::shared_ptr<> and boost::intrusive_ptr<> without changing any client code

使用Ptr typedef,以便我可以轻松地在boost :: shared_ptr <>和boost :: intrusive_ptr <>之间切换,而无需更改任何客户端代码

#5


If you stick with the ones that are in the standard library you will be fine.
Though there are a few other types than the ones you specified.

如果你坚持标准库中的那些你会没事的。虽然除了您指定的类型之外还有其他几种类型。

  • Shared: Where the ownership is shared between multiple objects
  • 共享:在多个对象之间共享所有权的位置

  • Owned: Where one object owns the object but transfer is allowed.
  • 拥有:一个对象拥有该对象但允许传输。

  • Unmovable: Where one object owns the object and it can not be transferred.
  • 不可移动:一个对象拥有该对象且无法传输的对象。

The standard library has:

标准库有:

  • std::auto_ptr

Boost has a couple more than have been adapted by tr1 (next version of the standard)

Boost有一些比tr1更适应(标准的下一个版本)

  • std::tr1::shared_ptr
  • std::tr1::weak_ptr

And those still in boost (which in relatively is a must have anyway) that hopefully make it into tr2.

而那些仍然处于提升状态(无论如何相对而言必须具备这种状态),希望能够成为tr2。

  • boost::scoped_ptr
  • boost::scoped_array
  • boost::shared_array
  • boost::intrusive_ptr

See: Smart Pointers: Or who owns you baby?

请参阅:智能指针:或者谁拥有宝贝?

#6


It seems to me this question is kind of like asking "Which is the best sort algorithm?" There is no one answer, it depends on your circumstances.

在我看来,这个问题有点像问“哪种是最好的排序算法?”没有一个答案,这取决于你的情况。

For my own purposes, I'm using your type 1. I don't have access to the TR1 library. I do have complete control over all the classes I need to have shared pointers to. The additional memory and time efficiency of type 1 might be pretty slight, but memory usage and speed are big issues for my code, so type 1 was a slam dunk.

出于我自己的目的,我使用的是类型1.我无法访问TR1库。我确实可以完全控制所有需要共享指针的类。类型1的额外内存和时间效率可能相当轻微,但内存使用和速度是我的代码的大问题,因此类型1是一个扣篮。

On the other hand, for anyone who can use TR1, I'd think the type 2 std::tr1::shared_ptr class would be a sensible default choice, to be used whenever there isn't some pressing reason not to use it.

另一方面,对于任何可以使用TR1的人来说,我认为类型2 std :: tr1 :: shared_ptr类是一个合理的默认选择,只要没有一些紧迫的理由不使用它就可以使用。

#7


The problem with 2 can be worked around. Boost offers boost::shared_from_this for this same reason. In practice, it's not a big problem.

2的问题可以解决。由于同样的原因,Boost提供了boost :: shared_from_this。在实践中,这不是一个大问题。

But the reason they went with your option #2 is that it can be used in all cases. Relying on inheritance isn't always an option, and then you're left with a smart pointer you can't use for half your code.

但他们选择#2的原因是它可以在所有情况下使用。依赖继承并不总是一个选项,然后你留下一个智能指针,你不能使用一半的代码。

I'd have to say #2 is best, simply because it can be used in any circumstances.

我不得不说#2是最好的,因为它可以在任何情况下使用。

#8


Our project uses smart pointers extensively. In the beginning there was uncertainty about which pointer to use, and so one of the main authors chose an intrusive pointer in his module and the other a non-intrusive version.

我们的项目广泛使用智能指针。在开始时,关于使用哪个指针存在不确定性,因此其中一位主要作者在他的模块中选择了一个侵入式指针而另一个是非侵入式版本。

In general, the differences between the two pointer types were not significant. The only exception being that early versions of our non-intrusive pointer implicitly converted from a raw pointer and this can easily lead to memory problems if the pointers are used incorrectly:

通常,两种指针类型之间的差异并不显着。唯一的例外是我们的非侵入式指针的早期版本从原始指针隐式转换,如果指针使用不正确,这很容易导致内存问题:

void doSomething (NIPtr<int> const &);

void foo () {
  NIPtr<int> i = new int;
  int & j = *i;
  doSomething (&j);          // Ooops - owned by two pointers! :(
}

A while ago, some refactoring resulted in some parts of the code being merged, and so a choice had to be made about which pointer type to use. The non-intrusive pointer now had the converting constructor declared as explicit and so it was decided to go with the intrusive pointer to save on the amount of code change that was required.

不久前,一些重构导致代码的某些部分被合并,因此必须选择使用哪种指针类型。非侵入式指针现在将转换构造函数声明为显式,因此决定使用侵入式指针来节省所需的代码更改量。

To our great surprise one thing we did notice was that we had an immediate performance improvement by using the intrusive pointer. We did not put much research into this, and just assumed that the difference was the cost of maintaining the count object. It is possible that other implementations of non-intrusive shared pointer have solved this problem by now.

令我们惊讶的是,我们注意到的一件事是我们通过使用侵入式指针立即改善了性能。我们没有对此进行太多研究,只是假设差异是维护计数对象的成本。到目前为止,非侵入式共享指针的其他实现可能已经解决了这个问题。

#9


What you are talking about are intrusive and non-intrusive smart pointers. Boost has both. boost::intrusive_ptr calls a function to decrease and increase the reference count of your object, everytime it needs to change the reference count. It's not calling member functions, but free functions. So it allows managing objects without the need to change the definition of their types. And as you say, boost::shared_ptr is non-intrusive, your category 2.

你所谈论的是侵入性和非侵入性的智能指针。 Boost有两个。每次需要更改引用计数时,boost :: intrusive_ptr都会调用一个函数来减少和增加对象的引用计数。它不是调用成员函数,而是调用*函数。因此,它允许管理对象,而无需更改其类型的定义。正如你所说,boost :: shared_ptr是非侵入性的,你的类别2。

I have an answer explaining intrusive_ptr: Making shared_ptr not use delete. In short, you use it if you have an object that has already reference counting, or need (as you explain) an object that is already referenced to be owned by an intrusive_ptr.

我有一个解释intrusive_ptr的答案:使shared_ptr不使用删除。简而言之,如果您有一个已经引用计数的对象,或者需要(如您所解释)已被引用为intrusive_ptr所拥有的对象,则使用它。

#1


"What is the best way to implement smart pointers in C++"

“在C ++中实现智能指针的最佳方法是什么?”

  1. Don't! Use an existing, well tested smart pointer, such as boost::shared_ptr or std::tr1::shared_ptr (std::unique_ptr and std::shared_ptr with C++ 11)
  2. 别!使用现有的,经过良好测试的智能指针,例如boost :: shared_ptr或std :: tr1 :: shared_ptr(std :: unique_ptr和std :: shared_ptr与C ++ 11)

  3. If you have to, then remember to:
    1. use safe-bool idiom
    2. 使用safe-bool成语

    3. provide an operator->
    4. 提供运营商 - >

    5. provide the strong exception guarantee
    6. 提供强有力的例外保证

    7. document the exception requirements your class makes on the deleter
    8. 记录您的类对删除器的异常要求

    9. use copy-modify-swap where possible to implement the strong exception guarantee
    10. 尽可能使用copy-modify-swap来实现强大的异常保证

    11. document whether you handle multithreading correctly
    12. 记录您是否正确处理多线程

    13. write extensive unit tests
    14. 写广泛的单元测试

    15. implement conversion-to-base in such a way that it will delete on the derived pointer type (policied smart pointers / dynamic deleter smart pointers)
    16. 以这样的方式实现转换为base,它将在派生的指针类型上删除(policied smart pointers / dynamic deleter smart pointers)

    17. support getting access to raw pointer
    18. 支持访问原始指针

    19. consider cost/benifit of providing weak pointers to break cycles
    20. 考虑提供破坏周期的弱指针的成本/利益

    21. provide appropriate casting operators for your smart pointers
    22. 为您的智能指针提供合适的铸造操作符

    23. make your constructor templated to handle constructing base pointer from derived.
    24. 使您的构造函数模板化以处理从派生构造基指针。

  4. 如果必须,请记住:使用safe-bool idiom提供运算符 - >提供强大的异常保证文档,您的类对删除器使用copy-modify-swap的异常要求,尽可能实现强大的异常保证文件你处理多线程正确编写广泛的单元测试实现转换为base这样一种方式,它将删除派生指针类型(policied智能指针/动态删除智能指针)支持获取原始指针考虑成本/利益提供弱指向break周期的指针为你的智能指针提供了适当的转换操作符,使你的构造函数模板化来处理从derived构造的基指针。

And don't forget anything I may have forgotten in the above incomplete list.

不要忘记我在上面不完整的清单中遗忘的任何事情。

#2


Just to supply a different view to the ubiquitous Boost answer (even though it is the right answer for many uses), take a look at Loki's implementation of smart pointers. For a discourse on the design philosophy, the original creator of Loki wrote the book Modern C++ Design.

只是为无处不在的Boost答案提供不同的视图(即使它是许多用途的正确答案),看看Loki的智能指针实现。对于设计哲学的讨论,Loki的原始创作者编写了“现代C ++设计”一书。

#3


I've been using boost::shared_ptr for several years now and while you are right about the downside (no assignment via pointer possible), I think it was definitely worth it because of the huge amount of pointer-related bugs it saved me from.

我已经使用boost :: shared_ptr好几年了,虽然你对下行没有正确(没有可能通过指针进行分配),但我认为这绝对是值得的,因为它节省了大量指针相关的错误。

In my homebrew game engine I've replaced normal pointers with shared_ptr as much as possible. The performance hit this causes is actually not so bad if you are calling most functions by reference so that the compiler does not have to create too many temporary shared_ptr instances.

在我的自制游戏引擎中,我尽可能地用shared_ptr替换了普通指针。如果您通过引用调用大多数函数,那么这种性能下降实际上并没有那么糟糕,因此编译器不必创建太多临时的shared_ptr实例。

#4


Boost also has an intrusive pointer (like solution 1), that doesn't require inheriting from anything. It does require changing the pointer to class to store the reference count and provide appropriate member functions. I've used this in cases where memory efficiency was important, and didn't want the overhead of another object for each shared pointer used.

Boost还有一个侵入式指针(如解决方案1),不需要继承任何东西。它确实需要将指针更改为类来存储引用计数并提供适当的成员函数。我已经在内存效率很重要的情况下使用它,并且不希望使用每个共享指针的另一个对象的开销。

Example:

class Event {
public:
typedef boost::intrusive_ptr<Event> Ptr;
void addRef();
unsigned release();
\\ ...
private:
unsigned fRefCount;
};

inline void Event::addRef()
{
  fRefCount++;
}
inline unsigned Event::release(){
    fRefCount--;
    return fRefCount;
}

inline void intrusive_ptr_add_ref(Event* e)
{
  e->addRef();
}

inline void intrusive_ptr_release(Event* e)
{
  if (e->release() == 0)
  delete e;
}

The Ptr typedef is used so that I can easily switcth between boost::shared_ptr<> and boost::intrusive_ptr<> without changing any client code

使用Ptr typedef,以便我可以轻松地在boost :: shared_ptr <>和boost :: intrusive_ptr <>之间切换,而无需更改任何客户端代码

#5


If you stick with the ones that are in the standard library you will be fine.
Though there are a few other types than the ones you specified.

如果你坚持标准库中的那些你会没事的。虽然除了您指定的类型之外还有其他几种类型。

  • Shared: Where the ownership is shared between multiple objects
  • 共享:在多个对象之间共享所有权的位置

  • Owned: Where one object owns the object but transfer is allowed.
  • 拥有:一个对象拥有该对象但允许传输。

  • Unmovable: Where one object owns the object and it can not be transferred.
  • 不可移动:一个对象拥有该对象且无法传输的对象。

The standard library has:

标准库有:

  • std::auto_ptr

Boost has a couple more than have been adapted by tr1 (next version of the standard)

Boost有一些比tr1更适应(标准的下一个版本)

  • std::tr1::shared_ptr
  • std::tr1::weak_ptr

And those still in boost (which in relatively is a must have anyway) that hopefully make it into tr2.

而那些仍然处于提升状态(无论如何相对而言必须具备这种状态),希望能够成为tr2。

  • boost::scoped_ptr
  • boost::scoped_array
  • boost::shared_array
  • boost::intrusive_ptr

See: Smart Pointers: Or who owns you baby?

请参阅:智能指针:或者谁拥有宝贝?

#6


It seems to me this question is kind of like asking "Which is the best sort algorithm?" There is no one answer, it depends on your circumstances.

在我看来,这个问题有点像问“哪种是最好的排序算法?”没有一个答案,这取决于你的情况。

For my own purposes, I'm using your type 1. I don't have access to the TR1 library. I do have complete control over all the classes I need to have shared pointers to. The additional memory and time efficiency of type 1 might be pretty slight, but memory usage and speed are big issues for my code, so type 1 was a slam dunk.

出于我自己的目的,我使用的是类型1.我无法访问TR1库。我确实可以完全控制所有需要共享指针的类。类型1的额外内存和时间效率可能相当轻微,但内存使用和速度是我的代码的大问题,因此类型1是一个扣篮。

On the other hand, for anyone who can use TR1, I'd think the type 2 std::tr1::shared_ptr class would be a sensible default choice, to be used whenever there isn't some pressing reason not to use it.

另一方面,对于任何可以使用TR1的人来说,我认为类型2 std :: tr1 :: shared_ptr类是一个合理的默认选择,只要没有一些紧迫的理由不使用它就可以使用。

#7


The problem with 2 can be worked around. Boost offers boost::shared_from_this for this same reason. In practice, it's not a big problem.

2的问题可以解决。由于同样的原因,Boost提供了boost :: shared_from_this。在实践中,这不是一个大问题。

But the reason they went with your option #2 is that it can be used in all cases. Relying on inheritance isn't always an option, and then you're left with a smart pointer you can't use for half your code.

但他们选择#2的原因是它可以在所有情况下使用。依赖继承并不总是一个选项,然后你留下一个智能指针,你不能使用一半的代码。

I'd have to say #2 is best, simply because it can be used in any circumstances.

我不得不说#2是最好的,因为它可以在任何情况下使用。

#8


Our project uses smart pointers extensively. In the beginning there was uncertainty about which pointer to use, and so one of the main authors chose an intrusive pointer in his module and the other a non-intrusive version.

我们的项目广泛使用智能指针。在开始时,关于使用哪个指针存在不确定性,因此其中一位主要作者在他的模块中选择了一个侵入式指针而另一个是非侵入式版本。

In general, the differences between the two pointer types were not significant. The only exception being that early versions of our non-intrusive pointer implicitly converted from a raw pointer and this can easily lead to memory problems if the pointers are used incorrectly:

通常,两种指针类型之间的差异并不显着。唯一的例外是我们的非侵入式指针的早期版本从原始指针隐式转换,如果指针使用不正确,这很容易导致内存问题:

void doSomething (NIPtr<int> const &);

void foo () {
  NIPtr<int> i = new int;
  int & j = *i;
  doSomething (&j);          // Ooops - owned by two pointers! :(
}

A while ago, some refactoring resulted in some parts of the code being merged, and so a choice had to be made about which pointer type to use. The non-intrusive pointer now had the converting constructor declared as explicit and so it was decided to go with the intrusive pointer to save on the amount of code change that was required.

不久前,一些重构导致代码的某些部分被合并,因此必须选择使用哪种指针类型。非侵入式指针现在将转换构造函数声明为显式,因此决定使用侵入式指针来节省所需的代码更改量。

To our great surprise one thing we did notice was that we had an immediate performance improvement by using the intrusive pointer. We did not put much research into this, and just assumed that the difference was the cost of maintaining the count object. It is possible that other implementations of non-intrusive shared pointer have solved this problem by now.

令我们惊讶的是,我们注意到的一件事是我们通过使用侵入式指针立即改善了性能。我们没有对此进行太多研究,只是假设差异是维护计数对象的成本。到目前为止,非侵入式共享指针的其他实现可能已经解决了这个问题。

#9


What you are talking about are intrusive and non-intrusive smart pointers. Boost has both. boost::intrusive_ptr calls a function to decrease and increase the reference count of your object, everytime it needs to change the reference count. It's not calling member functions, but free functions. So it allows managing objects without the need to change the definition of their types. And as you say, boost::shared_ptr is non-intrusive, your category 2.

你所谈论的是侵入性和非侵入性的智能指针。 Boost有两个。每次需要更改引用计数时,boost :: intrusive_ptr都会调用一个函数来减少和增加对象的引用计数。它不是调用成员函数,而是调用*函数。因此,它允许管理对象,而无需更改其类型的定义。正如你所说,boost :: shared_ptr是非侵入性的,你的类别2。

I have an answer explaining intrusive_ptr: Making shared_ptr not use delete. In short, you use it if you have an object that has already reference counting, or need (as you explain) an object that is already referenced to be owned by an intrusive_ptr.

我有一个解释intrusive_ptr的答案:使shared_ptr不使用删除。简而言之,如果您有一个已经引用计数的对象,或者需要(如您所解释)已被引用为intrusive_ptr所拥有的对象,则使用它。