为类成员使用智能指针

mic*_*elk 146 c++ smart-pointers shared-ptr unique-ptr c++11

我无法理解智能指针在C++ 11中作为类成员的用法.我已经阅读了很多关于智能指针的内容,我想我确实理解了如何unique_ptrshared_ptr/ weak_ptr一般的工作.我不明白的是真正的用法.似乎每个人都建议使用unique_ptr几乎所有的时间.但是,我将如何实现这样的事情:

class Device {
};

class Settings {
    Device *device;
public:
    Settings(Device *device) {
        this->device = device;
    }

    Device *getDevice() {
        return device;
    }
};    

int main() {
    Device *device = new Device();
    Settings settings(device);
    // ...
    Device *myDevice = settings.getDevice();
    // do something with myDevice...
}
Run Code Online (Sandbox Code Playgroud)

假设我想用智能指针替换指针.A unique_ptr不会起作用getDevice(),对吧?那是我使用的时间shared_ptrweak_ptr?没办法用unique_ptr?对我来说似乎对大多数情况shared_ptr更有意义,除非我在一个非常小的范围内使用指针?

class Device {
};

class Settings {
    std::shared_ptr<Device> device;
public:
    Settings(std::shared_ptr<Device> device) {
        this->device = device;
    }

    std::weak_ptr<Device> getDevice() {
        return device;
    }
};

int main() {
    std::shared_ptr<Device> device(new Device());
    Settings settings(device);
    // ...
    std::weak_ptr<Device> myDevice = settings.getDevice();
    // do something with myDevice...
}
Run Code Online (Sandbox Code Playgroud)

这是要走的路吗?非常感谢!

And*_*owl 190

A unique_ptr不会起作用getDevice(),对吧?

不,不一定.这里重要的是为您的对象确定适当的所有权策略Device,即谁将成为您的(智能)指针所指向的对象的所有者.

它会单独作为Settings对象的实例吗?将对象必须被销毁时自动的对象被销毁,还是应该活得比那个对象?DeviceSettings

在第一种情况下,std::unique_ptr是你需要的,因为它是Settings唯一的(唯一的)指向对象的所有者,也是唯一负责它的破坏的对象.

在这个假设下,getDevice()应该返回一个简单的观察指针(观察指针是不保持指向对象存活的指针).最简单的观察指针是原始指针:

#include <memory>

class Device {
};

class Settings {
    std::unique_ptr<Device> device;
public:
    Settings(std::unique_ptr<Device> d) {
        device = std::move(d);
    }

    Device* getDevice() {
        return device.get();
    }
};

int main() {
    std::unique_ptr<Device> device(new Device());
    Settings settings(std::move(device));
    // ...
    Device *myDevice = settings.getDevice();
    // do something with myDevice...
}
Run Code Online (Sandbox Code Playgroud)

[ 注1: 你可能想知道为什么我在这里使用原始指针,当每个人都在告诉原始指针是坏的,不安全的和危险的.实际上,这是一个宝贵的警告,但重要的是将它放在正确的上下文中:原始指针在用于执行手动内存管理时是不好的,即通过new和分配和释放对象delete.当纯粹用作实现引用语义和传递非拥有,观察指针的手段时,原始指针中没有任何本质上的危险,除了可能因为人们应该注意不要取消引用悬空指针这一事实.- 结束注1 ]

[ 注2: 正如评论中出现的那样,在这种特殊情况下,所有权是唯一的,并且始终保证所拥有的对象存在(即内部数据成员device永远不会存在nullptr),函数getDevice()可能(也许应该)返回引用而不是指针.虽然这是事实,我决定在这里返回原始指针,因为我的意思是这是一个简单的答案,人们可以推广到那里的情况device可能是nullptr,并表明原始指针都行,只要一个不使用它们手动内存管理.- 结束注2 ]


当然,如果您的Settings对象具有设备的独占所有权,则情况完全不同.例如,如果Settings对象的破坏不应意味着对尖头Device物体的破坏,则可能是这种情况.

这是只有你作为你的程序设计师才能分辨出来的东西; 从您提供的示例中,我很难判断是否是这种情况.

为了帮助你解决这个问题,你可能会问自己除了Settings那些有权保持Device对象存活的任何其他对象,只要它们有一个指向它的指针,而不仅仅是被动的观察者.如果确实如此,那么您需要一个共享所有权政策,这是std::shared_ptr提供:

#include <memory>

class Device {
};

class Settings {
    std::shared_ptr<Device> device;
public:
    Settings(std::shared_ptr<Device> const& d) {
        device = d;
    }

    std::shared_ptr<Device> getDevice() {
        return device;
    }
};

int main() {
    std::shared_ptr<Device> device = std::make_shared<Device>();
    Settings settings(device);
    // ...
    std::shared_ptr<Device> myDevice = settings.getDevice();
    // do something with myDevice...
}
Run Code Online (Sandbox Code Playgroud)

注意,这weak_ptr是一个观察指针,而不是一个拥有指针 - 换句话说,如果指向对象的所有其他拥有指针都超出范围,它就不会使指向的对象保持活动状态.

的优势weak_ptr比普通生指针,你可以放心地告诉是否weak_ptr晃来晃去与否(即它是否指向有效的对象,如果对象原来指向已被破坏).这可以通过调用对象expired()上的成员函数来完成weak_ptr.

  • @Purrformance:虽然这会阻止客户移动(除非客户是一个热衷于'const_cast'的疯狂科学家),我个人不会这样做.它公开了一个实现细节,即所有权是唯一的,并通过`unique_ptr`实现.我这样看待事情:如果你想/需要传递/返回所有权,传递/返回一个智能指针(`unique_ptr`或`shared_ptr`,具体取决于所有权的类型).如果您不想/需要传递/返回所有权,请使用(正确的`const`-qualified)指针或引用,主要取决于参数是否为null. (7认同)
  • 在第一种情况下,让`getDevice()`返回引用可能更好,不是吗?因此调用者不必检查`nullptr`. (5认同)
  • @chico:不确定你的意思.`auto myDevice = settings.getDevice()`将创建一个名为`myDevice`的`Device`类型的新实例,并从`getDevice()`返回的引用引用的实例中复制构造它.如果你想要`myDevice`作为参考,你需要做`auto&myDevice = settings.getDevice()`.因此,除非我遗漏了某些东西,否则我们将回到与不使用`auto`相同的情况. (5认同)
  • @LKK:是的,没错.`weak_ptr`始终是原始观察指针的替代品.从某种意义上说它更安全,因为你可以在解除引用之前检查它是否悬空,但它也带来了一些开销.如果你可以很容易地保证你不会取消引用悬空指针,那么你应该没心观察原始指针 (3认同)
  • @vobject:此外,返回引用可能会让用户错误地编写类似:“Device myDevice = settings.getDevice();”的内容,而实际上他们的意思是“Device&amp; myDevice = settings.getDevice();”(忘记了`&amp;`),返回指针时不会发生这种情况。我还觉得这里不需要检查“nullptr”,因为该函数总是保证返回一个有效的指针。 (2认同)
  • 返回观察原始指针意味着多重责任。指针语义意味着“nullptr”是一个可能且有效的结果。只有通过约定或显式文档才不需要检查“nullptr”。我认为 (`const`) 引用应该是默认的选择,如果您还希望表示该类型是可选的,则可以使用观察指针作为替代方案。 (2认同)
  • @Purrformance:因为你不想放弃对象的所有权 - 将一个可修改的`unique_ptr`交给客户端会打开客户端从它移动的可能性,从而获得所有权并留给你null(唯一的)指针. (2认同)