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

Don*_*ris 74 c++

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

注意:虽然我来自Java背景,但我非常了解智能指针的实现和RAII的概念.因此,在发布答案时,您可以将这些知识视为我的理所当然.我几乎到处都使用静态分配,只在必要时才使用指针.我的问题仅仅是:我是否可以使用智能指针代替原始指针?

Mat*_* M. 72

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

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);
}
Run Code Online (Sandbox Code Playgroud)

这个例子受到限制.但是指针在语义上与引用的不同之处在于它可能指向无效位置(空指针).在这种情况下,完全没有使用智能指针,因为您不想管理对象的生命周期.

2.聪明的经理

除非您正在编写智能管理器类,否则如果您使用该关键字,则表明您 delete 做错了.

这是一个有争议的观点,但在审查了这么多有缺陷代码的例子后,我不再冒险了.所以,如果你写,new你需要一个智能管理器来为新分配的内存.而你现在需要它.

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

现在,真正的困难开始:哪位智能经理?

3.智能指针

有各种智能指针,具有各种特征.

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

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

通常,尝试使用scoped_ptr或者unique_ptr.如果您需要多个所有者尝试更改设计.如果您无法更改设计并且确实需要多个所有者,请使用a shared_ptr,但要注意使用weak_ptr中间某处应该打破的引用周期.

4.智能容器

许多智能指针并不意味着被复制,因此它们与STL容器的使用有些妥协.

而不是诉诸shared_ptr和开销,使用Boost指针容器中的智能容器.它们模拟经典STL容器的接口,但存储它们拥有的指针.

5.滚动你自己

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

在存在异常的情况下编写智能管理器非常困难.您通常不能假设内存可用(new可能失败)或者Copy Constructorno throw保证.

在某种程度上,忽略std::bad_alloc异常并强制Copy Constructor许多帮助程序的s不会失败可能是可以接受的...毕竟,这就是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 ;)
Run Code Online (Sandbox Code Playgroud)

  • 很棒的答案!:)我想Butterworth先生可以从中学到一些东西. (4认同)
  • 我个人喜欢Neil的答案(总的来说,尤其是这个答案),我只是认为这个主题需要更深入的解释,因为内存管理是如何棘手的以及库是如何"相对"新的(我在想这里的指针容器) ,2007年). (2认同)

小智 17

智能指针确实执行显式内存管理,如果您不理解它们是如何操作的,那么在使用C++编程时,您将陷入困境.请记住,内存并不是他们管理的唯一资源.

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

string * s1 = new string( "foo" );      // bad
string s2( "bar" );    // good
Run Code Online (Sandbox Code Playgroud)

编辑:回答你的问题"我是否总能使用智能指针代替原始指针???然后,不,你不能.如果(例如)你需要实现自己的operator new版本,你将不得不让它返回一个指针,而不是一个智能指针.

  • @Dony我一般都是错误的,而不是简单的无益答案.毕竟,很难确切地知道提问者需要学习什么来开悟. (12认同)
  • 完全没有用的答案.我希望我有足够的代表来支持这一点. (10认同)
  • @Dony答案的质量通常反映了问题的质量. (8认同)
  • 不幸的是,尼尔的答案往往伴随着屈尊俯就.他应该停止回答让他感到沮丧的问题,因为世界并不像他那样聪明或经验丰富. (4认同)
  • @Neil:你在评论中对OP的能力水平进行了几次刺戳.不要误解我的意思.我全力以赴.我认为,至少5次没有阅读过语言标准的人在"使用C++编程时遇到了麻烦". (3认同)
  • 我看到Stereotype Continuity Brigade已经全力以赴,以保持没有社交技能的极客的形象. (3认同)
  • @Dony:真的吗?有什么无益的呢?您可能对智能指针的工作原理有错误的概念?在这里:我已经为你投票了! (2认同)
  • 如果他还不知道该特定实现的语义和细节,那么在没有推理的情况下"执行此操作"并使用特定的自动ptr实现作为"第一近似"是最糟糕的事情. (2认同)

sth*_*sth 13

通常你不应该使用指针(智能或其他),如果你不需要它们.最好使局部变量,类成员,向量元素和类似项正常对象而不是指向对象的指针.(因为你来自Java,你可能会试图分配一切new,不推荐.)

这种方法(" RAII ")可以帮助您避免在大多数情况下担心指针.

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

  • 那么,那些"依赖"的情况是什么?为什么没有人告诉它? (3认同)

Ste*_*sop 9

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

在可执行文件或库中,如果要表示指针对象的所有权,那么智能指针通常是执行它的最佳方式.因此,想要始终使用它们而不是原始它是好的.你是否真的可以随时使用它们是另一回事.

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

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


Mar*_*ins 6

在许多情况下,我相信它们绝对是可行的方法(清理代码不那么凌乱,泄漏风险降低等).然而,有一些非常轻微的额外费用.如果我正在编写一些必须尽可能快的代码(比如一个必须进行一些分配和免费的紧密循环),我可能不会使用智能指针,希望能够提高速度.但我怀疑它会在大多数情况下产生任何可衡量的差异.

  • 如果您在紧密循环中分配,使用和销毁资源(这可能不是一个好主意),您可能会使用几乎零开销的scoped_ptr.为任务选择合适的智能指针. (6认同)
  • `shared_ptr`在共享信息块的分配中都有开销,并根据该共享信息检查是否解除分配.但是对于单一所有权智能指针,析构函数不需要执行任何测试:只需删除内部指针即可.示例:libstdc ++:`~auto_ptr(){delete _M_ptr; }`,boost 1.37:`~scoped_ptr(){checked_delete(ptr); ``checked_delete`是一个编译时检查类型完整性和单个调用`delete`,很可能是内联的. (2认同)

小智 5

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