为什么我要在这种情况下使用智能指针?

Why would I want to use a smart pointer in this situation?

我从未使用过任何类型的智能指针,但当主题是指针时,我几乎到处都在阅读它们。我确实理解在某些情况下智能指针比原始指针更好用,因为在某种程度上它们管理指针的所有权。但是,我仍然不知道,"I do not needing smart pointers for that"和"this is a case for smart pointers"之间的界线在哪里。

比方说,我有以下情况:

class A {
public:
    double get1(){return 1;}
    double get2(){return 2;}
};
class SomeUtilityClass {
public:
    SomeUtilityClass(A* a) : a(a) {}
    double getResult(){return a->get1() + a->get2();}
    void setA(A* a){a = a;}
private:
    A* a;
};
int main(int argc, char** argv) {
    A a;
    SomeUtilityClass u(&a);
    std::cout << u.getResult() << std::endl;
    A a2;
    u.setA(&a2);
    std::cout << u.getResult() << std::endl;
    return 0;
}

这当然是一个过于简单化的例子。我的意思是 SomeUtilityClass 不应该 "own" A 的一个实例(因为它只是一个实用程序 class),因此它只包含一个指针。

关于指针,我知道唯一可能出错的是:

智能指针如何帮助避免这个问题?在这种情况下使用智能指针还有什么其他好处?

PS:我知道有几个关于智能指针的问题(例如this one)。但是,如果您能告诉我对这个特定示例的影响,我将不胜感激。

如果SomeUtilityClass不拥有成员变量a,那么智能指针就没有意义。

您可以考虑引用成员,这将消除空指针的问题。

这取决于参数的创建和存储方式。如果您不拥有内存并且它可以静态或动态分配,那么原始指针是一个非常合理的解决方案 - 特别是如果您需要像示例中那样支持数据交换。另一种选择是使用 std::reference_wrapper,这将消除您的 nullptr 问题,同时保持相同的语义。

如果您持有指向某个共享资源的指针(即存储在 std::shared_ptr 某处)并且希望能够检查它是否已被删除,您可以持有 std::weak_ptr.

C++中默认的非拥有指针的表达方式是weak_ptr。要使用 weak_ptr 您需要使用 shared_ptr 作为所有权,因此在您的示例中您将使用

shared_ptr<A> owner(...)

而不是

A a

然后作为你 SomeUtilityClass 的私有指针成员,你使用弱指针:

weak_ptr<A> w;

并用shared_ptr初始化它:

SomeUtilityClass(shared_ptr<A> o) : w(o) {}

但是,您不能直接使用 weak_ptr,因为 shared_ptr 可能超出范围并且您的弱指针无法再指向任何东西。使用前需要锁定:

shared_ptr<A> locked = w.lock();

如果拥有指针不再管理对象,locked 指针将为空,因为例如它超出了范围。如果它不为空,你可以使用它,然后它会超出范围自动释放对象的锁。

shared_ptrweak_ptr 在 C++11 的标准库中可用,在旧编译器的 Boost 中可用。

有不同类型的智能指针。在你的情况下,很明显智能指针并不是真正需要的,但它仍然可以提供一些好处。

SomeUtilityClass can be instantiated with a null pointer

这个问题可能最好通过检查构造函数来解决,在您获得 NULL 指针作为参数的情况下抛出异常或以其他方式指示错误。我很难想象智能指针会有什么帮助,除非您使用不接受 NULL 的特定智能指针 class,所以它已经为您进行了检查。

The object pointed to may be deleted/go out of scope, without the SomeUtilityClass noticing it

这个问题实际上可以用一种特殊类型的智能指针来解决,但是需要被指向的对象以某种方式支持销毁通知。一个这样的例子是Qt库中的QPointer class,它只能指向QObject个实例,删除时会通知它,所以当对象被删除时智能指针自动变为NULL .不过,这种方法存在一些问题:

  1. 每次通过智能指针访问对象时都需要检查是否为NULL。
  2. 如果智能指针指向class的一个实例,比如说MyClass扩展class执行删除通知( QObject 在 Qt 的情况下),你会得到奇怪的结果:通知智能指针的是 QObject 的析构函数,所以当 MyClass 析构函数已经开始它的肮脏工作时你可能会访问它,所以对象已部分销毁,但指针尚未为 NULL,因为销毁仍在进行中。

为了这个答案的目的,我将 setA 重新定义为:

void setA(A* new_a){a = new_a;}

考虑:

// Using your SomeUtilityClass

int main() {
  A a;
  SomeUtilityClass u(&a);
  // We define a new scope, just because:
  {
    A b;
    u.setA(&b);
  }
  std::cout << u.getResult() << '\n';
  return 0;
}

范围完成后,SomeUtilityClass 有一个悬空指针,getResult() 调用未定义的行为。请注意,这不能通过参考来解决:您仍然会得到一个悬空的。

现在考虑使用智能指针的版本:

class SomeUtilityClass {
public:
    SomeUtilityClass(std::shared_ptr<A>& a) : a{a} {}
    double getResult(){return a->get1() + a->get2();}
    void setA(std::shared_ptr<A>& new_a){a = new_a;}
private:
    std::shared_ptr<A> a;
};

int main() {
  std::shared_ptr<A> a{new A};
  SomeUtilityClass u{a};
  // We define a new scope, just because:
  {
    std::shared_ptr<A> b{new A};
    u.setA(b);
  }
  std::cout << u.getResult() << '\n';
  return 0;
}

因为您拥有共享所有权,所以无法获得悬挂指针。 b指向的内存会像往常一样被删除,但只有在u被销毁(或者它的指针被改变)之后才会被删除。

恕我直言,在大多数情况下你应该使用智能指针(即使一开始它似乎没有多大意义)。它使维护更容易。仅在实际需要它们的特定代码中使用原始指针,并且 encapsulate/isolate 尽可能多地使用此代码。