什么时候应该使用原始指针而不是智能指针?

Alo*_*kin 56 c++ boost pointers smart-pointers

在阅读完这个答案后,看起来最好尽可能使用智能指针,并将"普通"/原始指针的使用量降至最低.

真的吗?

Arm*_*yan 82

不,这不是真的.如果一个函数需要一个指针并且与所有权无关,那么我强烈认为应该传递一个常规指针,原因如下:

  • 没有所有权,因此你不知道什么样的智能指针传递
  • 如果你传递一个特定的指针,shared_ptr那么你将无法通过,比方说,scoped_ptr

规则就是这样 - 如果你知道一个实体必须对对象拥有某种所有权,那么总是使用智能指针 - 那个能为你提供所需所有权的指针.如果没有所有权的概念,永远不要使用智能指针.

例1:

void PrintObject(shared_ptr<const Object> po) //bad
{
    if(po)
      po->Print();
    else
      log_error();
}

void PrintObject(const Object* po) //good
{
    if(po)
      po->Print();
    else
      log_error();
}
Run Code Online (Sandbox Code Playgroud)

例2:

Object* createObject() //bad
{
    return new Object;
}

some_smart_ptr<Object> createObject() //good
{
   return some_smart_ptr<Object>(new Object);
}
Run Code Online (Sandbox Code Playgroud)

  • 我喜欢关于所有权的说明(这是智能指针的全部内容).但是,您可能需要考虑PrintObject应该采用const而不是const*.这样,您可以保证不能转移所有权,因此不存在PrintObject是否应该占有它的问题. (15认同)
  • Example1中的另一个选择是传递引用,因为没有所有权被转移 - 就像在旧的`auto_ptr`中一样. (13认同)
  • @Armen:是的,但为了避免转让所有权,你要传递参考资料.当我写这篇文章时,它对我来说很有意义,但反思却不太明确. (4认同)
  • 我同意,如果可能的话,参数应该通过引用传递.我建议只有在函数不能承担所有权的情况下才使用原始指针**并且**该对象是可选的,因此指针可能需要为NULL. (3认同)
  • @AudioDroid:我不喜欢我的函数参数的顶级函数.即使指针确实改变为指向另一个地址,原始指针也会保持不变 (2认同)
  • 请注意,此答案同样适用于类成员,而不仅仅适用于函数参数.如果类成员暗示所有权,智能指针**可能是一个合适的解决方案; 如果类成员严格用于导航,则原始指针更合适.(另请注意,如果您使用OO范例,唯一的"所有者"通常是类本身,并且"这个"无法成为智能指针.) (2认同)
  • @Audio:当然不会 - 你传递指针BY VALUE(即你复制它).如果你在`const Object*&po`中传递对指针的引用,那么你期望的行为就是这样 (2认同)

Luc*_*ton 14

使用智能指针来管理所有权是正确的做法.相反,在所有权不是问题的地方使用原始指针并没有错.

以下是原始指针的一些完全合法的使用(记住,总是假设它们是非拥有的):

他们与参考竞争的地方

  • 论证传递; 但引用不能为null,因此更可取
  • 作为集体成员来表示结社而不是成分; 通常比引用更可取,因为赋值的语义更直接,另外构造函数设置的不变量可以确保它们不是0对象的生命周期
  • 作为其他地方拥有的(可能是多态的)对象的句柄; 引用不能为null所以再次它们是可取的
  • std::bind使用一种约定,其中传递的参数被复制到生成的仿函数中; 但是std::bind(&T::some_member, this, ...)只复制指针而std::bind(&T::some_member, *this, ...)复制对象; std::bind(&T::some_member, std::ref(*this), ...)是另一种选择

他们与参考文献竞争的地方

  • 作为迭代器!
  • 参数传递可选参数; 他们在这里竞争boost::optional<T&>
  • 作为其他地方拥有的(可能是多态的)对象的句柄,当它们无法在初始化站点声明时; 再次,与...竞争boost::optional<T&>

