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

时间:2022-02-22 21:17:27

After reading this answer, it looks like it is a best practice to use smart pointers as much as possible, and to reduce the usage of "normal"/raw pointers to minimum.

在阅读了这个答案之后,看起来尽可能多地使用智能指针是一种最佳实践,并将“常规”/原始指针的使用减少到最少。

Is that true?

这是真的吗?

7 个解决方案

#1


67  

No, it's not true. If a function needs a pointer and has nothing to do with ownership, then I strongly believe that a regular pointer should be passed for the following reasons:

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

  • No ownership, therefore you don't know what kind of a smart pointer to pass
  • 没有所有权,因此您不知道传递哪种智能指针
  • If you pass a specific pointer, like shared_ptr, then you won't be able to pass, say, scoped_ptr
  • 如果您传递一个特定的指针,比如shared_ptr,那么您将无法传递,比如scoped_ptr

The rule would be this - if you know that an entity must take a certain kind of ownership of the object, always use smart pointers - the one that gives you the kind of ownership you need. If there is no notion of ownership, never use smart pointers.

规则是这样的——如果您知道一个实体必须拥有对象的某种所有权,那么一定要使用智能指针——它将为您提供所需的所有权。如果没有所有权的概念,就不要使用智能指针。

Example1:

例二:

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();
}

Example2:

Example2:

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

some_smart_ptr<Object> createObject() //good
{
   return some_smart_ptr<Object>(new Object);
}

#2


14  

Using smart pointers to manage ownership is the right thing to do. Conversely, using raw pointers wherever ownership is not an issue is not wrong.

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

Here are some perfectly legitimate use of raw pointers (remember, it is always assumed they are non-owning):

这里有一些使用原始指针的完全合法的方法(请记住,它总是假定它们是不可拥有的):

where they compete with references

他们在哪里与参考文献竞争

  • argument passing; but references can't be null, so are preferable
  • 参数传递;但是引用不能为空,所以更可取
  • as class members to denote association rather than composition; usually preferable to references because the semantics of assignment are more straightforward and in addition an invariant set up by the constructors can ensure that they are not 0 for the lifetime of the object
  • 作为班级成员来表示联想而不是组成;通常比引用更可取,因为赋值的语义更简单,而且构造函数设置的不变量可以确保对象的生命周期不为0
  • as a handle to a (possibly polymorphic) object owned somewhere else; references can't be null so again they are preferable
  • 作为在其他地方拥有的(可能是多态的)对象的句柄;引用不能为空,因此它们更可取。
  • std::bind uses a convention where arguments that are passed are copied into the resulting functor; however std::bind(&T::some_member, this, ...) only makes a copy of the pointer whereas std::bind(&T::some_member, *this, ...) copies the object; std::bind(&T::some_member, std::ref(*this), ...) is an alternative
  • :bind使用一个约定,其中传递的参数被复制到结果的函子中;但是std::bind(&T: some_member, this,…)只复制指针,而std::bind(&T::some_member, *this,…)复制对象;绑定(&T: some_member, std:::ref(*this),…)是另一种选择

where they do not compete with references

在哪里他们不与参考文献竞争

  • as iterators!
  • 迭代器!
  • argument passing of optional parameters; here they compete with boost::optional<T&>
  • 可选参数的参数传递;这里他们与boost::可选的< t&>竞争
  • as a handle to a (possibly polymorphic) object owned somewhere else, when they can't be declared at the site of initialization; again, competing with boost::optional<T&>
  • 作为一个(可能是多态的)对象的句柄,当它们不能在初始化的位置声明时;再次,与促进竞争::可选< t >

As a reminder, it's almost always wrong to write a function (that is not a constructor, or a function member that e.g. takes ownership) that accepts a smart pointer unless it in turn pass it to a constructor (e.g. it's correct for std::async because semantically it's close to being a call to the std::thread constructor). If it's synchronous, no need for the smart pointer.

