【问题标题】:Should I use references members in this case?在这种情况下我应该使用引用成员吗?
【发布时间】:2015-03-05 18:28:19
【问题描述】:

我是计算机科学专业的学生,​​我正在自学 C++,并尝试编写一个干净的 C++ 程序。

这是我的问题: 我正在编写一个 App 类,它将成为程序的主类,我希望我的很多类都可以访问将在主函数中创建的 App 对象。

基本上主函数会是这样的

int main(int argc, char *argv[])
{
   App myApp;

   return myApp.run();
}

App 类将有许多来自类的对象需要引用(或者在这种情况下是其他更好的东西吗?)到应用程序。 这是一个需要它的类的示例

class AppStateHandler{
private:
    /// < Reference to the App
    App& m_app;
public:
    AppStateHandler(); // How can I initialize m_app with that ??

    /// @param app The app the AppStateHandler will work with
    AppStateHandler(App &app);
};

这是我的问题,在这种情况下使用引用作为类成员是否不好?我应该使用某种智能指针吗? 在使用引用的情况下,我无法创建 默认构造函数,因为 m_app 必须在构造函数初始化列表中初始化 所以,没有默认值是不是很糟糕构造函数?

如果您对如何改进我的代码有任何建议,我很感兴趣。

非常感谢!

编辑:我实际上想让 AppStateHandler 成为 App 的成员。在主函数中声明的唯一对象是 App。

【问题讨论】:

  • 如果有可能无法将类链接到应用程序,我建议使用智能指针。
  • 我个人更喜欢对象将指针存储为成员而不是引用,因为它允许以后重新分配它们。
  • “我希望我的很多类都可以访问 App 对象”——这通常是个坏主意。就像使用全局变量一样,它导致了意大利面设计。
  • 我同意,但是我所有的 AppStates 都会有一个 render() 过程,它必须在属于应用程序的 SFML 窗口上绘制东西,所以我需要调用类似 m_app- >getWindow().draw(thingToDraw);我从一些使用 SFML 的 Game Engine 示例中举了一个例子,这种设计似乎很常见,即使它可能不太好,我正在寻找另一种设计。

标签: c++ class reference smart-pointers


【解决方案1】:

在这种情况下使用引用意味着应用程序不能为 NULL。但只有在保证对象的生命周期始终在父对象的生命周期内时才使用引用。如果没有,请使用智能指针。

【讨论】:

    【解决方案2】:

    首先,正如 cmets 中提到的,您应该真正考虑是否要将对您的应用程序对象的引用传递给每个成员对象(我在这里使用术语引用来表示任何东西,即引用另一个对象,无论是原始/智能指针还是实际引用)。结果将是程序中的任何函数调用都可能改变程序中的任何其他状态。如果您的对象需要对窗口的引用来绘制某些东西,那么只需给他们一个对窗口的引用即可。

    接下来,您不应该在此处使用智能指针,原因有两个:大多数智能指针(例如 std::unique_ptrstd::shared_ptr)表示拥有关系。然而,对父对象的引用正好相反。其次,据我了解您的程序设计,您的主应用程序对象的生命周期 - 按照设计 - 无论如何都会超过程序中任何其他对象的生命周期,因此您不必担心生命周期问题。

    然后是指针与引用的问题:通常,由于您的应用程序的生命周期将超过任何其他对象的生命周期,因此使用引用没有害处(除非您需要能够默认构造您的目的)。 就“好”的软件设计而言,你应该问自己每一堂课,那个课是否有意义,而不是参考主应用程序。如果没有,请使用参考。如果是这样,请使用一个指针,在使用它之前,您总是会检查 nullptr

    最后:如果您已经确定,您的大多数对象确实需要该引用并且您的程序逻辑是这样的,即只能有一个 App,那么您可以考虑将其实现为单例。尽管我不是它们的忠实粉丝,但它们并不比在程序中传递对每个单个对象的引用更糟糕。另一方面,它们有时可以简化您的代码,因为某些对象仅持有“传递它”的引用,但不自己使用它。

    【讨论】:

    • 非常感谢您提供这些信息,对我帮助很大。
    【解决方案3】:

    您可以使用引用,甚至可以使用智能指针(尽管这样做没有什么意义),但您的问题急需另一种解决方案:singleton

    要使用单例模式,您需要向App 类添加一个静态方法,该方法唯一的责任是返回对唯一一个App 对象的引用。 App 的构造函数应该是私有的,这样其他代码就无法生成第二个App 对象。实现看起来像这样:

    class App {
        private:
            App();
            App(App const&) = delete;
    
        public:
            static App& getApp() {
                static App theApp;
                return theApp;
            }
    }
    

    这样所有其他对象都可以通过简单地调用App::getApp() 来引用App 对象。

    【讨论】:

    • 是的,我也在考虑使用这种模式
    【解决方案4】:

    不使用默认构造函数一点也不差。

    我不认为这是一个“参考 VS 指针”的问题。它看起来更像是对象的生命周期和所有权管理。

    有一些方法可以确保推荐人在被推荐人的生命周期内。

    在您的情况下,您在main() 的堆栈中构建它们,这是正确的。或者你可以让AppStateHandler成为App的成员,这取决于你的设计目的。

    需要注意的是,随着AppStateHandler 的增长,它会有很多成员类/对象、方法,每个只做少量的工作,真的不需要访问整个@987654325 @。将整个App 传递给他们每个人很快就会使您的代码变得讨厌。

    同样,根据您的设计目的,无论您选择的是指针还是引用,都不要传递太多。

    附:我相信使用 shared_ptr/unique_ptr 来管理跨范围的生命周期/所有权表明设计不佳。 99% 的情况下,您可以通过 RAII 等其他设计技术避免所有权转移的必要性。绝对必要时,智能指针用于另外 1%。

    【讨论】:

    • 哦,我说错了,我的意思是 AppStateHandler 正是 App 的成员。
    • 抱歉,您的某些陈述对我来说没有多大意义。智能指针,例如是 RAII 的一种形式。 ressource 是堆中创建的对象,用于初始化智能指针。如果您的意思是 shared_ptr 被过度使用,那么我们经常同意,可以使用 unique_ptr 甚至是原始指针。此外,三规则与具有默认构造函数无关。它是关于需要析构函数、复制赋值和复制构造函数(以及从 c++11 开始它们各自的移动版本)。
    • @MikeMB 已编辑。但是 RAII 需要构造函数分配和析构函数释放。智能指针不符合该描述,因为在调用构造函数之前已经分配了资源。
    • 嗯,首先,推荐的方法是使用std::make_sharedstd::make_unique,这正是你所说的。其次,我并不是说分配必须发生在构造函数内部——这只是典型的执行方式(CADRe)。 RAII 是一个概念,可以通过不同的方式实现。重要的是,当获取资源(或者更确切地说是责任)时(不管它是内存、文件、互斥体等),它用于初始化句柄并处理其释放和智能指针只能用于此目的。
    猜你喜欢
    • 2011-01-17
    • 2022-07-22
    • 2011-10-26
    • 2013-05-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-15
    • 2010-09-05
    相关资源
    最近更新 更多