始终使用智能指针是一个好习惯吗?

发布于 2024-08-24 22:36:47 字数 273 浏览 5 评论 0原文

我发现智能指针比原始指针舒服得多。那么始终使用智能指针是个好主意吗? (请注意,我来自 Java 背景,因此不太喜欢显式内存管理的想法。因此,除非智能指针存在一些严重的性能问题,否则我想坚持使用它们。)

注意:虽然我来了有Java背景,我非常了解智能指针的实现和RAII的概念。因此,在发布答案时,您可以认为我的这些知识是理所当然的。我几乎在所有地方都使用静态分配,并且仅在必要时才使用指针。我的问题只是:我可以始终使用智能指针代替原始指针吗???

I find smart pointers to be a lot more comfortable than raw pointers. So is it a good idea to always use smart pointers? ( Please note that I am from Java background and hence don't much like the idea of explicit memory management. So unless there are some serious performance issues with smart pointers, I'd like to stick with them. )

Note: Though I come from Java background, I understand the implementation of smart pointers and the concepts of RAII quite well. So you can take this knowledge for granted from my side when posting an answer. I use static allocation almost everywhere and use pointers only when necessary. My question is merely: Can I always use smart pointers in place of raw pointers???

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

§对你不离不弃 2024-08-31 22:36:47

鉴于多次编辑,我的印象是全面的总结会很有用。

1.什么时候不应该

有两种情况不应该使用智能指针。

第一种情况是完全相同的,实际上您不应该使用 C++ 类。 IE:如果不向客户端提供源代码,则为 DLL 边界。说个轶事吧。

第二种情况更常见:聪明的经理意味着所有权。您可以使用指针来指向现有资源,而不管理它们的生命周期,例如:

void notowner(const std::string& name)
{
  Class* pointer(0);
  if (name == "cat")
    pointer = getCat();
  else if (name == "dog")
    pointer = getDog();

  if (pointer) doSomething(*pointer);
}

本示例受到限制。但指针在语义上与引用不同,因为它可能指向无效位置(空指针)。在这种情况下,最好不要使用智能指针来代替它,因为您不想管理对象的生命周期。

2.智能管理器

除非您正在编写智能管理器类,否则如果您使用关键字 delete 您做错了什么。

这是一个有争议的观点,但在审查了这么多有缺陷的代码示例之后,我不再冒险了。因此,如果您编写new,您需要一个智能管理器来管理新分配的内存。而你现在就需要它。

这并不意味着您不是一个程序员!相反,重用已被证明有效的代码而不是一遍又一遍地重新发明轮子是一项关键技能。

现在,真正的困难开始了:哪个聪明的经理?

3.智能指针

市面上有各种各样的智能指针,它们具有不同的特性。

跳过您通常应该避免的 std::auto_ptr (它的复制语义被搞砸了)。

  • scoped_ptr:无开销,无法复制或移动。
  • unique_ptr:无开销,无法复制,可以移动。
  • shared_ptr / weak_ptr:一些开销(引用计数),可以复制。

通常,尝试使用 scoped_ptrunique_ptr。如果您需要多个所有者,请尝试更改设计。如果您无法更改设计并且确实需要多个所有者,请使用 shared_ptr,但要注意应该在中间某处使用 weak_ptr 来打破引用循环。

4.智能容器

许多智能指针并不是要复制的,因此它们在 STL 容器中的使用会受到一定程度的影响。

不要诉诸 shared_ptr 及其开销,而是使用 Boost 指针容器。它们模拟经典 STL 容器的接口,但存储它们自己的指针。

5.推出自己的

智能管理器 在某些情况下,您可能希望推出自己的智能管理器。请检查您是否错过了您之前使用的库中的某些功能。

在存在异常的情况下编写一个智能管理器是相当困难的。您通常不能假设内存可用(new 可能会失败)或复制构造函数具有不抛出保证。