提醒一下,几乎总是错的写一个函数(这不是一个构造函数,或一个函数成员,如所有权),接受一个智能指针,除非它反过来又将它传递给构造函数(例如对std::异步因为语义接近调用std::线程的构造函数)。如果是同步的,则不需要智能指针。


To recap, here's a snippet that demonstrates several of the above uses. We're writing and using a class that applies a functor to every element of an std::vector<int> while writing some output.

总结一下,这里有一个片段演示了上述几种用法。我们正在编写并使用一个类,该类将一个函子应用到std::vector 的每个元素,同时编写一些输出。

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;
};

#3


6  

The use of smart pointers is always recommended because they clearly document the ownership.

总是建议使用智能指针,因为它们清楚地记录了所有权。

What we really miss, however, is a "blank" smart pointer, one that does not imply any notion of ownership.

然而,我们真正遗漏的是一个“空白”的智能指针,一个不包含任何所有权概念的指针。

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>

This is, indeed, the simplest version of any smart pointer that may exist: a type that documents that it does not own the resource it points too.

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

#4


2  

One instance where reference counting (used by shared_ptr in particular) will break down is when you create a cycle out of the pointers (e.g. A points to B, B points to A, or A->B->C->A, or etc). In that case, none of the objects will ever be automatically freed, because they are all keeping each other's reference counts greater than zero.

引用计数(特别由shared_ptr使用)会崩溃的一个实例是,当您从指针中创建一个循环时(例如,a指向B, B指向a,或a ->B->C-> a,等等)。在这种情况下,不会自动释放任何对象,因为它们都保持彼此的引用计数大于零。

For that reason, whenever I am creating objects that have a parent-child relationship (e.g. a tree of objects), I will use shared_ptrs in the parent objects to hold their child objects, but if the child objects need a pointer back to their parent, I will use a plain C/C++ pointer for that.

因此,每当我创建对象,有父子关系(如对象树),我将使用要在父对象持有他们的子对象,但如果子对象需要一个指针回到他们的家长,我将使用一个简单的C / c++指针。

#5


2  

I think a little bit more thorough answer was given here: Which kind of pointer do I use when?

我认为这里给出了一个更彻底的答案:当我使用哪种指针时?

Excerpted from that link: "Use dumb pointers (raw pointers) or references for non-owning references to resources and when you know that the resource will outlive the referencing object / scope." (bold preserved from the original)

从该链接摘录:“使用哑指针(原始指针)或引用来引用非拥有的资源,当您知道该资源将比引用的对象/范围活得长时。”(大胆保留原作)

The problem is that if you're writing code for general use it's not always easy to be absolutely certain the object will outlive the raw pointer. Consider this example:

问题是,如果您正在编写用于一般用途的代码,那么要绝对确定对象将比原始指针活得长并不总是容易的。考虑一下这个例子:

struct employee_t {
    employee_t(const std::string& first_name, const std::string& last_name) : m_first_name(first_name), m_last_name(last_name) {}
    std::string m_first_name;
    std::string m_last_name;
};

void replace_current_employees_with(const employee_t* p_new_employee, std::list<employee_t>& employee_list) {
    employee_list.clear();
    employee_list.push_back(*p_new_employee);
}

void main(int argc, char* argv[]) {
    std::list<employee_t> current_employee_list;
    current_employee_list.push_back(employee_t("John", "Smith"));
    current_employee_list.push_back(employee_t("Julie", "Jones"));
    employee_t* p_person_who_convinces_boss_to_rehire_him = &(current_employee_list.front());

    replace_current_employees_with(p_person_who_convinces_boss_to_rehire_him, current_employee_list);
}

Much to its surprise, the replace_current_employees_with() function can inadvertently cause one of its parameters to be deallocated before it's finished using it.

令人惊讶的是,replace_current_employees_with()函数可能会不经意地导致在使用它之前释放它的一个参数。

So even though it might at first seem like the replace_current_employees_with() function doesn't need ownership of it's parameters, it needs some kind of defense against the possiblity of its parameters being insidiously deallocated before it's finished using them. The simplest solution is to actually take (temporary shared) ownership of the parameter(s), presumably through a shared_ptr.

