什么时候应该使用智能指针上的原始指针?
看完这个答案之后 ,看起来最好尽可能地使用智能指针 ,并将“正常”/原始指针的使用量降到最低。
真的吗?
不,这不是真的。 如果一个函数需要一个指针,并且与所有权无关,那么我坚信一个正则指针应该被传递,原因如下:
- 没有所有权,所以你不知道什么样的智能指针传递
- 如果你传递一个特定的指针,比如
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); }
使用智能指针来pipe理所有权是正确的。 相反,在所有权不是问题的地方使用原始指针是没有错的。
这里有一些完全合法的原始指针的使用(记住,它总是假定它们是非拥有的):
他们与参考文献竞争
- 论证传递; 但引用不能为空,所以更好
- 作为集体成员来表示联想而非组成; 通常比引用更可取,因为赋值的语义更直接,另外由构造函数设置的不variables可以确保它们在对象的生命周期中不为
0
- 作为其他地方拥有的(可能是多态的)对象的句柄; 引用不能为空,所以他们是可取的
-
std::bind
使用一个惯例,传递的参数被复制到结果函数中; 然而,std::bind(&T::some_member, this, ...)
只会生成一个指针的副本,而std::bind(&T::some_member, *this, ...)
复制该对象;std::bind(&T::some_member, std::ref(*this), ...)
是一种替代scheme
他们不与参考文献竞争
- 作为迭代器!
- parameter passing可选参数; 在这里他们与
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; };
总是build议使用智能指针,因为它们明确logging了所有权。
然而,我们真正想念的是一个“空白”的智能指针,并不意味着任何所有权的概念。
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>
这确实是可能存在的任何智能指针的最简单版本:logging它不拥有它指向的资源的types。
引用计数(特别是由shared_ptr使用)将中断的一种情况是当您从指针创build一个循环时(例如,A指向B,B指向A或A-> B-> C-> A或等等)。 在这种情况下,没有任何对象会被自动释放,因为它们都保持彼此的引用计数大于零。
出于这个原因,每当我创build具有父子关系的对象(例如对象树)时,我将在父对象中使用shared_ptrs来保存它们的子对象,但是如果子对象需要一个指向父对象的指针,我将使用一个简单的C / C ++指针。
很less的情况下,你可能想使用指针:
- 函数指针(显然没有智能指针)
- 定义您自己的智能指针或容器
- 处理低级别编程,其中原始指针是至关重要的
- 从原始数组中衰减
我认为在这里给出了一个更彻底的答案: 什么时候使用哪种指针?
摘自该链接:“使用哑指针(原始指针) 或非拥有引用的资源引用 ,当你知道资源将超过引用的对象/范围。 (从原来的大胆保存)
问题是,如果你正在编写一般用途的代码,那么绝对肯定对象会超过原始指针并不总是那么容易。 考虑这个例子:
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()
函数可能会无意中导致其参数之一在其完成使用之前被释放。
所以,尽pipe起初看起来replace_current_employees_with()
函数不需要它的参数所有权,但它在完成使用之前需要对其参数的可能性进行一些防御。 最简单的解决scheme是实际上通过shared_ptr
获取(临时共享)参数的所有权。
但是,如果你真的不想取得所有权,现在有一个安全的select – 这是答案的无耻插件 – “ 注册指针 ”。 “注册指针”是智能指针,它们的行为与原始指针类似,只不过它们在目标对象被销毁时被自动设置为null_ptr
,并且默认情况下会在尝试访问已被删除的对象时抛出exception。
还要注意,已注册的指针可以使用编译时指令“禁用”(自动replace为原始指针对应部分),只允许在debugging/testing/testing版模式下使用(并产生开销)指针。 所以你应该真的不得不求助于实际的原始指针。
这是真的。 我看不到智能指针的原始指针的好处,特别是在复杂的项目中。
对于暂时的和轻量级的使用,虽然原始指针是好的。