【问题标题】:c++ Forward Declaration designc++ 前向声明设计
【发布时间】:2014-07-16 01:35:06
【问题描述】:

根据我的阅读,我应该尽可能使用前向声明。我有这样的类(由于前向声明,每个字段都是指针):

class A
{
    // ...

    A* a;
    B* b;
    C* c;
    D* d;
    E* e;
};

但是这样做有问题。

1- 这意味着为构造函数中的每个字段调用 new 和 delete(或至少使用智能指针新建),而堆栈分配的字段不需要这个。

2- 我听说堆栈分配比堆分配快。

3- 这也意味着几乎每个类的每个字段都应该是指针。

我是否像我的示例课程那样做正确的事情?还是我在前向声明中遗漏了什么?

【问题讨论】:

  • 旁注:在A* a;的情况下,你没有其他机会使用指针,因为A不能保留A - 它会产生无限依赖导致A的大小是无限的。
  • Qt 一直使用这个。他们声称这无关紧要,因为大多数代码无论如何都不是性能关键。
  • @Laethnes:真的很抱歉,这只是一个例子。
  • @sashoalm 是的,这是真的,我一直想知道它在小型设备上的表现如何:3。
  • @Aulaulz 无需道歉,我只是想向潜在的未来读者澄清这一点。

标签: c++ forward-declaration forward


【解决方案1】:

含义是相反的——你不应该仅仅为了使用前向声明而使用指针,但你应该在做出设计决定之后使用前向声明(例如使用指针而不是对象作为成员)尽可能。

因此,如果使用对象更有意义,请这样做,并包含您需要的文件。不要为了前向声明类而使用指针。

【讨论】:

  • 你写了or objects as members——前向声明真的可以吗?
  • @Wolf 没有。正是我的意思——不要根据你是否可以使用前向声明来做出设计决定。如果您需要对象作为成员,请执行此操作并包含您需要的文件。
  • 那么,instead of 对这个答案的困惑会少一些(当然,我明白你的意思)。
  • @Wolf 是有道理的。谢谢!
【解决方案2】:

你展示的例子有点过头了。使用前向声明的建议并不意味着您的代码设计是由前向声明实践驱动的。前向声明只是实现细节,设计为准。

先判断需要聚合还是组合,再判断前向声明是否合适。

不要当前向声明的类型是您的方法签名的一部分时,更喜欢前向声明而不是 #include,即参数的类型。

#include "OtherClass.h" // 'bad' practice

class OtherClass; // this is better than #include
....
class MyClass
{
    void method(OtherClass *ptr);
}

无论如何,这不是一个绝对规则,因为使用前向 decls 代替包含并不总是可能/方便的。

【讨论】:

  • 这个。另外阅读一些关于过早优化的内容 - 建议使用前向声明,以便编译时间更短,并且避免在标题更改时重新编译大量文件。但像往常一样,恕我直言,这是在出现问题时应该优化的其中之一,而不是之前。我自己的例子:我的个人库遇到了这个问题,解决方案很简单:预编译头文件。而且我在开发过程中不需要处理很多指针:)。
  • @Laethnes 绝对。句法细节很容易获得,大问题几年后才出现。
  • 我发现了一个小错误(弗洛伊德?):froward ;)
【解决方案3】:

如果您使用指针作为成员,则更喜欢前向声明而不是公开完整的类定义。不要盲目地使用指针来满足某些规则。

【讨论】:

    【解决方案4】:

    对不属于该类的对象使用指针或引用。但是对于此类拥有的对象,不要使用前向声明作为选择指针的理由。

    如果你真的想最小化编译时间依赖,请考虑 PIMPL 惯用语,而不是将所有成员都变成指针:

    MyClass.h:

    #include <memory>
    class MyClassImpl;
    
    class MyClass {
     public:
       MyClass();
      ~MyClass();
       void doThing();
     private:
      std::unique_ptr<MyClassImpl> pimpl_;
    };
    

    MyClass.cpp

    #include "MyClass.h"
    #include "MyClassImpl.h"
    
    MyClass::MyClass() { } // in .cpp so unique_ptr constructor has complete type
    
    MyClass::~MyClass() { } // in .cpp so unique_ptr destructor has complete type
    
    void MyClass::doThing(){
      pimpl_->doThing();
    }
    

    MyClassImpl.h:

    #include "A.h"
    #include "B.h"
    
    class MyClassImpl {
     private:
      A a_;
      B b_;
     public:
      void doThing();
    };
    

    MyClassImpl.cpp:

    #include "MyClassImpl.h"
    
    void MyClassImpl::doThing() {
      // Do stuff with a_, b_, etc...
    }
    

    这可能无法解决性能问题,因为您仍然有动态内存分配,但您必须对其进行测量才能看到。

    【讨论】:

    • Aulaulz 提到过这个吗?
    • @Wolf:最小化编译时依赖是使用前向声明的主要原因,所以间接地,是的。
    【解决方案5】:

    从技术上讲,您可以(并且应该!)使用前向声明,如果您的类的接口不依赖于完全限定类型。编译器必须为成员保留足够的空间并在编译时添加管理函数——在类中仅使用指针或引用不会引入对类型的依赖。

    顺便说一句:前向声明并不是什么新鲜事:在某些 C 标准库中,FILE 是一个 typedef 用于前向声明的 struct,这是有道理的,因为 FILE 总是用于指针在整个公共文件 API 中。

    【讨论】:

      【解决方案6】:

      除了已经给出的好的答案:如果您的类不创建对象但私下使用它(例如某些实用程序类),可以使用引用而不是指针。

      class UtilityClass; // forward declaration (even interfaces make sense here)
      
      class MyClass {
      public:
          /// takes an UtilityClass for implementing some of its functions
          MyClass(UtilityClass& u): util(u) {}
      private:
          UtilityClass& util;
          // ...more details
      };
      

      在这些情况下,前向声明并不意味着必须在堆上创建对象(至于您的问题 #1 和 #2)。

      【讨论】:

        猜你喜欢
        • 2013-10-01
        • 2010-12-29
        • 2021-12-25
        • 1970-01-01
        • 2023-03-16
        • 1970-01-01
        • 1970-01-01
        • 2011-07-08
        相关资源
        最近更新 更多