【问题标题】:Trouble with Factory and dynamic allocation in C++C++ 中的工厂和动态分配问题
【发布时间】:2008-12-29 20:55:58
【问题描述】:

我有一个工厂,可以在我的应用程序中构建生命周期最长的对象。它们有类型,比如说ClientAClientB,它们依赖于Provider(具有许多可能实现的抽象类),因此两个客户端都引用了Provider 作为成员。

根据命令行参数,工厂选择Provider 的一种实现,构造它(使用“new”),并将其传递给两个客户端的构造函数。

工厂返回一个代表我的整个应用程序的对象。我的主要功能基本上是这样的:

int main(int argc, char** argv)
{
    AppFactory factory(argc, argv);
    App app = factory.buildApp();
    return app.run();
}

buildApp的方法基本上是这样的:

App AppFactory::buildApp()
{
    Provider* provider = NULL;

    if (some condition)
    {
        provider = new ProviderX(); 
    }
    else
    {
        provider = new ProviderY();
    }

    ClientA clientA(*provider);
    ClientB clientB(*provider);

    App app(clientA, clientB);
    return app;
}

所以,当执行结束时,所有对象的析构函数都会被调用,除了提供者对象(因为它是用“new”构造的)。

如何改进此设计以确保调用提供程序的析构函数?

编辑:澄清一下,我的意图是客户端、提供者和 App 对象共享相同的生命周期。在所有答案之后,我现在认为客户端和提供者都应该在堆上分配其传递给 App 对象的引用,该对象将负责在它死时删除它们。你说什么?

