什么时候特别适合使用智能指针,如果不鼓励使用 C++ 中的指针,为什么还要使用智能指针?举个例子:
class Object { }
smart_pointer < Object > pInstance;
//wherein I can use the very simple way
Object instance;
什么时候特别适合使用智能指针,如果不鼓励使用 C++ 中的指针,为什么还要使用智能指针?举个例子:
class Object { }
smart_pointer < Object > pInstance;
//wherein I can use the very simple way
Object instance;
当您需要维护对象的所有权时,智能指针非常有用。使用它们将确保适当的销毁。当指针被视为引用时,使用智能指针有时会更糟(例如,在性能方面)。
指针是 C++ 的重要组成部分,但在 C++11 中引入了移动语义(这基本上使之成为unique_ptr
可能),使用它们变得更加容易。这就是为什么在现代代码中你应该总是使用std::unique_ptr
或者std::shared_ptr
如果可用的话。
编辑:你问了一个例子,指针可能有益于使用。我能想到的最常见的问题是某个系统的可选组件。该组件将使用大量内存,因此您不想总是分配它,也不想控制它的分配(因此它本身不能处于“空”状态,即不可为空)。Boost.Optional 和 C++14-ishstd::optional
分配 T 的 POD-ish 大小的内存,所以它们不会这样做。使用指针,您可以选择分配该内存:
class Outer {
std::unique_ptr<BigInner> optionalComponent;
public:
void initializeOptionalComponent() {
optionalComponent = new BigInner();
}
void useOptionalComponent() {
if (!optionalComponent)
// handle the error
// operate
}
};
这将解决问题,但会引入另一个明显的问题: optionalComponent 可以为 null,这要求所有使用它的函数始终检查有效状态。如果它是一个普通的按值成员,它将(或至少应该)始终处于有效状态。因此,如果您不需要指针,请不要使用它,使用vector<MyClass>
和普通成员。
无论如何,在这种情况下使用智能指针可以让您保持零规则;您不必编写析构函数、复制构造函数或赋值运算符,并且该类将安全地运行。
快速回答:智能指针对(尤其是)有用
执行 RAII
指针带来的一个问题并导致程序中的崩溃(在许多方面,一些明显的,一些扭曲的)是你对它们下面的内存负责。这意味着当您动态分配内存时(通过new
),您对这块内存负责,并且一定不要忘记调用delete
. 这意味着它会发生,更糟糕的是,即使你没有忘记,删除语句也永远不会到达。考虑这段代码:
void function(){
MyClass* var = new MyClass;
//Do something
delete var;
}
现在,如果这个函数在到达 delete 语句之前抛出异常,指针将不会被删除......内存泄漏!
RAII 是一种避免这种情况的方法:
void function(){
std::shared_ptr<MyClass> var(new MyClass);
//Do something
//No need to delete anything
}
指针由对象持有,并在其析构函数中删除。与前面的代码不同的是,如果函数抛出异常,则会调用共享指针的析构函数,从而删除指针,避免内存泄漏。
RAII 利用了这样一个事实,即当局部变量超出范围时,它的 dtor 会被调用。
管理指针所有权
请注意我在前面的示例中使用了哪个智能指针。这std::shared_ptr
是一个智能指针,当您传递指针时很有用。如果您的代码的许多部分需要指向同一个对象的指针,那么决定应该在哪里删除它可能会很棘手。您可能想在某处删除指针,但如果您的代码的另一部分正在使用它怎么办?它会导致访问已删除的指针,这是不可取的!std::shared_ptr
有助于防止这种情况。当你传递一个 shared_ptr 时,它会跟踪有多少部分代码引用了它。当不再有对该指针的任何引用时,该指针将删除内存。换句话说,当没有人再使用指针时,它就会被安全地删除。
还有其他类型的智能指针可以解决其他问题,例如std::unique_ptr
提供一个指针,该指针是其下方指针的唯一所有者。
注意 - 关于多态性的小解释
您需要使用多态性的指针。如果你有一个抽象类MyAbstract
(也就是说,它至少有一个虚拟类,比如说doVirtual()
),它就不能被实例化。以下代码:
MyAbstract var;
不会编译,你会Can't instantiate abstract class
从你的编译器中得到类似的东西。
但这是合法的(两者都ImplA
公开ImplB
继承自MyAbstract
:
MyAbstract* varA = new ImplA;
MyAbstract* varB = new ImplB;
varA->doVirtual(); //Will call ImplA implementation
varB->doVirtual(); //Will call ImplB implementation
delete varA;
delete varB;