我发现智能指针比原始指针更舒服.那么总是使用智能指针是一个好主意吗?(请注意,我来自Java背景,因此不太喜欢显式内存管理的想法.所以除非智能指针存在一些严重的性能问题,否则我想坚持使用它们.)
注意:虽然我来自Java背景,但我非常了解智能指针的实现和RAII的概念.因此,在发布答案时,您可以将这些知识视为我的理所当然.我几乎到处都使用静态分配,只在必要时才使用指针.我的问题仅仅是:我是否可以使用智能指针代替原始指针?
Mat*_* M. 72
鉴于经过多次编辑,我的印象是全面的总结会很有用.
1.什么时候不去
有两种情况你不应该使用智能指针.
第一个是完全相同的情况,你C++实际上不应该使用类.IE:DLL边界,如果您不向客户端提供源代码.让我们说轶事.
第二种情况经常发生:智能经理意味着所有权.您可以使用指针指向现有资源而无需管理其生命周期,例如:
void notowner(const std::string& name)
{
Class* pointer(0);
if (name == "cat")
pointer = getCat();
else if (name == "dog")
pointer = getDog();
if (pointer) doSomething(*pointer);
}
Run Code Online (Sandbox Code Playgroud)
这个例子受到限制.但是指针在语义上与引用的不同之处在于它可能指向无效位置(空指针).在这种情况下,完全没有使用智能指针,因为您不想管理对象的生命周期.
2.聪明的经理
除非您正在编写智能管理器类,否则如果您使用该关键字,则表明您 delete 做错了.
这是一个有争议的观点,但在审查了这么多有缺陷代码的例子后,我不再冒险了.所以,如果你写,new你需要一个智能管理器来为新分配的内存.而你现在需要它.
这并不意味着你不是一个程序员!相反,重复使用已被证明有效的代码而不是一遍又一遍地重新发明轮子是一项关键技能.
现在,真正的困难开始:哪位智能经理?
3.智能指针
有各种智能指针,具有各种特征.
跳过std::auto_ptr你通常应该避免的(它的复制语义被搞砸了).
scoped_ptr:没有开销,无法复制或移动.unique_ptr:没有开销,无法复制,可以移动.shared_ptr/ weak_ptr:一些开销(引用计数),可以复制.通常,尝试使用scoped_ptr或者unique_ptr.如果您需要多个所有者尝试更改设计.如果您无法更改设计并且确实需要多个所有者,请使用a shared_ptr,但要注意使用weak_ptr中间某处应该打破的引用周期.
4.智能容器
许多智能指针并不意味着被复制,因此它们与STL容器的使用有些妥协.
而不是诉诸shared_ptr和开销,使用Boost指针容器中的智能容器.它们模拟经典STL容器的接口,但存储它们拥有的指针.
5.滚动你自己
在某些情况下,您可能希望推出自己的智能管理器.请检查您是否错过了之前正在使用的库中的某些功能.
在存在异常的情况下编写智能管理器非常困难.您通常不能假设内存可用(new可能失败)或者Copy Constructor有no throw保证.
在某种程度上,忽略std::bad_alloc异常并强制Copy Constructor许多帮助程序的s不会失败可能是可以接受的...毕竟,这就是boost::shared_ptr它的删除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 ;)
Run Code Online (Sandbox Code Playgroud)
小智 17
智能指针确实执行显式内存管理,如果您不理解它们是如何操作的,那么在使用C++编程时,您将陷入困境.请记住,内存并不是他们管理的唯一资源.
但是要回答你的问题,你应该更喜欢智能指针作为解决方案的第一个近似值,但可能准备在必要时抛弃它们.在可以避免时,不应该使用指针(或任何类型)或动态分配.例如:
string * s1 = new string( "foo" ); // bad
string s2( "bar" ); // good
Run Code Online (Sandbox Code Playgroud)
编辑:回答你的问题"我是否总能使用智能指针代替原始指针???然后,不,你不能.如果(例如)你需要实现自己的operator new版本,你将不得不让它返回一个指针,而不是一个智能指针.
不使用智能指针的好时机是在DLL的接口边界.您不知道是否将使用相同的编译器/库构建其他可执行文件.系统的DLL调用约定不会指定标准或TR1类的外观,包括智能指针.
在可执行文件或库中,如果要表示指针对象的所有权,那么智能指针通常是执行它的最佳方式.因此,想要始终使用它们而不是原始它是好的.你是否真的可以随时使用它们是另一回事.
有关具体示例,请不要 - 假设您正在编写通用图的表示,其中顶点由对象表示,边由对象之间的指针表示.通常的智能指针对你没有帮助:图形可以是循环的,没有特定的节点可以负责其他节点的内存管理,因此共享和弱指针是不够的.例如,您可以将所有内容放在向量中并使用索引而不是指针,或者将所有内容放在双端队列中并使用原始指针.您可以shared_ptr根据需要使用,但除了开销之外不会添加任何内容.或者你可以寻找标记扫描GC.
一个更边缘的情况:我更喜欢看到函数通过指针或引用获取参数,并承诺不保留指针或引用它,而不是采取shared_ptr并让你想知道它们是否可能在它们返回后保留引用,也许是你再次修改了referand你会破坏某些东西,等等.不保留引用是经常没有明确记录的东西,它不言而喻.也许它不应该,但确实如此.智能指针暗示了所有权,并错误地暗示这可能令人困惑.因此,如果您的函数采用a shared_ptr,请务必记录它是否可以保留引用.
在许多情况下,我相信它们绝对是可行的方法(清理代码不那么凌乱,泄漏风险降低等).然而,有一些非常轻微的额外费用.如果我正在编写一些必须尽可能快的代码(比如一个必须进行一些分配和免费的紧密循环),我可能不会使用智能指针,希望能够提高速度.但我怀疑它会在大多数情况下产生任何可衡量的差异.