总是使用智能指针是一个好习惯吗?
我发现智能指针比原始指针更舒适。 那么总是使用智能指针是个好主意吗? (请注意,我来自Java的背景,因此不太喜欢显式内存pipe理的概念,所以除非智能指针有一些严重的性能问题,否则我会坚持下去。)
注意:虽然我来自Java背景,但是我很了解智能指针的实现和RAII的概念。 因此,在发布答案时,您可以将这些知识视为理所当然。 我几乎在任何地方都使用静态分配,只在必要时才使用指针。 我的问题只是: 我可以总是使用智能指针代替原始指针?
经过几次编辑,我有一个印象,一个全面的总结将是有益的。
1.什么时候不
有两种情况你不应该使用智能指针。
第一个是事实上你不应该使用C++
类的完全相同的情况。 IE:DLL边界,如果你不提供源代码给客户端。 说轶事。
第二种情况经常发生: 聪明的经理意味着所有权 。 您可以使用指针指向现有资源而不pipe理其生命周期,例如:
void notowner(const std::string& name) { Class* pointer(0); if (name == "cat") pointer = getCat(); else if (name == "dog") pointer = getDog(); if (pointer) doSomething(*pointer); }
这个例子受到限制。 但是一个指针在语义上与引用不同,因为它可能指向一个无效的位置(空指针)。 在这种情况下,最好不要使用智能指针,因为您不想pipe理对象的生命周期。
2.聪明的经理
除非你正在写一个聪明的经理类,否则如果你使用关键字 delete
你做错了什么。
这是一个有争议的观点,但是在回顾了这么多有缺陷的代码的例子之后,我再也没有机会了。 所以,如果你写new
你需要一个聪明的经理为新分配的内存。 而你现在需要它。
这并不意味着你不是一个程序员! 相反,重复使用已被certificate有效的代码,而不是一遍又一遍地重复轮转是一项关键技能。
现在,真正的困难开始:哪位聪明的经理?
3.智能指针
那里有各种各样的聪明的指针,有各种各样的特征。
跳过你通常应该避免的std::auto_ptr
(它的复制语义被拧紧)。
-
scoped_ptr
:没有开销,不能被复制或移动。 -
unique_ptr
:没有开销,不能被复制,可以被移动。 -
shared_ptr
/weak_ptr
:一些开销(引用计数),可以被复制。
通常,尝试使用scoped_ptr
或unique_ptr
。 如果你需要几个业主尝试改变devise。 如果你不能改变devise,并且确实需要多个所有者,那么使用shared_ptr
,但要小心使用weak_ptr
在某个地方应该被破坏的引用周期。
4.智能容器
许多智能指针并不意味着被复制,因此它们在STL容器中的使用会受到某些影响。
而不是求助于shared_ptr
及其开销,使用Boost指针容器中的智能容器。 他们模拟经典的STL容器的接口,但存储他们自己的指针。
5.滚动你自己的
有些情况下,您可能希望推出自己的智能pipe理器。 检查一下,你是否错过了事先使用的库中的某些function。
在有例外的情况下写一个聪明的经理是相当困难的。 你通常不能假定内存是可用的( new
可能会失败),或者Copy Constructor
有no throw
保证。
忽略std::bad_alloc
exception可能是可以接受的,并强制许多助手的Copy Constructor
不会失败…毕竟,这就是boost::shared_ptr
为其deleter D
模板参数所做的事情。
但我不会推荐它,特别是对于初学者。 这是一个棘手的问题,你现在不太可能注意到这个错误。
6.例子
// For the sake of short code, avoid in real code ;) using namespace boost; // Example classes // Yes, clone returns a raw pointer... // it puts the burden on the caller as for how to wrap it // It is to obey the `Cloneable` concept as described in // the Boost Pointer Container library linked above struct Cloneable { virtual ~Cloneable() {} virtual Cloneable* clone() const = 0; }; struct Derived: Cloneable { virtual Derived* clone() const { new Derived(*this); } }; void scoped() { scoped_ptr<Cloneable> c(new Derived); } // memory freed here // illustration of the moved semantics unique_ptr<Cloneable> unique() { return unique_ptr<Cloneable>(new Derived); } void shared() { shared_ptr<Cloneable> n1(new Derived); weak_ptr<Cloneable> w = n1; { shared_ptr<Cloneable> n2 = n1; // copy n1.reset(); assert(n1.get() == 0); assert(n2.get() != 0); assert(!w.expired() && w.get() != 0); } // n2 goes out of scope, the memory is released assert(w.expired()); // no object any longer } void container() { ptr_vector<Cloneable> vec; vec.push_back(new Derived); vec.push_back(new Derived); vec.push_back( vec.front().clone() // Interesting semantic, it is dereferenced! ); } // when vec goes out of scope, it clears up everything ;)
智能指针确实执行显式的内存pipe理,如果你不明白他们是怎么做的,那么在用C ++编程的时候就会遇到麻烦。 请记住,内存不是他们pipe理的唯一资源。
但是要回答你的问题,你应该首选智能指针作为解决scheme的第一个近似值,但是也可能在必要时做好准备。 避免使用指针(或任何types)或dynamic分配。 例如:
string * s1 = new string( "foo" ); // bad string s2( "bar" ); // good
编辑:为了回答你的问题“我可以总是使用智能指针代替原始指针吗?然后,不,你不能。如果(例如)你需要实现你自己的版本的operator new,你将不得不使它返回一个指针,而不是一个智能指针。
通常你不应该使用指针(智能或其他),如果你不需要它们。 最好使本地variables,类成员,向量元素和类似的项目的普通对象,而不是对象的指针。 (既然你来自Java,你可能试图用new
分配所有东西,这是不推荐的。)
这种方法(“ RAII ”)可以让您大部分时间不用担心指针。
当你必须使用指针时,这取决于情况,为什么你需要指针,但通常可以使用智能指针。 它可能并不总是 (粗体)是最好的select,但这取决于具体的情况。
一个不使用智能指针的好时机是在DLL的接口边界。 您不知道其他可执行文件是否将使用相同的编译器/库构build。 您的系统的DLL调用约定不会指定标准或TR1类,包括智能指针。
在可执行文件或库中,如果要表示指针对象的所有权,那么智能指针平均是最好的方法。 所以想要永远使用它们而不是原始的。 不pipe你是否真的可以随时使用它,都是另一回事。
对于一个具体的例子,当不去 – 假设你正在编写一个通用图的表示,顶点由对象表示,边由对象之间的指针表示。 通常的智能指针不会对你有帮助:graphics可以是循环的,没有特定的节点可以对其他节点的内存pipe理负责,所以共享和弱指针是不够的。 例如,你可能把所有东西放在一个向量中,并使用索引而不是指针,或者把所有东西都放在一个双转换器中,并使用原始指针。 如果你想,你可以使用shared_ptr
,但是除了开销之外,它不会添加任何内容。 或者你可以寻找标记扫描GC。
一个更为边缘的情况:我喜欢看函数通过指针或引用来获取参数, 并承诺不保留指针或引用它 ,而不是采取shared_ptr
,让你想知道他们是否可以在返回后保留引用,也许如果你再次修改这个referand,你会破坏某些东西,等等。不保留引用是通常没有明确logging的东西,这是不言而喻的。 也许它不应该,但它确实如此。 聪明的指针意味着所有权的东西,而虚假地暗示可能会令人困惑。 所以如果你的函数需要一个shared_ptr
,一定要logging它是否可以保留一个引用。
在很多情况下,我相信他们肯定是要走的路(清理代码较less,泄漏风险降低等)。 但是有一些额外的费用。 如果我写一些代码必须尽可能快(比如一个必须做一些分配和一个空闲的紧密循环),我可能不会使用一个智能指针来希望提高速度。 但是我怀疑在大多数情况下会有什么可衡量的差异。
一般来说,不可以一直使用智能指针。 例如,当您使用其他不使用智能指针的框架(如Qt)时,您也必须使用原始指针。
如果你正在处理一个资源,你应该总是使用RAII技术,在存储器的情况下,使用某种forms的智能指针(注意:智能,而不是shared_ptr
,select最适合您特定用途的智能指针案件)。 这是避免例外情况下泄漏的唯一方法。
当资源pipe理不通过指针处理时,仍然存在需要原始指针的情况。 特别是它们是具有可重置参考的唯一方法。 考虑保持对其生命期不能被明确处理的对象(成员属性,堆栈中的对象)的引用。 但这是一个非常特殊的情况,我只能看到一次真实的代码。 在大多数情况下,使用shared_ptr
是共享对象的更好方法。
是的,但我已经走了几个项目,而不使用智能指针或任何指针。 它是使用容器如deque,list,map等的好习惯。或者我可以使用引用。 而不是传入一个指针,我传递一个引用或const引用,它几乎总是不合逻辑的删除/释放一个引用,所以我从来没有问题在那里(通常我创build它们在堆栈{ Class class; func(class, ref2, ref3); }
我对智能指针很感兴趣:当很难知道何时会发生释放(比如说在一个try / catch块内部,或者在调用一个函数(甚至是一个构造函数)的函数内部) ,或者将更好的内存pipe理添加到返回代码中任何位置的函数中。 或者把指针放在容器中。
智能指针,但是,有一个成本,你可能不想支付你的程序。 如果内存pipe理很容易手工完成(“嗯,我知道当这个函数结束时,我需要删除这三个指针,我知道这个函数将运行到完成”),那么为什么浪费了计算机的周期它?
它是。 智能指针是旧的cocoa(触摸)生态系统的基石之一。 我相信它会不断影响新的。