因此,尽管replace_current_employees_with()函数一开始看起来似乎并不需要拥有它的参数,但它需要某种防御,以防止在使用参数之前,它的参数可能被秘密地分配。最简单的解决方案是实际获得参数(临时共享)的所有权,可能是通过shared_ptr。

But if you really don't want to take ownership, there is now a safe option - and this is the shameless plug portion of the answer - "registered pointers". "registered pointers" are smart pointers that behave like raw pointers, except that they are (automatically) set to null_ptr when the target object is destroyed, and by default, will throw an exception if you try to access an object that has already been deleted.

但如果你真的不想拥有所有权,现在有一个安全的选择——这是答案中无耻的插入部分——“注册指针”。“注册指针”是智能指针,它的行为类似于原始指针,但是当目标对象被销毁时,它们(自动)设置为null_ptr,并且默认情况下,如果您试图访问已经删除的对象,则会抛出异常。

Also note that registered pointers can be "disabled" (automatically replaced with their raw pointer counterpart) with a compile-time directive, allowing them to be used (and incur overhead) in debug/test/beta modes only. So you should really have to resort actual raw pointers quite rarely.

还要注意,注册指针可以“禁用”(用它们的原始指针对应项自动替换),并使用编译时指令,允许仅在调试/测试/beta模式中使用它们(并产生开销)。所以你真的应该很少使用真正的原始指针。

#6


1  

Few cases, where you may want to use pointers:

很少的情况下,你可能想要使用指针:

  • Function pointers (obviously no smart pointer)
  • 函数指针(显然没有智能指针)
  • Defining your own smart pointer or container
  • 定义自己的智能指针或容器
  • Dealing with low level programming, where raw pointers are crucial
  • 处理低级编程,其中原始指针是至关重要的
  • Decaying from raw arrays
  • 腐烂的从原始数组

#7


-1  

It is true. I can not see the benefits of raw pointers over smart pointers, especially in a complex project.

这是真的。我看不到原始指针比智能指针的好处,特别是在复杂的项目中。

For tempory and lightweight usage, raw pointers are fine though.

对于临时和轻量级使用,原始指针是可以的。

#1


67  

No, it's not true. If a function needs a pointer and has nothing to do with ownership, then I strongly believe that a regular pointer should be passed for the following reasons:

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

  • No ownership, therefore you don't know what kind of a smart pointer to pass
  • 没有所有权,因此您不知道传递哪种智能指针
  • If you pass a specific pointer, like shared_ptr, then you won't be able to pass, say, scoped_ptr
  • 如果您传递一个特定的指针,比如shared_ptr,那么您将无法传递,比如scoped_ptr

The rule would be this - if you know that an entity must take a certain kind of ownership of the object, always use smart pointers - the one that gives you the kind of ownership you need. If there is no notion of ownership, never use smart pointers.

规则是这样的——如果您知道一个实体必须拥有对象的某种所有权,那么一定要使用智能指针——它将为您提供所需的所有权。如果没有所有权的概念,就不要使用智能指针。

Example1:

例二:

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();
}

Example2:

Example2:

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

some_smart_ptr<Object> createObject() //good
{
   return some_smart_ptr<Object>(new Object);
}

#2


14  

Using smart pointers to manage ownership is the right thing to do. Conversely, using raw pointers wherever ownership is not an issue is not wrong.

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

Here are some perfectly legitimate use of raw pointers (remember, it is always assumed they are non-owning):

这里有一些使用原始指针的完全合法的方法(请记住,它总是假定它们是不可拥有的):

where they compete with references