在某种程度上,忽略 std::bad_alloc 异常并强制许多帮助器的复制构造函数不会失败可能是可以接受的......毕竟,那就是boost::shared_ptr 对其删除器 D 模板参数执行的操作。

但我不推荐它,尤其是对于初学者。这是一个棘手的问题,您现在不太可能注意到这些错误。

6.示例

// For the sake of short code, avoid in real code ;)
using namespace boost;

// Example classes
//   Yes, clone returns a raw pointer...
// it puts the burden on the caller as for how to wrap it
//   It is to obey the `Cloneable` concept as described in 
// the Boost Pointer Container library linked above
struct Cloneable
{
  virtual ~Cloneable() {}
  virtual Cloneable* clone() const = 0;
};

struct Derived: Cloneable
{
  virtual Derived* clone() const { new Derived(*this); }
};

void scoped()
{
  scoped_ptr<Cloneable> c(new Derived);
} // memory freed here

// illustration of the moved semantics
unique_ptr<Cloneable> unique()
{
  return unique_ptr<Cloneable>(new Derived);
}

void shared()
{
  shared_ptr<Cloneable> n1(new Derived);
  weak_ptr<Cloneable> w = n1;

  {
    shared_ptr<Cloneable> n2 = n1;          // copy

    n1.reset();

    assert(n1.get() == 0);
    assert(n2.get() != 0);
    assert(!w.expired() && w.get() != 0);
  } // n2 goes out of scope, the memory is released

  assert(w.expired()); // no object any longer
}

void container()
{
  ptr_vector<Cloneable> vec;
  vec.push_back(new Derived);
  vec.push_back(new Derived);

  vec.push_back(
    vec.front().clone()         // Interesting semantic, it is dereferenced!
  );
} // when vec goes out of scope, it clears up everything ;)

Given the several edits, I have the impression that a comprehensive summary would be useful.

1. When not to

There are two situations where you should not use smart pointers.

The first is the exact same situation in which you should not use a C++ class in fact. IE: DLL boundary if you do not offer the source code to the client. Let say anecdotal.

The second happens much more often: smart manager means ownership. You may use pointers to point at existing resources without managing their lifetime, for example:

void notowner(const std::string& name)
{
  Class* pointer(0);
  if (name == "cat")
    pointer = getCat();
  else if (name == "dog")
    pointer = getDog();

  if (pointer) doSomething(*pointer);
}

This example is constrained. But a pointer is semantically different from a reference in that it may point to an invalid location (the null pointer). In this case, it's perfectly fine not to use a smart pointer in its stead, because you don't want to manage the lifetime of the object.

2. Smart managers

Unless you are writing a smart manager class, if you use the keyword delete you are doing something wrong.

It is a controversial point of view, but after having reviewed so many example of flawed code, I don't take chances any longer. So, if you write new you need a smart manager for the newly allocated memory. And you need it right now.

It does not mean you are less of a programmer! On the contrary, reusing code that has been proved to work instead of reinventing the wheel over and over is a key skill.

Now, the real difficulty start: which smart manager ?

3. Smart pointers

There are various smart pointers out of there, with various characteristics.

Skipping std::auto_ptr which you should generally avoid (its copy semantic is screwed).

  • scoped_ptr: no overhead, cannot be copied or moved.
  • unique_ptr: no overhead, cannot be copied, can be moved.
  • shared_ptr / weak_ptr: some overhead (reference counting), can be copied.

Usually, try to use either scoped_ptr or unique_ptr. If you need several owners try to change the design. If you can't change the design and really need several owners, use a shared_ptr, but beware of references cycles that ought to be broken using a weak_ptr somewhere in the midst.

4. Smart containers

Many smart pointers are not meant to be copied, therefore their use with the STL containers are somewhat compromised.

Instead of resorting to shared_ptr and its overhead, use smart containers from the Boost Pointer Container. They emulate the interface of classic STL containers but store pointers they own.

5. Rolling your own

There are situations when you may wish to roll your own smart manager. Do check that you did not just missed some feature in the libraries your are using beforehand.

