我发现智能指针比原始指针更舒服。那么总是使用智能指针是个好主意吗? (请注意,我来自Java背景,因此不太喜欢显式内存管理的想法。所以除非智能指针存在一些严重的性能问题,否则我会坚持使用它们。)
注意:虽然我来自Java背景,但我非常了解智能指针的实现和RAII的概念。因此,在发布答案时,您可以将这些知识视为理所当然。我几乎到处都使用静态分配,只在必要时才使用指针。我的问题仅仅是:我是否可以使用智能指针代替原始指针???
答案 0 :(得分:68)
鉴于经过多次修改,我的印象是全面的摘要很有用。
<强> 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);
}
此示例受到限制。但是指针在语义上与引用的不同之处在于它可能指向无效位置(空指针)。在这种情况下,完全没有使用智能指针,因为您不想管理对象的生命周期。
<强> 2。聪明的经理
除非您正在撰写智能经理课程,否则如果您使用关键字 delete
,那么您做错了。
这是一个有争议的观点,但在审查了这么多有缺陷代码的例子后,我不再冒险了。所以,如果你写new
,你需要一个智能管理器来为新分配的内存。你现在需要它。
这并不意味着你不再是程序员!相反,重复使用已被证明有效的代码而不是一遍又一遍地重新发明轮子是一项关键技能。
现在,真正的困难开始了:哪位智能经理?
第3。智能指针
有各种智能指针,具有各种特征。
跳过你应该避免的std::auto_ptr
(它的复制语义被搞砸了)。
scoped_ptr
:无开销,无法复制或移动。unique_ptr
:没有开销,无法复制,可以移动。shared_ptr
/ weak_ptr
:可以复制一些开销(引用计数)。通常,请尝试使用scoped_ptr
或unique_ptr
。如果您需要多个所有者尝试更改设计。如果您无法更改设计并且确实需要多个所有者,请使用shared_ptr
,但要注意在中间某处使用weak_ptr
应该打破的引用周期。
<强> 4。智能容器
许多智能指针并不意味着被复制,因此它们与STL容器的使用有些妥协。
使用Boost Pointer Container中的智能容器,而不是诉诸shared_ptr
及其开销。他们模仿经典STL容器的接口,但存储他们拥有的指针。
<强> 5。滚动你自己的
在某些情况下,您可能希望推出自己的智能管理器。请检查您是否错过了之前正在使用的库中的某些功能。
在出现异常的情况下编写智能管理器非常困难。您通常无法假设内存可用(new
可能失败)或Copy Constructor
具有no throw
保证。
在某种程度上,忽略std::bad_alloc
例外可能是可以接受的,并且强制许多帮助者的Copy Constructor
不会失败......毕竟,这就是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 ;)
答案 1 :(得分:18)
智能指针执行执行显式内存管理,如果您不理解它们是如何操作的,那么在使用C ++编程时,您将陷入困境。请记住,内存不是他们管理的唯一资源。
但是要回答你的问题,你应该更喜欢智能指针作为解决方案的第一个近似值,但可能准备在必要时抛弃它们。在可以避免时,不应该使用指针(或任何类型)或动态分配。例如:
string * s1 = new string( "foo" ); // bad
string s2( "bar" ); // good
编辑:回答您的补充问题“我是否可以使用智能指针代替原始指针???然后,不,你不能。如果(例如)你需要实现你的自己的operator new版本,你必须让它返回一个指针,而不是一个智能指针。
答案 2 :(得分:13)
通常你不应该使用指针(智能或其他),如果你不需要它们。最好使局部变量,类成员,向量元素和类似项正常对象而不是指向对象的指针。 (因为你来自Java,你可能很想用new
来分配所有东西,这是不推荐的。)
这种方法(“RAII”)可以避免在大多数情况下担心指针。
当你必须使用指针时,它取决于情况以及为什么你需要指针,但通常可以使用智能指针。它可能不是总是(以粗体显示)是最佳选择,但这取决于具体情况。
答案 3 :(得分:9)
使用智能指针的好时机是在DLL的接口边界。您不知道是否将使用相同的编译器/库构建其他可执行文件。系统的DLL调用约定不会指定标准或TR1类的外观,包括智能指针。
在可执行文件或库中,如果要表示指针对象的所有权,那么智能指针通常是最好的方法。因此,想要始终使用它们而不是原始它是好的。你是否真的可以随时使用它们是另一回事。
具体示例何时不 - 假设您正在编写通用图的表示,其中顶点由对象表示,边由对象之间的指针表示。通常的智能指针对你没有帮助:图形可以是循环的,没有特定的节点可以负责其他节点的内存管理,因此共享和弱指针是不够的。例如,您可以将所有内容放在向量中并使用索引而不是指针,或者将所有内容放在双端队列中并使用原始指针。如果需要,可以使用shared_ptr
,但除了开销之外不会添加任何内容。或者你可以寻找标记扫描GC。
一个更边缘的情况:我更喜欢看到函数通过指针或引用获取参数,并承诺不保留指针或对它的引用,而不是采用shared_ptr
和让你想知道他们返回后是否可以保留一个引用,也许如果你再次修改referand你会破坏某些东西,等等。不保留引用是经常没有明确记录的东西,它不言而喻。也许它不应该,但确实如此。智能指针暗示了所有权,并错误地暗示这可能令人困惑。因此,如果您的函数采用shared_ptr
,请务必记录它是否可以保留引用。
答案 4 :(得分:6)
在许多情况下,我认为它们绝对是可行的方法(清理代码不那么凌乱,泄漏风险降低等)。然而,有一些非常轻微的额外费用。如果我正在编写一些必须尽可能快的代码(比如一个必须进行一些分配和免费的紧密循环),我可能不会使用智能指针,希望能够提高速度。但我怀疑它会在大多数情况下产生任何可衡量的差异。
答案 5 :(得分:4)
一般情况下,总是不能使用智能指针。例如,当您使用其他不使用智能指针的框架(如Qt)时,您也必须使用原始指针。
答案 6 :(得分:2)
如果你正在处理资源,你应该总是使用RAII技术,在内存的情况下意味着使用某种形式或另一种智能指针(注意:智能,而不是shared_ptr
),选择智能指针最适合您的特定用例)。这是在存在异常情况下避免泄漏的唯一方法。
当没有通过指针处理资源管理时,仍然存在需要原始指针的情况。特别是它们是具有可重置引用的唯一方式。考虑保持对无法显式处理其生命周期的对象的引用(成员属性,堆栈中的对象)。但这是一个非常具体的案例,我在实际代码中只见过一次。在大多数情况下,使用shared_ptr
是共享对象的更好方法。
答案 7 :(得分:2)
我对智能指针的看法:很难知道何时可能发生释放(比如在try / catch块内部,或者在调用函数的函数内部(或者甚至是构造函数!)可能会让你退出当前函数),或者为在代码中到处返回的函数添加更好的内存管理。或者将指针放在容器中。
然而,智能指针的成本可能不希望在整个程序中支付。如果内存管理很容易手工完成(“嗯,我知道当这个函数结束时我需要删除这三个指针,我知道这个函数将运行完成”),那么为什么浪费了计算机的循环呢?它?
答案 8 :(得分:1)
是的但我没有使用智能指针或任何指针就已经进行了几个项目。使用诸如deque,list,map等容器的好习惯。或者我尽可能使用引用。我没有传入指针而是传递引用或const引用,而且它几乎总是不合逻辑地删除/释放引用,所以我从来没有问题(通常我通过写{ Class class; func(class, ref2, ref3); }
答案 9 :(得分:0)
是的。智能指针是旧Cocoa(Touch)生态系统的基石之一。我相信它会不断影响新的。