他们在哪里与参考文献竞争

  • argument passing; but references can't be null, so are preferable
  • 参数传递;但是引用不能为空,所以更可取
  • as class members to denote association rather than composition; usually preferable to references because the semantics of assignment are more straightforward and in addition an invariant set up by the constructors can ensure that they are not 0 for the lifetime of the object
  • 作为班级成员来表示联想而不是组成;通常比引用更可取,因为赋值的语义更简单,而且构造函数设置的不变量可以确保对象的生命周期不为0
  • as a handle to a (possibly polymorphic) object owned somewhere else; references can't be null so again they are preferable
  • 作为在其他地方拥有的(可能是多态的)对象的句柄;引用不能为空,因此它们更可取。
  • std::bind uses a convention where arguments that are passed are copied into the resulting functor; however std::bind(&T::some_member, this, ...) only makes a copy of the pointer whereas std::bind(&T::some_member, *this, ...) copies the object; std::bind(&T::some_member, std::ref(*this), ...) is an alternative
  • :bind使用一个约定,其中传递的参数被复制到结果的函子中;但是std::bind(&T: some_member, this,…)只复制指针,而std::bind(&T::some_member, *this,…)复制对象;绑定(&T: some_member, std:::ref(*this),…)是另一种选择

where they do not compete with references

在哪里他们不与参考文献竞争

  • as iterators!
  • 迭代器!
  • argument passing of optional parameters; here they compete with boost::optional<T&>
  • 可选参数的参数传递;这里他们与boost::可选的< t&>竞争
  • as a handle to a (possibly polymorphic) object owned somewhere else, when they can't be declared at the site of initialization; again, competing with boost::optional<T&>
  • 作为一个(可能是多态的)对象的句柄,当它们不能在初始化的位置声明时;再次,与促进竞争::可选< t >

As a reminder, it's almost always wrong to write a function (that is not a constructor, or a function member that e.g. takes ownership) that accepts a smart pointer unless it in turn pass it to a constructor (e.g. it's correct for std::async because semantically it's close to being a call to the std::thread constructor). If it's synchronous, no need for the smart pointer.

提醒一下,几乎总是错的写一个函数(这不是一个构造函数,或一个函数成员,如所有权),接受一个智能指针,除非它反过来又将它传递给构造函数(例如对std::异步因为语义接近调用std::线程的构造函数)。如果是同步的,则不需要智能指针。


To recap, here's a snippet that demonstrates several of the above uses. We're writing and using a class that applies a functor to every element of an std::vector<int> while writing some output.

总结一下,这里有一个片段演示了上述几种用法。我们正在编写并使用一个类,该类将一个函子应用到std::vector 的每个元素,同时编写一些输出。

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;
};

#3


6  

The use of smart pointers is always recommended because they clearly document the ownership.

总是建议使用智能指针,因为它们清楚地记录了所有权。

What we really miss, however, is a "blank" smart pointer, one that does not imply any notion of ownership.

然而,我们真正遗漏的是一个“空白”的智能指针,一个不包含任何所有权概念的指针。

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>

This is, indeed, the simplest version of any smart pointer that may exist: a type that documents that it does not own the resource it points too.

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

#4


2  

One instance where reference counting (used by shared_ptr in particular) will break down is when you create a cycle out of the pointers (e.g. A points to B, B points to A, or A->B->C->A, or etc). In that case, none of the objects will ever be automatically freed, because they are all keeping each other's reference counts greater than zero.

引用计数(特别由shared_ptr使用)会崩溃的一个实例是,当您从指针中创建一个循环时(例如,a指向B, B指向a,或a ->B->C-> a,等等)。在这种情况下,不会自动释放任何对象,因为它们都保持彼此的引用计数大于零。

For that reason, whenever I am creating objects that have a parent-child relationship (e.g. a tree of objects), I will use shared_ptrs in the parent objects to hold their child objects, but if the child objects need a pointer back to their parent, I will use a plain C/C++ pointer for that.

因此,每当我创建对象,有父子关系(如对象树),我将使用要在父对象持有他们的子对象,但如果子对象需要一个指针回到他们的家长,我将使用一个简单的C / c++指针。

#5


2  

I think a little bit more thorough answer was given here: Which kind of pointer do I use when?

我认为这里给出了一个更彻底的答案:当我使用哪种指针时?