Writing a smart manager in the presence of exceptions is quite difficult. You usually cannot assume that memory is available (new may fail) or that Copy Constructors have the no throw guarantee.

It may be acceptable, somewhat, to ignore the std::bad_alloc exception and impose that Copy Constructors of a number of helpers do not fail... after all, that's what boost::shared_ptr does for its deleter D template parameter.

But I would not recommend it, especially for a beginner. It's a tricky issue, and you're not likely to notice the bugs right now.

6. Examples

// For the sake of short code, avoid in real code ;)
using namespace boost;

// Example classes
//   Yes, clone returns a raw pointer...
// it puts the burden on the caller as for how to wrap it
//   It is to obey the `Cloneable` concept as described in 
// the Boost Pointer Container library linked above
struct Cloneable
{
  virtual ~Cloneable() {}
  virtual Cloneable* clone() const = 0;
};

struct Derived: Cloneable
{
  virtual Derived* clone() const { new Derived(*this); }
};

void scoped()
{
  scoped_ptr<Cloneable> c(new Derived);
} // memory freed here

// illustration of the moved semantics
unique_ptr<Cloneable> unique()
{
  return unique_ptr<Cloneable>(new Derived);
}

void shared()
{
  shared_ptr<Cloneable> n1(new Derived);
  weak_ptr<Cloneable> w = n1;

  {
    shared_ptr<Cloneable> n2 = n1;          // copy

    n1.reset();

    assert(n1.get() == 0);
    assert(n2.get() != 0);
    assert(!w.expired() && w.get() != 0);
  } // n2 goes out of scope, the memory is released

  assert(w.expired()); // no object any longer
}

void container()
{
  ptr_vector<Cloneable> vec;
  vec.push_back(new Derived);
  vec.push_back(new Derived);

  vec.push_back(
    vec.front().clone()         // Interesting semantic, it is dereferenced!
  );
} // when vec goes out of scope, it clears up everything ;)
天荒地未老 2024-08-31 22:36:47

智能指针确实执行显式内存管理,如果您不了解它们是如何做到这一点的,那么在使用 C++ 编程时您就会遇到麻烦。请记住,内存并不是他们管理的唯一资源。

但要回答你的问题,你应该更喜欢智能指针作为解决方案的第一个近似值,但可能会在必要时准备放弃它们。如果可以避免,则永远不应该使用指针(或任何类型)或动态分配。例如:

string * s1 = new string( "foo" );      // bad
string s2( "bar" );    // good

编辑:要回答您的补充问题“我可以始终使用智能指针代替原始指针吗???那么,不,您不能。如果(例如)您需要实现您的自己版本的operator new,你必须让它返回一个指针,而不是一个智能指针。

Smart pointers do perform explicit memory management, and if you don't understand how they are doing it, you are in for a world of trouble when programming with C++. And remember that memory isn't the only resource that they manage.

But to answer your question you should prefer smart-pointers as a first approximation to a solution, but possibly be prepared to ditch them when necessary. You should never use pointers (or any sort) or dynamic allocation when it can be avoided. For example:

string * s1 = new string( "foo" );      // bad
string s2( "bar" );    // good

Edit: To answer your suplementary question "Can I always use smart pointers in place of raw pointers??? Then, no you can't. If (for example) you need to implement your own version of operator new, you would have to make it return a pointer, not a smart pointer.

九八野马 2024-08-31 22:36:47

通常,如果不需要指针(智能指针或其他指针),则不应使用它们。最好使局部变量、类成员、向量元素和类似项成为普通对象,而不是指向对象的指针。 (由于您来自 Java,因此您可能会尝试使用 new 分配所有内容,但不建议这样做。)

