【问题标题】:Using smart pointers for class members对类成员使用智能指针
【发布时间】:2013-03-16 22:38:32
【问题描述】:

我无法理解智能指针在 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...
}

假设我想用智能指针替换指针。 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...
}

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

【问题讨论】:

  • 它有助于真正清楚生命周期、所有权和可能的空值。例如,已经将device 传递给settings 的构造函数,您希望仍然能够在调用范围内引用它,还是只能通过settings?如果是后者,unique_ptr 很有用。另外,你有没有getDevice()的返回值为null的场景。如果没有,只需返回一个参考。
  • 是的,shared_ptr 在 8/10 的情况下是正确的。其他 2/10 在 unique_ptrweak_ptr 之间分配。另外,weak_ptr 通常用于中断循环引用;我不确定您的用法是否正确。
  • 首先,您希望device 数据成员拥有什么所有权?您首先必须做出决定。
  • 好的,我知道作为调用者我可以使用unique_ptr 代替,并在调用构造函数时放弃所有权,如果我知道我现在不再需要它的话。但作为Settings 类的设计者,我不知道调用者是否也想保留引用。也许该设备将在许多地方使用。好吧,也许这正是你的意思。在那种情况下,我不会是唯一的所有者,我猜那时我会使用 shared_ptr。并且:所以智能点确实替换了指针,但不是引用,对吧?
  • this->device = device;也使用初始化列表。

标签: c++ c++11 shared-ptr smart-pointers unique-ptr


【解决方案1】:

unique_ptr 不会因为 getDevice() 而工作,对吧?

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

它会是Settings 对象的实例单独吗?当Settings 对象被销毁时,Device 对象是否必须自动销毁,还是应该比该对象更长寿?

在第一种情况下,std::unique_ptr 是您所需要的,因为它使 Settings 成为指向对象的唯一(唯一)所有者,并且是唯一负责销毁该对象的对象。

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

#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...
}

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

[注意 2: 正如它在 cmets 中出现的那样,在所有权是唯一的这种特殊情况下并且始终保证拥有的对象存在(即内部数据成员device 永远不会是nullptr),函数getDevice() 可以(也许应该)返回一个引用而不是一个指针。虽然这是真的,但我决定在这里返回一个原始指针,因为我的意思是这是一个简短的答案,可以概括为device 可能是nullptr 的情况,并表明只要原始指针是可以的人们不会将它们用于手动内存管理。 - END NOTE 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...
}

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

weak_ptr 相对于常规原始指针的优势在于,您可以安全地判断 weak_ptr 是否悬空(即它是否指向有效对象,或者该对象是否原指已被破坏)。这可以通过在weak_ptr 对象上调用expired() 成员函数来完成。

【讨论】:

  • @LKK:是的,正确的。 weak_ptr 始终是原始观察指针的替代方案。从某种意义上说,它更安全,因为您可以在取消引用之前检查它是否悬空,但它也会带来一些开销。如果您可以轻松保证不会取消引用悬空指针,那么您应该可以观察原始指针
  • 在第一种情况下,让getDevice() 返回一个引用可能会更好,不是吗?所以调用者不必检查nullptr
  • @chico:不确定你的意思。 auto myDevice = settings.getDevice() 将创建一个名为myDeviceDevice 类型的新实例,并从getDevice() 返回的引用所引用的实例中复制构造它。如果你想让myDevice 成为参考,你需要做auto&amp; myDevice = settings.getDevice()。因此,除非我遗漏了什么,否则我们会回到不使用 auto 的情况。
  • @Purrformance:因为您不想放弃对象的所有权 - 将可修改的 unique_ptr 交给客户,客户就有可能离开它,从而获得所有权和给你一个空(唯一)指针。
  • @Purrformance:虽然这会阻止客户搬家(除非客户是热衷于const_casts 的疯狂科学家),但我个人不会这样做。它公开了一个实现细节,即所有权是唯一的并通过unique_ptr 实现的事实。我是这样看的:如果你想要/需要传递/返回所有权,传递/返回一个智能指针(unique_ptrshared_ptr,取决于所有权的类型)。如果您不想/不需要传递/返回所有权,请使用(正确的const-qualified)指针或引用,主要取决于参数是否可以为空。
【解决方案2】:
class Device {
};

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

    }

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

int main()
{
    std::shared_ptr<Device> device(new Device());
    Settings settings(device);
    // ...
    std::shared_ptr<Device> myDevice(settings.getDevice());
    // do something with myDevice...
    return 0;
}

week_ptr 仅用于参考循环。依赖图必须是无环有向图。在共享指针中有 2 个引用计数:1 个用于 shared_ptrs,1 个用于所有指针(shared_ptrweak_ptr)。当所有shared_ptrs 被删除时,指针被删除。当需要来自weak_ptr 的指针时,应该使用lock 来获取指针(如果存在)。

【讨论】:

  • 所以如果我正确理解你的答案,智能指针确实会替换原始指针,但不一定是引用?
  • shared_ptr 中实际上有 两个 引用计数吗?你能解释一下为什么吗?据我了解,weak_ptr 不必计算在内,因为它只是在对对象进行操作时创建了一个新的shared_ptr(如果底层对象仍然存在)。
  • @BjörnPollex:我为您创建了一个简短的示例:link。我还没有实现所有的东西,只是复制构造函数和lockboost 版本在引用计数上也是线程安全的(delete 只被调用一次)。
  • @Naszta:您的示例显示可能使用两个引用计数来实现此功能,但您的回答表明这是必需,即我不相信它是。您能否在回答中澄清这一点?
  • @BjörnPollex,为了让weak_ptr::lock() 判断对象是否已过期,它必须检查包含第一个引用计数和指向对象的指针的“控制块”,因此控制块不能当有任何 weak_ptr 对象仍在使用时被销毁,因此必须跟踪 weak_ptr 对象的数量,这就是第二个引用计数的作用。当第一个引用计数降至零时,对象被销毁,当第二个引用计数降至零时,控制块被销毁。
猜你喜欢
  • 1970-01-01
  • 2021-02-19
  • 2020-03-05
  • 2011-12-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-01
  • 2016-05-20
相关资源
最近更新 更多