总是使用智能指针是一个好习惯吗? [英] Is it a good practice to always use smart pointers?
问题描述
我发现智能指针比原始指针更舒服。因此,始终使用智能指针是个好主意吗? (请注意,我是从Java背景,因此不太喜欢显式内存管理的想法,所以除非智能指针有一些严重的性能问题,我想坚持使用它们。)
注意:尽管我来自Java背景,但我理解了智能指针的实现和RAII的概念。所以你可以在发布答案时从我这边得到这个知识。我使用静态分配几乎无处不在,只在必要时使用指针。我的问题只是:我可以使用智能指针代替原始指针吗
鉴于多次编辑,我有一个印象,一个全面的总结将是有用的。
1。何时不
有两种情况不应使用智能指针。
首先是完全相同的情况,你不应该使用 C ++
实际上。 IE:DLL边界如果你不提供源代码给客户端。让我们说anecdotal。
第二个发生得更频繁:智能管理器意味着所有权。您可以使用指针指向现有资源,而不管理其生命周期,例如:
void notowner(const std :: string& name)
{
Class * pointer(0);
if(name ==cat)
pointer = getCat();
else if(name ==dog)
pointer = getDog();
if(pointer)doSomething(* pointer);
}
此示例受到约束。但是指针在语义上不同于引用,因为它可能指向一个无效位置(空指针)。在这种情况下,不要使用智能指针代替它,这是完全正确的,因为你不想管理对象的生命周期。
2 。智能管理员
除非您正在编写智能管理员类,否则如果您使用关键字
这是一个有争议的观点,有缺陷的代码,我不再采取机会。所以,如果你写 new
,你需要一个智能管理器为新分配的内存。现在你需要它。
这并不意味着你不是一个程序员!相反,重用已被证明有效的代码,而不是重复发明轮子是一个关键技能。
现在,真正的困难开始:哪个智能管理器?
3。智能指针
有各种智能指针,有各种特征。
跳过 std :: auto_ptr
你应该避免(它的副本语义被拧了)。
-
unique_ptr
$ c>:一些开销(引用计数),可以复制。
< $
scoped_ptr
:无开销,无法复制或移动。 通常,尝试使用 scoped_ptr
或 unique_ptr
。如果你需要几个业主试图改变设计。如果你不能改变设计,真的需要几个所有者,使用 shared_ptr
,但要小心引用循环,应该使用 weak_ptr
中间某处。
4。智能容器
许多智能指针并不意味着被复制,因此它们与STL容器的使用在一定程度上受到了影响。
不使用 shared_ptr
及其开销,请使用指针容器。它们模拟了经典STL容器的接口,但存储了他们自己的指针。
5。滚动您自己的
在某些情况下,您可能希望滚动自己的智能管理器。请检查您是否错过了您之前使用的库中的某些功能。
在存在异常的情况下编写智能管理器非常困难。你通常不能假定内存是可用的( new
可能会失败)或复制构造函数
有<$ c $
在某种程度上,忽略 std :: bad_alloc
异常,并强加复制构造函数
的一些帮助器不会失败...毕竟,这是 boost :: shared_ptr
用于其删除程序 D
模板参数。
但我不推荐,特别是对于初学者。这是一个棘手的问题,你现在不太可能注意到错误。
6。示例
//为了简短的代码,避免使用真实的代码;)
using namespace促进;
//示例类
//是的,clone返回一个原始指针...
//它会给调用者带来如何包装的负担
//它遵循
中描述的`Cloneable'概念$ bool指针容器库链接
struct可克隆
{
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);
} //内存释放这里
//移动的语义的例子
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超出范围,释放内存
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()//有意义的语义,它被取消引用
);
} //当vec超出范围,它清除一切;)
I find smart pointers to be a lot more comfortable than raw pointers. So is it a good idea to always use smart pointers? ( Please note that I am from Java background and hence don't much like the idea of explicit memory management. So unless there are some serious performance issues with smart pointers, I'd like to stick with them. )
Note: Though I come from Java background, I understand the implementation of smart pointers and the concepts of RAII quite well. So you can take this knowledge for granted from my side when posting an answer. I use static allocation almost everywhere and use pointers only when necessary. My question is merely: Can I always use smart pointers in place of raw pointers???
Given the several edits, I have the impression that a comprehensive summary would be useful.
1. When not to
There are two situations where you should not use smart pointers.
The first is the exact same situation in which you should not use a C++
class in fact. IE: DLL boundary if you do not offer the source code to the client. Let say anecdotal.
The second happens much more often: smart manager means ownership. You may use pointers to point at existing resources without managing their lifetime, for example:
void notowner(const std::string& name)
{
Class* pointer(0);
if (name == "cat")
pointer = getCat();
else if (name == "dog")
pointer = getDog();
if (pointer) doSomething(*pointer);
}
This example is constrained. But a pointer is semantically different from a reference in that it may point to an invalid location (the null pointer). In this case, it's perfectly fine not to use a smart pointer in its stead, because you don't want to manage the lifetime of the object.
2. Smart managers
Unless you are writing a smart manager class, if you use the keyword delete
you are doing something wrong.
It is a controversial point of view, but after having reviewed so many example of flawed code, I don't take chances any longer. So, if you write new
you need a smart manager for the newly allocated memory. And you need it right now.
It does not mean you are less of a programmer! On the contrary, reusing code that has been proved to work instead of reinventing the wheel over and over is a key skill.
Now, the real difficulty start: which smart manager ?
3. Smart pointers
There are various smart pointers out of there, with various characteristics.
Skipping std::auto_ptr
which you should generally avoid (its copy semantic is screwed).
scoped_ptr
: no overhead, cannot be copied or moved.unique_ptr
: no overhead, cannot be copied, can be moved.shared_ptr
/weak_ptr
: some overhead (reference counting), can be copied.
Usually, try to use either scoped_ptr
or unique_ptr
. If you need several owners try to change the design. If you can't change the design and really need several owners, use a shared_ptr
, but beware of references cycles that ought to be broken using a weak_ptr
somewhere in the midst.
4. Smart containers
Many smart pointers are not meant to be copied, therefore their use with the STL containers are somewhat compromised.
Instead of resorting to shared_ptr
and its overhead, use smart containers from the Boost Pointer Container. They emulate the interface of classic STL containers but store pointers they own.
5. Rolling your own
There are situations when you may wish to roll your own smart manager. Do check that you did not just missed some feature in the libraries your are using beforehand.
Writing a smart manager in the presence of exceptions is quite difficult. You usually cannot assume that memory is available (new
may fail) or that Copy Constructor
s have the no throw
guarantee.
It may be acceptable, somewhat, to ignore the std::bad_alloc
exception and impose that Copy Constructor
s of a number of helpers do not fail... after all, that's what boost::shared_ptr
does for its deleter D
template parameter.
But I would not recommend it, especially for a beginner. It's a tricky issue, and you're not likely to notice the bugs right now.
6. Examples
// 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 ;)
这篇关于总是使用智能指针是一个好习惯吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!