2011-07-13 62 views
43

看完this answer之后,看起来最好尽量使用smart pointers,并将“正常”/原始指针的使用量降至最低。什么时候应该在智能指针上使用原始指针?

这是真的吗?

+15

只是为了记录,许多智能指针类型*与原始指针一样快。只是当你说“智能指针”时,几乎每个人都听到它是“共享指针”,并且共享指针比原指针慢得多。但'scoped_ptr'或'unique_ptr'没有性能开销。所以“我想要表现”并不是避免智能指针的真正借口。仅用于避免'shared_ptr'特定 – jalf

回答

62

不,这是不正确的。如果一个函数需要一个指针和无关所有权,那么我强烈相信,一个普通指针应,原因如下传递:

  • 没有所有权,所以你不知道什么样的智能的指针传递
  • 如果你传递一个特定的指针,就像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); 
} 
+11

Example1中的一个替代方法是传递一个引用,因为没有所有权被传递 - 就像在旧的'auto_ptr'中一样。 – DanS

+12

我喜欢关于所有权的说明(这是智能指针的全部内容)。但是,您可能需要考虑PrintObject应采用const而不是const *。这样,您就可以保证不能转让所有权,所以PrintObject不应该占有它。 –

+0

@DanS:传递一个auto_ptr会简直是错误的 - 它会在函数完成后破坏对象 –

1

少数情况下,您可能需要使用指针:

  • 函数指针(显然没有智能指针)
  • 定义您自己的智能指针或容器
  • 低层次的编程,其中原始指针是至关重要的
  • 衰变从原材料阵列
+0

虽然你可以使用容器而不是数组或智能数组,但是为什么低级编程需要原始指针但是你没有指定它们)? –

+0

有一些类似于函数的智能指针:'boost :: function'或'std :: function'。它们是处理复制等的各种类型的包装器功能对象,函数指针等。 –

2

一个实例就是当你出去的创建循环引用计数(在特定的shared_ptr使用)会分解为应对指针(例如, A指向B,B指向A,或A-> B-> C-> A等)。在这种情况下,没有任何对象会被自动释放,因为它们都保持彼此的引用计数大于零。因此,无论何时我创建具有父子关系的对象(例如对象树),我都会在父对象中使用shared_ptrs来保存它们的子对象,但是如果子对象需要一个指针回到他们的父母,我将使用一个普通的C/C++指针。

+6

好点,但仍然是弱指针,像'boost :: weak_ptr' –

+0

啊,是的,我忘记了weak_ptr。 –

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> 

这确实是,任何可能存在的智能指针的简单版本:该文档,它不拥有它指向太资源类型。

+7

我不明白这是一个“智能”指针。它是一个原始指针的包装,它既不共享所有权,也不会检测指向对象何时被删除。它提供了一个简单的原始指针有什么优势? – 2011-07-13 08:49:19

+5

@OrbWeaver:功能上?没有。然而,从语义上讲,它明确表示所有权是被考虑的,并且决定该变量不会拥有所有权。当你看到一个void foo(Bar *)方法时,这个方法是否拥有指针的所有权总是含糊不清的,如果你看到'void foo(client_ptr )',那么你知道它没有取得所有权 - 虽然我会对更好的名字感兴趣:) –

+1

哦,我明白了。有趣的技术,我从来没有想过以这种方式使用类。 – 2011-07-13 08:59:22

13

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

这里有一些完全合法的使用原始指针(请记住,它总是以为他们都是非所属)的:

在那里他们与引用

  • 参数传递竞争;但引用不能为空,所以优先使用
  • 作为类成员来表示关联而不是成分;通常比引用更可取,因为赋值的语义更直接,另外由构造函数设置的不变量可以确保它们不是对象的生命周期的0作为对某个(可能是多态的)对象拥有的句柄的对象的句柄
  • 其他;引用不能为空,因此它们也是可取的
  • 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; 
}; 
1

我觉得有点更全面的答案在这里给出:Which kind of pointer do I use when?

从该链接摘录:“使用dumb指针(裸指针)或引用为非所属引用资源和时你知道资源将超过引用对象/范围的。“(黑体从原来保存)

的问题是,如果你正在写的一般使用代码它并不总是很容易以绝对的对象会活得比原始指针考虑这个例子:

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,并且默认情况下会在尝试访问已被删除的对象时引发异常。

还要注意,已注册的指针可以使用编译时指令“禁用”(自动替换为其原始指针对应部分),只允许它们在调试/测试/测试模式下使用(并产生开销)。所以你应该非常少用实际的原始指针。

-1

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

对于暂时性和轻量级的使用,原始指针虽然没问题。