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

时间:2011-07-13 07:44:14

标签: c++ pointers boost smart-pointers

阅读this answer后,看起来最好尽可能使用smart pointers,并将“普通”/原始指针的使用量降至最低。

这是真的吗?

8 个答案:

答案 0 :(得分:78)

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

  • 没有所有权,因此您不知道传递什么样的智能指针
  • 如果您传递了特定指针,例如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();
}

例2:

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

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

答案 1 :(得分:14)

使用智能指针管理所有权是正确的做法。 相反,使用原始指针,无论所有权,问题都是错误。

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

他们与参考资料竞争

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

他们与参考

竞争
  • as iterators!
  • 参数传递可选参数;在这里,他们与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;
};

答案 2 :(得分: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>

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

答案 3 :(得分:4)

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

出于这个原因,每当我创建具有父子关系的对象(例如对象树)时,我将使用父对象中的shared_ptrs来保存它们的子对象,但是如果子对象需要指针返回对于他们的父母,我将使用一个普通的C / C ++指针。

答案 4 :(得分:1)

在某些情况下,您可能需要使用指针:

  • 函数指针(显然没有智能指针)
  • 定义您自己的智能指针或容器
  • 处理低级编程,原始指针至关重要
  • 从原始数组中衰减

答案 5 :(得分:1)

我认为这里给出了一个更彻底的答案:Which kind of pointer do I use when?

摘自该链接:“使用哑指针(原始指针)或非拥有引用对资源的引用,以及当您知道资源将超过引用时对象/范围。“ (保留原文的粗体)

问题在于,如果您正在编写通用代码,那么绝对肯定该对象将比原始指针更长。考虑这个例子:

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

令人惊讶的是,replace_current_employees_with()函数可能会在无意中导致其中一个参数在完成使用之前被释放。

所以尽管最初可能看起来replace_current_employees_with()函数不需要对其参数的所有权,但它需要某种防御来防止其参数在完成使用之前被隐藏地解除分配的可能性。最简单的解决方案是实际采用(临时共享)参数的所有权,可能是通过shared_ptr

但如果你真的不想取得所有权,那么现在有一个安全的选择 - 这是答案的无耻插件部分 - “registered pointers”。 “注册指针”是指向原始指针的智能指针,除了它们在目标对象被销毁时(自动)设置为null_ptr,并且默认情况下,如果您尝试访问一个对象,它将抛出异常已被删除。

另请注意,已注册的指针可以使用编译时指令“禁用”(自动替换为其原始指针对应部分),从而允许它们仅在调试/测试/测试模式下使用(并产生开销)。所以你真的必须很少使用实际的原始指针。

答案 6 :(得分:0)

我相信即使在原始指针已足够的情况下,也应尽可能使用智能指针。 text ='''Market 05-20 06-20 07-20 08-20 HK 5 5 5 5 US 2 2 2 2 HK 3 3 3 3 UK 7 7 7 7 UK 2 2 2 2''' import pandas as pd import io #df = pd.read_csv("filename.csv") df = pd.read_csv(io.StringIO(text), sep="\s+") df = df.groupby("Market").sum() print(df) df = df.stack() print(df) df = df.reset_index() print(df) df.columns = ['Market', 'Data', 'Quantity'] print(df) 可以帮助管理资源生命周期,同时仍保持较小和快速。不要回头!

答案 7 :(得分:-2)

确实如此。我无法看到原始指针优于智能指针的好处,特别是在复杂的项目中。

对于临时和轻量级用法,原始指针很好。