【问题讨论】:

    标签: c++ design-patterns factory


    【解决方案1】:

    使用共享所有权智能指针非常简单:

    App AppFactory::buildApp()
    {
        boost::shared_ptr<Provider> provider;
    
        if (some condition)
        {
            provider.reset(new ProviderX()); 
        }
        else
        {
            provider.reset(new ProviderY());
        }
    
        ClientA clientA(provider);
        ClientB clientB(provider);
    
        App app(clientA, clientB);
        return app;
    }
    

    假设 app 对象拥有客户端,并且客户端都共享一个提供者。然后让客户使用shared_ptr&lt;Provider&gt;,而不是Provider&amp;。只要仍然存在拥有提供者对象的 shared_ptr 的副本,就不会释放该对象。

    最好不要复制clientA和clientB,也不要通过值返回来复制app,而是将客户端移动到应用中,并将应用本身移动到返回的对象中。这将在即将推出的 C++ 版本中成为可能。但目前,要么将它们设为指针(使用 shared_ptr),要么继续复制它们。另一种选择是使用 auto_ptr,它具有伪所有权转移语义。但该模板存在一些固有问题。所以你应该避免使用它。

    【讨论】:

    • 从一开始,我就想使用智能指针来进行类似 Java 的内存管理。不幸的是,我不能在这个项目中使用 boost。但是感谢您指出,我认为这是最优雅的解决方案。
    • 是你不希望客户必须下载boost,还是因为你不被允许使用它?如果是前者,您还可以使用“bcp”工具轻松地从 boost 树中提取 shared_ptr 源。我过去做过,效果很好。注意都是标题。
    【解决方案2】:

    使提供者成为 AppFactory 的实例变量。然后将 provider 设为智能指针或在 AppFactory 的 dtor 中将其删除。

    【讨论】:

    • 我认为 Provider 和 Client 之间的关系比 provider 和 appfactory 之间的关系要好...您是在暗示一种以前没有的任意关系。
    • 您说的是应用程序的生命周期是工厂的范围。如果以后变了怎么办?实际上,范围是提供者的聚合用户的生活。在这种情况下,它是客户......
    • 我同意不应该依赖于工厂——如果工厂用于在不同线程中创建应用程序的多个实例会发生什么?
    • 我也认为依赖不应该在工厂上,但是 App 对象可以拥有提供者,因为它已经拥有其他组件。
    【解决方案3】:

    除非 App 的构造函数复制客户端,否则它们也需要进行 new() 处理 - 当前的客户端分配在堆栈上,并在应用返回时被删除。

    我认为您可能需要注意正在创建哪些对象 - 例如将调试语句放在客户端的构造函数中。

    你可能想要的是让 Provider 被引用计数,并且让每个 Client 只是减少引用计数,但这是很多工作。

    或者让 AppFactory 拥有 Provider。

    【讨论】:

    • 您更清楚地说明了我试图对客户和一般代码所说的话。
    【解决方案4】:

    这里没有足够的帮助,但是你可以在不改变太多的情况下向 Provider 对象添加引用计数,当客户端被破坏时,它们会删除引用。当 Provider 对象中的引用变为 0 时,调用 delete this。

    你的生命周期和范围有点粗略。为什么你要在堆栈上创建一些对象,而在堆上创建一些对象——特别是你的客户?

    【讨论】:

    • 好吧,以前,我所有的对象过去都在堆栈上,但后来我意识到我的提供者应该有不同的实现,多态性和动态分配进入了游戏..
    【解决方案5】:

    一种选择是让工厂返回一个包含工厂构造的所有组件的 AppComponents 对象。 IE。像这样:

    int main(int argc, char** argv)
    {
        AppFactory factory(argc, argv);
        AppComponents components = factory.buildApp();
        return components.getApp().run();
    }
    

    AppComponents 类将负责删除您的 Provider 和其他对象。

    【讨论】:

    • 也可以只在应用程序上有一个名为 cleanup 的方法,该方法在 run() 返回后调用。
    • 我的示例中的 App 对象正是具有保存所有组件的功能。正如你所说,我真的应该让它负责删除提供者。谢谢!
    • 如果您的 App-class 当前没有直接依赖于 Provider-interface(只是通过 Client-class 的间接依赖),您可能不想引入一个,以便它可以删除提供者。当然,如果您已经拥有该依赖项,那么让 App 删除 ...
    • .. 提供者。请注意不要违反单一职责原则。
    • 目前,App 对象有两个职责:持有所有组件和运行主客户端(App::run() 方法委托给主客户端)。我将删除此委托,以便 App 承担单一责任,就像您的代码 sn-p 中一样。谢谢拉斯穆斯!
    【解决方案6】:

    将provider设为AppFactory的成员变量,并在析构函数中删除:

    class AppFactory
    {
        public:
        AppFactory(int argc, char** argv) : provider(NULL)
        {
           //...
        }
        ~AppFactory()
        {
            if (provider != NULL)
                delete provider;
        }
        App buildApp()
        {
    
            if (some condition)
            {
                provider = new ProviderX(); 
            }
            else
            {
                provider = new ProviderY();
            }
    
            ClientA clientA(*provider);
            ClientB clientB(*provider);
    
            App app(clientA, clientB);
            return app;
    
        } 
        private:
        Provider* provider;
    
    };
    
    int main(int argc, char** argv)
    {
        AppFactory factory(argc, argv);
        App app = factory.buildApp();
        return app.run();
    }
    

    【讨论】:

    • rtayek 说了同样的话,但正如那里的 cmets 所指出的那样,这也不是一个好主意。您创建了一种不存在且不需要的关系。关系片段与提供者和客户有关。
    • 如果只是provider和client之间的关系,可以在buildApp()中删除clients后立即删除provider。
    • 确实如此。我不认为他是故意的。我怀疑他希望这些人比这种方法活得更久,但同样,我们没有我们需要的所有信息。
    • 好建议,但对我来说,按照 Rasmus 的建议,让 App 拥有提供者更有意义,谢谢!
    • 澄清一下,我的意图是客户端、提供者和 App 对象都具有相同的生命周期。毕竟答案,我现在认为它们都应该在堆上分配并在调用其析构函数时由 App 对象删除。你说什么?
    【解决方案7】:

    您说“提供程序和 App 对象共享相同的生命周期”,但要注意在 C++ 中,以下代码片段...

    App app(clientA, clientB);
    return app;
    

    ... 正在返回 App 对象的 copy:因此您可能(取决于编译器,例如参见 http://msdn.microsoft.com/en-us/library/ms364057(VS.80).aspx)实际上有两个 App 对象实例(一个在 AppFactory 内) ::buildApp() 方法和 main 函数中的另一个)。

    为了回答您的问题,我想我同意您的编辑:将指向提供程序的指针传递到您的 App 构造函数中,将其存储为 App 实例的成员数据,并在您销毁 App 实例时将其删除。但是,除此之外,您还可以更改代码以确保不复制 App 实例:例如,在堆上分配 App 实例,更改 AppFactory::buildApp() 方法以返回指向App,并在 main 函数结束时删除 App 实例。

    【讨论】:

    • 好点,我认为最后一种将所有内容(包括应用程序)都放到堆中的方法对我来说已经足够了。谢谢克里斯!
    【解决方案8】:

    只要打电话

    delete provider;
    provider = NULL;
    

    在 ClientA 和 ClientB 的析构函数中。这也会调用提供者的析构函数。

    【讨论】:

    • 这并没有说明 dtor 在不同时间被调用。多线程应用程序也不安全......最好有引用计数或其他东西。
    • 题外话:在析构函数中将指针删除后设置为NULL没有任何价值。
    • @Marcin:需要解释一下吗?析构函数是对象生命周期中最后被调用的东西。执行后,周围没有有效对象。
    • 如果你在多个类中使用指向对象的指针,每个类都试图删除它,你必须在删除它后将它设置为 NULL - 否则下一个类将尝试删除已经释放的内存。
    猜你喜欢
    • 2023-04-04
    • 2012-05-02
    • 1970-01-01
    • 1970-01-01
    • 2022-01-17
    • 2017-05-07
    • 2011-11-15
    • 1970-01-01
    • 2021-03-13
    相关资源
    最近更新 更多