作为提醒,它几乎总是错写一个函数(这不是一个构造函数,或如取得所有权的函数成员),然后再将接受一个智能指针,除非它把它传递给构造(例如,它是正确的std::async,因为语义上它是接近是对std::thread构造函数的调用).如果它是同步的,则不需要智能指针.


回顾一下,这是一个演示上述几种用法的片段.我们正在编写和使用一个类,它将一个仿函数应用于std::vector<int>编写一些输出的每一个元素.

class apply_and_log {
public:
    // C++03 exception: it's acceptable to pass by pointer to const
    // to avoid apply_and_log(std::cout, std::vector<int>())
    // notice that our pointer would be left dangling after call to constructor
    // this still adds a requirement on the caller that v != 0 or that we throw on 0
    apply_and_log(std::ostream& os, std::vector<int> const* v)
        : log(&os)
        , data(v)
    {}

    // C++0x alternative
    // also usable for C++03 with requirement on v
    apply_and_log(std::ostream& os, std::vector<int> const& v)
        : log(&os)
        , data(&v)
    {}
    // now apply_and_log(std::cout, std::vector<int> {}) is invalid in C++0x
    // && is also acceptable instead of const&&
    apply_and_log(std::ostream& os, std::vector<int> const&&) = delete;

    // Notice that without effort copy (also move), assignment and destruction
    // are correct.
    // Class invariants: member pointers are never 0.
    // Requirements on construction: the passed stream and vector must outlive *this

    typedef std::function<void(std::vector<int> const&)> callback_type;

    // optional callback
    // alternative: boost::optional<callback_type&>
    void
    do_work(callback_type* callback)
    {
        // for convenience
        auto& v = *data;

        // using raw pointers as iterators
        int* begin = &v[0];
        int* end = begin + v.size();
        // ...

        if(callback) {
            callback(v);
        }
    }

private:
    // association: we use a pointer
    // notice that the type is polymorphic and non-copyable,
    // so composition is not a reasonable option
    std::ostream* log;

    // association: we use a pointer to const
    // contrived example for the constructors
    std::vector<int> const* data;
};
Run Code Online (Sandbox Code Playgroud)


Mat*_* M. 6

始终建议使用智能指针,因为它们清楚地记录了所有权.

然而,我们真正想念的是一个"空白"智能指针,这并不意味着任何所有权概念.

template <typename T>
class ptr // thanks to Martinho for the name suggestion :)
{
public:
  ptr(T* p): _p(p) {}
  template <typename U> ptr(U* p): _p(p) {}
  template <typename SP> ptr(SP const& sp): _p(sp.get()) {}

  T& operator*() const { assert(_p); return *_p; }
  T* operator->() const { assert(_p); return _p; }

private:
  T* _p;
}; // class ptr<T>
Run Code Online (Sandbox Code Playgroud)

实际上,这是可能存在的任何智能指针的最简单版本:一种记录它不拥有它所指向的资源的类型.

  • 对我来说听起来更像是一个'pointless_ptr <T>`.智能指针*保证*所有权遵循指示的语义.这个没有保证,它只是表明"某人,某个地方,认为这不应该拥有所有权".完全像原始指针一样.没有任何保证,该程序实际上*行为*就像那样. (10认同)
  • 我不知道这是一个"智能"指针.它是原始指针的包装器,它既不共享所有权,也不能检测指向对象何时被删除.它比简单的原始指针有什么优势? (7认同)
  • @OrbWeaver:功能上?没有.然而,在语义上,它清楚地表明了所有权被考虑,并且确定变量不会**拥有所有权.当你看到一个方法`void foo(Bar*)`时,无论方法是否取得指针的所有权总是不明确的,如果你看到`void foo(client_ptr <Bar>)`那么你*知道*它没有所有权 - 虽然我会对一个更好的名字感兴趣:) (6认同)
  • 有趣的名字:`not_so_smart_ptr <T>`,`stupid_ptr <T>`,`sep_ptr <T>`.严肃地说,我认为`ptr <T>`没问题. (2认同)