【问题标题】:c++ Dependency Injection: Object lifetimes?c++ 依赖注入:对象生命周期?
【发布时间】:2011-11-01 22:50:46
【问题描述】:

我来自 C#,并试图将我的一些实践翻译成 C++。我使用原始指针在整个代码中的各个地方使用了依赖注入。然后我决定用 std::shared_ptr 替换原始指针。作为该过程的一部分,有人建议我考虑使用堆栈分配的自动变量而不是动态分配它们(请参阅this question,尽管该问题是在 unique_ptr 的上下文中,所以可能不同)。

我相信下面的例子展示了自动变量的使用。

class MyClass
{ 
public:
   MyClass(ApplicationService& app): appService_(app)
   {
   }

   ~MyClass()
   {
        appService_.Destroy(something);

   }
private:   
   ApplicationService& appService_;
}

class ConsumerClass
{
    DoSomething()
    {
        CustomApplicationService customAppService;
        MyClass myclass(customAppService);
        myclass...
    }
}

在上面的例子中,当 customAppservice 和 myclass 超出范围时,我怎么知道哪个会先被销毁?如果 customAppService 首先被销毁,则 MyClass 析构函数将失败。这是在这种情况下使用 shared_ptr 的好理由吗?还是有一个干净的方法来解决这个问题?

更新

ApplicationService 是一个封装了与我的代码使用的第 3 方库交互所需的全局函数的类。我有这门课,因为我相信这是支持单元测试和独立功能的存根/模拟的标准方法。此类只是将调用委托给相应的全局函数。调用 appService_.Destroy(something);实际上是在破坏 MyClass 的每个特定实例使用的对象,而不是破坏与 Application 类本身有关的任何东西。

【问题讨论】:

    标签: c++ dependency-injection object-lifetime


    【解决方案1】:

    答案是:你不需要知道,反正你的设计坏了。

    首先,Destroy 听起来是个坏主意,此外,如果调用一个不负责销毁另一个对象的对象。 Destroy 方法中的代码属于 ApplicationService 的析构函数(希望是虚拟的,尽管在这种情况下它实际上不需要),与 C# 相比,它在完全确定的时间点被调用。

    完成此操作后,您将(希望)意识到,MyClass 没有责任销毁 appService_,因为它不拥有它。这是ConsumerClass(或者更确切地说是DoSomething 方法)的责任,它真正管理实际的服务,并且一旦你将Destroy 的代码移动到析构函数中,它就会自动销毁它。 RAII 以一种干净和自动的方式让所有事情发生,这不是很好吗?

    class MyClass
    { 
    public:
       MyClass(ApplicationService& app): appService_(app)
       {
       }
    
    private:   
       ApplicationService& appService_;
    }
    
    class ConsumerClass
    {
        DoSomething()
        {
            CustomApplicationService customAppService;
            MyClass myclass(customAppService);
            myclass...
        }
    }
    
    class ApplicationService
    {
    public:
        virtual ~ApplicationService()
        {
            //code from former Destroy method
        }
    }
    
    class CustomApplicationService
    {
    public:
        virtual ~CustomApplicationService()
        {
            //code from former Destroy method
        }
    }
    

    恕我直言,这是解决它的完美干净的 C++ 方式,这个问题绝对不是垃圾邮件shared_ptrs 的理由。即使你真的需要一个专用的Destroy 方法并且不能将代码移动到析构函数中(我认为这是过度思考设计的动机),那么你仍然会再次从DoSomething 调用DestroyMyClass 不负责销毁 appService_

    编辑:根据您的更新(以及我对something 参数的愚蠢忽略),您的设计似乎确实非常正确(至少如果您不能更改ApplicationService),对不起。

    虽然类成员应该以相反的构造顺序被销毁,但我不确定这也适用于局部自动变量。为了确保以定义的顺序调用析构函数,您可以做的是使用简单的块引入嵌套范围:

    void DoSomething()
    {
        CustomApplicationService customAppService;
        {
            MyClass myclass(customAppService);
            myclass...
        }       // myclass destroyed
    }       // customAppService destroyed
    

    当然还是完全没有必要使用动态分配,更不用说shared_ptrs了。尽管嵌套块有点破坏代码,但它与以非动态方式且无缘无故地应用动态分配的丑陋无关,并且它至少“在语义上看起来不错”,上面有 customAppService 的声明块的;)

    【讨论】:

    • 我知道这在我的示例中并不明显,但 MyClass 并没有破坏 appService_。查看我的更新。
    • @User 啊抱歉,我愚蠢地忽略了 something 参数。
    • 不用担心您没有完整的上下文。我相信这在技术上是可行的,但我想知道维护和创建脆弱代码的可能性以及代码是否传达了意图。我想我宁愿使用 shared_ptr 而不是依赖不明显的破坏行为。它让我想起了构造函数初始化列表中依赖项的顺序(对象如何以与声明它们的顺序相同的顺序进行初始化)。当我读到人们谈论依赖这一事实时,恕我直言,这似乎真的很脆弱。但如果这是最好的方法,那么我会考虑这样做。
    • @User 使用显式块,您可以完全确定它不使用任何“脆弱”信息(我希望我的直觉猜到了脆弱的正确含义)。而且我认为类成员按照声明顺序(如果需要,它们出现在头文件中的顺序)而不是它们在初始化列表中出现的顺序是标准知识。就像您不知道C++++C 之间的区别一样。这里完全没有必要使用shared_ptrs。您可以使用 C++ 或 C# 编写代码,但请不要使用 C++/C# 编写代码。
    • @User 不要让懒惰驱使您忽略所有内存和资源注意事项,并在各处发送垃圾邮件shared_ptrs(如果这听起来有点刺耳,抱歉)。显式(和确定性!)内存(甚至更重要的是通用资源)管理是 C++ 的主要优势之一,它可以实现诸如 RAII 和简单的异常安全之类的美好事物。
    【解决方案2】:

    在 C++ 中,一般而言,对象的销毁顺序与创建它们的顺序完全相反。

    根据您的示例,MyClass 将在 CustomApplicationService 之前被销毁

    显式调用析构函数时例外。但是,我认为您在现阶段不应该关心这个例外。

    另一个微妙之处称为static initialization order fiasco。但是,这不适用于自动(堆栈)变量。

    编辑:
    从 C++2003 开始​​ - 寻找“逆序”

    6.6.0.2

    On exit from a scope (however accomplished), destructors (12.4) are called for all 
    constructed objects with automatic storage duration (3.7.2) (named objects or 
    temporaries) that are declared in that scope, in the reverse order of their
    declaration. ... [Note: However, the program can be terminated (by calling exit()
    or abort()(18.3), for example) without destroying class objects with automatic
    storage duration. ]
    

    【讨论】:

    • 它是特定实例的子对象以相反的顺序被破坏。你确定在这种情况下它仍然是相反的吗?任何引用都会有所帮助。
    • 当然。如果 MyClass 包含子对象,它们将在调用 CustomApplicationService 析构函数之前被完全销毁。
    • 我相信 Chris 是正确的,但如果我真的想确定(并且不相信规范的解释),我会将 MyClass 对象放入它自己的范围内 { MyClass myClass(...); ... } 然后我知道对象首先消失了。
    • 实际上这个陈述的标准证明会很好。我知道它适用于成员变量,但也适用于局部变量?
    • 这个 C++ 常见问题解答说这是正确的:parashift.com/c++-faq-lite/dtors.html#faq-11.2 虽然它没有引用规范。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-07
    • 2017-06-04
    • 1970-01-01
    相关资源
    最近更新 更多