【问题标题】:Two step constructions for enable_shared_from_this object that needs to pass std::shared_ptr<self> to children created in constructorenable_shared_from_this 对象的两步构造,需要将 std::shared_ptr<self> 传递给在构造函数中创建的子对象
【发布时间】:2016-09-23 05:32:08
【问题描述】:

我知道额外的初始化方法是邪恶的,因为它们留下了一个非常讨厌的选项来让对象半构建,因此所有方法都需要检查这一点。但是这种情况呢?

class config;
class cfg_item final
{
    private:
        friend class config;
        cfg_item(std::weak_ptr<config> owner) : owner(owner) { }
        std::weak_ptr<config> owner;
}
class config final : private std::enable_shared_from_this<config>
{
    public:
        config()
        {
             items.emplace(std::make_shared<cfg_item>(weak_from_this())); // Will crash!
        }
    private:
        std::vector<std::shared_ptr<cfg_item>> items;
}
int main(int argc, char * argv[])
{
    std::shared_ptr<config> cfg = std::make_shared<config>();
}

我知道它为什么会崩溃。 main 中的 std::shared_ptr 尚未使用指向配置实例的共享指针进行初始化,因此构造函数不知道如何创建 weak_from_this 并且只是引发 std::bad_weak_ptr 异常,因为没有有效的 std::shared_ptr 指向 this构造函数的调用时间。

问题是:我怎样才能避免整件事?我相信我看到的唯一方法是添加单独的初始化方法,正如我已经提到的那样,这是 evil...

关于真实代码的注释:构造函数从外部源加载cfg_item。假设所有cfg_items 在config 的整个生命周期内都可用。指向config 的弱指针是强制性的,因为cfg_item 必须将对其所做的所有更改推回config 以保存到外部源

【问题讨论】:

  • 您可以在cfg_item 类中嵌入config 的非拥有引用(如config&amp;std::reference_wrapper&lt;config&gt;)而不是weak_ptr&lt;config&gt;。它应该是安全的,因为从逻辑上讲,owner 不会比 config 对象寿命长,除非你做一些非常疯狂的事情。
  • @FrankHB 这实际上是我三年前错过的一个非常好的主意。谢谢!

标签: c++11 design-patterns smart-pointers


【解决方案1】:

如果您查看this question 的答案,就会发现为什么需要外部初始化函数的理由很充分。但是,你写得对

我知道额外的初始化方法是邪恶的,因为它们留下了一个非常讨厌的选项来让对象半构建,因此所有方法都需要检查这一点。

可以减少这个问题。假设您有一个类foo,其协议规定每次构造foo 对象时,都需要调用foo::init()。显然,这是一个脆弱的类(客户端代码最终会省略对init() 的调用)。

因此,一种方法是制作fooprivate 的(非复制/非移动)构造函数,并创建一个可变参数静态工厂方法来创建对象,然后调用init()

#include <utility>

class foo { 
private:
    foo() {}
    foo(int) {}
    void init() {}

public:
    template<typename ...Args>
    static foo create(Args &&...args) {
        foo f{std::forward<Args>(args)...};
        f.init();
        return f;
    }
};

在下面的代码中

    template<typename ...Args>
    static foo create(Args &&...args) {
        foo f{std::forward<Args>(args)...};
        f.init();
        return f;
    }

请注意,这个单一方法可用于所有构造函数,无论其签名如何。此外,由于它是static,它在构造函数之外,不存在您的问题。

您可以按如下方式使用它:

int main() {
    auto f0 = foo::create();
    auto f1 = foo::create(2);
    // Next line doesn't compile if uncommented
    // foo f2; 
}

注意,没有这个方法是无法创建对象的,而且接口中甚至不包含init

【讨论】:

  • 您的回答很好,但我错过了描述中的一件重要事情 - config 类是通过 boost 依赖注入器注入的,我无法控制 config 分配。特别是,我无法将其移至工厂方法。这个答案:stackoverflow.com/a/3430580/151150 听起来像是在正确的轨道上,但我的cfg_item 可能比config 寿命更长,所以我不能只使用原始指针:-(
  • @PiotrK 那确实让它变得更加困难。一切顺利。
猜你喜欢
  • 2012-08-13
  • 1970-01-01
  • 1970-01-01
  • 2014-06-04
  • 2020-05-24
  • 1970-01-01
  • 2012-07-09
  • 2014-10-24
  • 1970-01-01
相关资源
最近更新 更多