这种方法(“RAII") 可以让您在大多数时候不用担心指针。

当你必须使用指针时,这取决于具体情况以及为什么你需要指针,但通常可以使用智能指针。它可能不是总是(粗体)是最好的选择,但这取决于具体情况。

Usually you shouldn't use pointers (smart or otherwise) if you don't need them. Better make local variables, class members, vector elements and similar items normal objects instead of pointers to objects. (Since you come from Java you're probably tempted allocate everything with new, which is not recommended.)

This approach ("RAII") saves you from worrying about pointers most of the time.

When you have to use pointers it depends on the situation and why exactly you need pointers, but usually smart pointers can be used. It might not be always (in bold) be the best option, but this depends on the specific situation.

尝蛊 2024-08-31 22:36:47

不使用智能指针的好时机是在 DLL 的接口边界处。您不知道是否会使用相同的编译器/库构建其他可执行文件。您的系统的 DLL 调用约定不会指定标准或 TR1 类的外观,包括智能指针。

在可执行文件或库中,如果您想表示指针对象的所有权,那么智能指针平均来说是最好的方法。因此,最好总是优先使用它们而不是原始的。您是否真的可以始终使用它们是另一回事。

举一个具体的例子,假设您正在编写一个通用图的表示,其中顶点由对象表示,边由对象之间的指针表示。通常的智能指针不会帮助你:图可以是循环的,并且没有特定的节点可以负责其他节点的内存管理,因此共享指针和弱指针是不够的。例如,您可以将所有内容放入向量中并使用索引而不是指针,或者将所有内容放入双端队列中并使用原始指针。如果需要,您可以使用 shared_ptr,但除了开销之外它不会增加任何内容。或者你可以寻找标记-清除GC。

更边缘的情况:我更喜欢看到函数通过指针或引用获取参数,并承诺不保留对其的指针或引用,而不是采用 shared_ptr 和让您想知道它们返回后是否保留引用,也许如果您再次修改引用对象,您会破坏某些东西,等等。不保留引用通常没有明确记录,这是不言而喻的。也许不应该,但确实如此。智能指针暗示了一些有关所有权的信息,而错误地暗示这一点可能会令人困惑。因此,如果您的函数采用 shared_ptr,请务必记录它是否可以保留引用。

A good time not to use smart pointers, is at the interface boundary of a DLL. You don't know whether other executables will be built with the same compiler/libraries. Your system's DLL calling convention won't specify what standard or TR1 classes look like, smart pointers included.

Within an executable or library, if you want to represent ownership of the pointee, then smart pointers are on average the best way to do it. So it's fine to want to always use them in preference to raw. Whether you actually can always use them is another matter.

For a concrete example when not to - suppose you are writing a representation of a generic graph, with vertices represented by objects and edges represented by pointers between the objects. The usual smart pointers will not help you: graphs can be cyclic, and no particular node can be held responsible for the memory management of other nodes, so shared and weak pointers are insufficient. You might for example put everything in a vector and use indices instead of pointers, or put everything in a deque and use raw pointers. You could use shared_ptr if you wanted, but it won't add anything except overhead. Or you could look for mark-sweep GC.

A more marginal case: I prefer to see functions take a parameter by pointer or reference, and promise not to retain a pointer or reference to it, rather than take a shared_ptr and leave you wondering whether maybe they retain a reference after they return, maybe if you modify the referand ever again you'll break something, etc. Not retaining references is something that often isn't documented explicitly, it just goes without saying. Maybe it shouldn't, but it does. Smart pointers imply something about ownership, and falsely implying that can be confusing. So if your function takes a shared_ptr, be sure to document whether it can retain a reference or not.

少女的英雄梦 2024-08-31 22:36:47

在许多情况下,我相信它们绝对是正确的选择(更少混乱的清理代码,降低泄漏风险等)。然而,有一些非常轻微的额外费用。如果我正在编写一些必须尽可能快的代码(例如必须进行一些分配和释放的紧密循环),我可能不会使用智能指针来希望提高一点速度。但我怀疑在大多数情况下这不会产生任何可衡量的差异。

In many situations, I believe they are definitely the way to go (less messy cleanup code, reduced risk of leaks, etc.). However there is some very slight extra expense. If I were writing some code that had to be as fast as possible (say a tight loop that had to do some allocation and a free), I would probably not use a smart pointer in hopes of eking out a bit more speed. But I doubt that it would make any measurable difference in most situations.

长不大的小祸害 2024-08-31 22:36:47

一般来说,不,你不能总是使用智能指针。例如,当您使用其他不使用智能指针的框架(如 Qt)时,您也必须使用原始指针。

In general, no you cannot use smart pointers always. For example, when you use other frameworks that don't use smart pointer (like Qt), you have to use raw pointers too.

傲世九天 2024-08-31 22:36:47

如果您正在处理资源,则应始终使用 RAII 技术,在内存情况下意味着使用某种形式的智能指针(注意:智能指针,而不是 shared_ptr,选择最适合您的特定用例)。这是在出现异常时避免泄漏的唯一方法。

当资源管理不是通过指针处理时,仍然存在需要原始指针的情况。特别是,它们是获得可重置参考的唯一方法。考虑保留对生命周期无法显式处理的对象(成员属性、堆栈中的对象)的引用。但这是一个非常具体的情况,我只在实际代码中见过一次。在大多数情况下,使用 shared_ptr 是共享对象的更好方法。

If you are handling a resource, you should always use RAII techniques, with in the case of memory means using some form or another of a smart pointer (note: smart, not shared_ptr, choose the smart pointer that is most appropriate for your specific use case). It is the only way to avoid leaks in the presence of exceptions.

There are still cases where raw pointers are necessary, when resource management is not handled through the pointer. In particular they are the only way of having a resettable reference. Think of keeping a reference into an object whose lifetime cannot be explicitly handled (member attribute, object in the stack). But that is a very specific case that I have only seen once in real code. In most cases, using a shared_ptr is a better approach to sharing an object.

囚我心虐我身 2024-08-31 22:36:47

我对智能指针的看法:当很难知道何时会发生释放时(比如在 try/catch 块内,或者在调用函数(甚至是构造函数!)的函数内,这可能会让您退出当前函数),这非常有用,或者为代码中到处都有 return 的函数添加更好的内存管理。或者将指针放入容器中。

然而,智能指针的成本可能是您不想在整个程序中付出的。如果内存管理很容易手动完成(“嗯,我知道当这个函数结束时我需要删除这三个指针,并且我知道这个函数将运行完成”),那么为什么要浪费计算机执行的周期它?

My take on smart pointers: GREAT when it's hard to know when deallocation could happen (say inside an try/catch block, or inside a function that calls a function (or even a constructor!) that could throw you out of your current function), or adding better memory management to a function that has returns everywhere in the code. Or putting pointers in containers.

Smart pointers, however, have a cost that you might not want to pay all over your program. If memory management is easy to do by hand ("Hmm, I know that when this function ends I need to delete these three pointers, and I know that this function will run to completion"), then why waste the cycles having the computer do it?

怀念你的温柔 2024-08-31 22:36:47

是的,但是我已经完成了几个没有使用智能指针或任何指针的项目。最好的做法是使用双端队列、列表、映射等容器。或者,如果可能的话,我会使用引用。我不是传递指针,而是传递引用或常量引用,删除/释放引用几乎总是不合逻辑的,所以我从来没有在那里遇到问题(通常我通过编写 { Class class; func(class ,参考2,参考3);

Yes BUT i have gone several projects without the use of a smart pointer or any pointers. Its good practice to use containers such as deque, list, map etc. Alternatively i use references when possible. Instead of passing in a pointer i pass a reference or const reference and its almost always illogical to delete/free a reference so i never have issue there (typically i create them on the stack by writing { Class class; func(class, ref2, ref3); }

來不及說愛妳 2024-08-31 22:36:47

这是。智能指针是老Cocoa(Touch)生态系统的基石之一。我相信它会不断影响新事物。

It is. Smart pointer is one of the cornerstones of the old Cocoa (Touch) ecosystem. I believe it keeps impacting the new.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文