我何时应该在智能指针上使用原始指针?

问题描述 投票:60回答:8

阅读this answer之后,似乎最好的方法是尽可能多地使用smart pointers,并将“正常” /原始指针的使用减少到最小。

是真的吗?

c++ pointers boost smart-pointers
8个回答
89
投票

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

  • 没有所有权,因此您不知道要传递哪种智能指针
  • 如果传递特定的指针,例如shared_ptr,则将无法传递,例如scoped_ptr

规则就是这样-如果您知道实体必须对对象拥有某种所有权,总是使用智能指针-一种可以为您提供所需所有权的指针。如果没有所有权概念,则never使用智能指针。

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

16
投票

使用智能指针来管理所有权是正确的做法。相反,在所有权为[[not的地方使用原始指针,则问题为[[not错误。]]这里有一些完全合法的原始指针用法(请记住,始终假定它们是非所有者的::

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

参数传递;但引用不能为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&>]竞争
  • [提醒一下,编写一个接受智能指针的函数(不是构造函数,或者例如拥有所有权的函数成员),除非将它依次传递给构造函数(例如,对于[ C0],因为从语义上讲,它几乎是对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; };

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

引用计数(特别是由shared_ptr使用)将中断的一个实例是在指针之外创建一个循环(例如,A指向B,B指向A或A-> B-> C-> A ,或其他)。在那种情况下,不会自动释放任何对象,因为它们都使彼此的引用计数都大于零。
出于这个原因,每当我创建具有父子关系的对象(例如,一棵对象树)时,我都会在父对象中使用shared_ptrs来保存其子对象,但是如果子对象需要一个指向的指针,他们的父母,我将为此使用普通的C / C ++指针。

在少数情况下,您可能希望使用指针:

函数指针(显然没有智能指针)

    定义您自己的智能指针或容器
  • 处理低级编程,原始指针至关重要。>
  • 从原始数组中衰减
  • 我认为这里给出了更彻底的答案: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>
    摘自该链接:“对资源的

    非所有者引用使用哑指针(原始指针)或引用,并且当您知道

    资源将失效引用对象/作用域时。” (大胆保留原始内容)

    问题是,如果您要编写通用代码,要确定对象的寿命将超出原始指针并不总是那么容易。考虑这个例子:
    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
    投票
    但是,我们真正想念的是一个“空白”智能指针,它并不暗示任何所有权概念。

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


    4
    投票
    出于这个原因,每当我创建具有父子关系的对象(例如,一棵对象树)时,我都会在父对象中使用shared_ptrs来保存其子对象,但是如果子对象需要一个指向的指针,他们的父母,我将为此使用普通的C / C ++指针。

    2
    投票

    函数指针(显然没有智能指针)

      定义您自己的智能指针或容器

    1
    投票
    摘自该链接:“对资源的

    非所有者引用使用哑指针(原始指针)或引用,并且当您知道

    资源将失效引用对象/作用域时。” (大胆保留原始内容)


    0
    投票

    是的。我看不到原始指针优于智能指针的好处,特别是在复杂的项目中。
    对于临时性和轻量级的使用,原始指针虽然很好。

    -4
    投票
    © www.soinside.com 2019 - 2024. All rights reserved.