Excerpted from that link: "Use dumb pointers (raw pointers) or references for non-owning references to resources and when you know that the resource will outlive the referencing object / scope." (bold preserved from the original)

从该链接摘录:“使用哑指针(原始指针)或引用来引用非拥有的资源,当您知道该资源将比引用的对象/范围活得长时。”(大胆保留原作)

The problem is that if you're writing code for general use it's not always easy to be absolutely certain the object will outlive the raw pointer. Consider this example:

问题是,如果您正在编写用于一般用途的代码,那么要绝对确定对象将比原始指针活得长并不总是容易的。考虑一下这个例子:

struct employee_t {
    employee_t(const std::string& first_name, const std::string& last_name) : m_first_name(first_name), m_last_name(last_name) {}
    std::string m_first_name;
    std::string m_last_name;
};

void replace_current_employees_with(const employee_t* p_new_employee, std::list<employee_t>& employee_list) {
    employee_list.clear();
    employee_list.push_back(*p_new_employee);
}

void main(int argc, char* argv[]) {
    std::list<employee_t> current_employee_list;
    current_employee_list.push_back(employee_t("John", "Smith"));
    current_employee_list.push_back(employee_t("Julie", "Jones"));
    employee_t* p_person_who_convinces_boss_to_rehire_him = &(current_employee_list.front());

    replace_current_employees_with(p_person_who_convinces_boss_to_rehire_him, current_employee_list);
}

Much to its surprise, the replace_current_employees_with() function can inadvertently cause one of its parameters to be deallocated before it's finished using it.

令人惊讶的是,replace_current_employees_with()函数可能会不经意地导致在使用它之前释放它的一个参数。

So even though it might at first seem like the replace_current_employees_with() function doesn't need ownership of it's parameters, it needs some kind of defense against the possiblity of its parameters being insidiously deallocated before it's finished using them. The simplest solution is to actually take (temporary shared) ownership of the parameter(s), presumably through a shared_ptr.

因此,尽管replace_current_employees_with()函数一开始看起来似乎并不需要拥有它的参数,但它需要某种防御,以防止在使用参数之前,它的参数可能被秘密地分配。最简单的解决方案是实际获得参数(临时共享)的所有权,可能是通过shared_ptr。

But if you really don't want to take ownership, there is now a safe option - and this is the shameless plug portion of the answer - "registered pointers". "registered pointers" are smart pointers that behave like raw pointers, except that they are (automatically) set to null_ptr when the target object is destroyed, and by default, will throw an exception if you try to access an object that has already been deleted.

但如果你真的不想拥有所有权,现在有一个安全的选择——这是答案中无耻的插入部分——“注册指针”。“注册指针”是智能指针,它的行为类似于原始指针,但是当目标对象被销毁时,它们(自动)设置为null_ptr,并且默认情况下,如果您试图访问已经删除的对象,则会抛出异常。

Also note that registered pointers can be "disabled" (automatically replaced with their raw pointer counterpart) with a compile-time directive, allowing them to be used (and incur overhead) in debug/test/beta modes only. So you should really have to resort actual raw pointers quite rarely.

还要注意,注册指针可以“禁用”(用它们的原始指针对应项自动替换),并使用编译时指令,允许仅在调试/测试/beta模式中使用它们(并产生开销)。所以你真的应该很少使用真正的原始指针。

#6


1  

Few cases, where you may want to use pointers:

很少的情况下,你可能想要使用指针:

  • Function pointers (obviously no smart pointer)
  • 函数指针(显然没有智能指针)
  • Defining your own smart pointer or container
  • 定义自己的智能指针或容器
  • Dealing with low level programming, where raw pointers are crucial
  • 处理低级编程,其中原始指针是至关重要的
  • Decaying from raw arrays
  • 腐烂的从原始数组

#7


-1  

It is true. I can not see the benefits of raw pointers over smart pointers, especially in a complex project.

这是真的。我看不到原始指针比智能指针的好处,特别是在复杂的项目中。

For tempory and lightweight usage, raw pointers are fine though.

对于临时和轻量级使用,原始指针是可以的。