我什么时候可以使用前瞻性声明?

Igo*_*Oks 588 c++ c++-faq forward-declaration

我正在寻找允许在另一个类的头文件中进行类的前向声明的定义:

我是否允许为基类,作为成员持有的类,通过引用传递给成员函数的类等执行此操作?

Luc*_*lle 937

让自己处于编译器的位置:当你转发声明一个类型时,所有编译器都知道这个类型存在; 它对它的大小,成员或方法一无所知.这就是为什么它被称为不完整类型.因此,您不能使用该类型来声明成员或基类,因为编译器需要知道该类型的布局.

假设以下前瞻性声明.

class X;
Run Code Online (Sandbox Code Playgroud)

这是你能做什么,不能做的.

你可以用不完整的类型做什么:

你不能用不完整的类型做什么:


对于模板,没有绝对规则:是否可以使用不完整类型作为模板参数取决于模板中使用类型的方式.

例如,std::vector<T>要求其参数为完整类型,而boost::container::vector<T>不是.有时,只有在使用某些成员函数时才需要完整类型; 例如,情况就是如此std::unique_ptr<T>.

记录良好的模板应在其文档中指出其参数的所有要求,包括它们是否需要完整类型.

  • 为"让自己置于编译器的位置"+1.我想象"编译器正在"留胡子. (11认同)
  • 这组规则忽略了一个非常重要的情况:您需要一个完整的类型来实例化标准库中的大多数模板.需要特别注意这一点,因为违反规则会导致未定义的行为,并且可能不会导致编译器错误. (8认同)
  • 很好的答案,但请参阅我的下面我不同意的工程点.简而言之,如果您没有包含您接受或返回的不完整类型的标题,则会强制对标题的使用者不可知的依赖性,而不必知道他们需要哪些其他人. (4认同)
  • @JesusChrist:确切地说:当你按值传递一个对象时,编译器需要知道它的大小才能进行适当的堆栈操作; 当传递指针或引用时,编译器不需要对象的大小或布局,只需要地址的大小(即指针的大小),这不依赖于指向的类型. (3认同)
  • @AndyDent:是的,但是标题的使用者只需要包含他实际使用的依赖项,所以这遵循C++原则"你只需支付你使用的东西".但实际上,对于期望标题是独立的用户来说可能是不方便的. (2认同)

Tim*_*sch 43

主要规则是,您只能转发声明其内存布局(以及成员函数和数据成员)不需要在您转发声明的文件中知道的类.

这将排除基类和除引用和指针使用的类之外​​的任何东西.

  • 几乎.您还可以将"plain"(即非指针/引用)不完整类型称为函数原型中的参数或返回类型. (6认同)

Mar*_*utz 32

Lakos区分了课堂使用

  1. 仅限名称(前向声明就足够了)和
  2. in-size(需要类定义).

我从来没有见过它更简洁明了:)

  • @Boon:我敢说...?如果你使用_only_类'_name_? (4认同)
  • 什么是名义上的意思? (2认同)

j_r*_*ker 28

除了指向不完整类型的指针和引用之外,您还可以声明指定参数和/或返回不完整类型值的函数原型.但是,除非是指针或引用,否则无法定义具有不完整的参数或返回类型的函数.

例子:

struct X;              // Forward declaration of X

void f1(X* px) {}      // Legal: can always use a pointer
void f2(X&  x) {}      // Legal: can always use a reference
X f3(int);             // Legal: return value in function prototype
void f4(X);            // Legal: parameter in function prototype
void f5(X) {}          // ILLEGAL: *definitions* require complete types
Run Code Online (Sandbox Code Playgroud)


R S*_*ahu 18

到目前为止,没有一个答案描述何时可以使用类模板的前向声明.所以,在这里.

可以将类模板转发声明为:

template <typename> struct X;
Run Code Online (Sandbox Code Playgroud)

遵循接受的答案的结构,

这是你能做什么,不能做的.

你可以用不完整的类型做什么:

  • 将成员声明为指针或对另一个类模板中不完整类型的引用:

    template <typename T>
    class Foo {
        X<T>* ptr;
        X<T>& ref;
    };
    
    Run Code Online (Sandbox Code Playgroud)
  • 声明一个成员是一个指针或对其不完整实例之一的引用:

    class Foo {
        X<int>* ptr;
        X<int>& ref;
    };
    
    Run Code Online (Sandbox Code Playgroud)
  • 声明接受/返回不完整类型的函数模板或成员函数模板:

    template <typename T>
       void      f1(X<T>);
    template <typename T>
       X<T>    f2();
    
    Run Code Online (Sandbox Code Playgroud)
  • 声明接受/返回其不完整实例化的函数或成员函数:

    void      f1(X<int>);
    X<int>    f2();
    
    Run Code Online (Sandbox Code Playgroud)
  • 定义接受/返回指向不完整类型的指针/引用的函数模板或成员函数模板(但不使用其成员):

    template <typename T>
       void      f3(X<T>*, X<T>&) {}
    template <typename T>
       X<T>&   f4(X<T>& in) { return in; }
    template <typename T>
       X<T>*   f5(X<T>* in) { return in; }
    
    Run Code Online (Sandbox Code Playgroud)
  • 定义接受/返回其不完整实例之一(但不使用其成员)的指针/引用的函数或方法:

    void      f3(X<int>*, X<int>&) {}
    X<int>&   f4(X<int>& in) { return in; }
    X<int>*   f5(X<int>* in) { return in; }
    
    Run Code Online (Sandbox Code Playgroud)
  • 将它用作另一个模板类的基类

    template <typename T>
    class Foo : X<T> {} // OK as long as X is defined before
                        // Foo is instantiated.
    
    Foo<int> a1; // Compiler error.
    
    template <typename T> struct X {};
    Foo<int> a2; // OK since X is now defined.
    
    Run Code Online (Sandbox Code Playgroud)
  • 用它来声明另一个类模板的成员:

    template <typename T>
    class Foo {
        X<T> m; // OK as long as X is defined before
                // Foo is instantiated. 
    };
    
    Foo<int> a1; // Compiler error.
    
    template <typename T> struct X {};
    Foo<int> a2; // OK since X is now defined.
    
    Run Code Online (Sandbox Code Playgroud)
  • 使用此类型定义函数模板或方法

    template <typename T>
      void    f1(X<T> x) {}    // OK if X is defined before calling f1
    template <typename T>
      X<T>    f2(){return X<T>(); }  // OK if X is defined before calling f2
    
    void test1()
    {
       f1(X<int>());  // Compiler error
       f2<int>();     // Compiler error
    }
    
    template <typename T> struct X {};
    
    void test2()
    {
       f1(X<int>());  // OK since X is defined now
       f2<int>();     // OK since X is defined now
    }
    
    Run Code Online (Sandbox Code Playgroud)

你不能用不完整的类型做什么:

  • “到目前为止,没有答案描述何时可以向前声明类模板。” 这不仅仅是因为X和X &lt;int&gt;的语义完全相同,而且只有前向声明语法在任何实质性方面都不同,答案中除1行外的所有行仅等于Luc的和`s / X / X &lt;int&gt; / g`?真的需要吗?还是我错过了一个与众不同的微小细节?有可能,但是我在视觉上进行了几次比较,看不到任何东西。 (2认同)

yes*_*aaj 5

在只使用指针或引用类的文件中.如果指针/引用,则不应调用任何成员/成员函数.

class Foo;//前向声明

我们可以声明Foo*或Foo&类型的数据成员.

我们可以使用Foo类型的参数和/或返回值声明(但不定义)函数.

我们可以声明Foo类型的静态数据成员.这是因为静态数据成员是在类定义之外定义的.


And*_*ent 5

我将其写为一个单独的答案,而不仅仅是一条评论,因为我不同意 Luc Touraille 的答案,不是因为合法性,而是因为强大的软件和误解的危险。

具体来说,我对您期望界面用户必须了解的隐含合同有疑问。

如果您返回或接受引用类型,那么您只是说它们可以传递指针或引用,而它们可能只能通过前向声明才能知道。

当您返回不完整的类型X f2();时,您是在说您的调用者必须具有 X 的完整类型规范。他们需要它才能在调用站点创建 LHS 或临时对象。

同样,如果您接受不完整的类型,则调用者必须构造作为参数的对象。即使该对象作为函数中的另一个不完整类型返回,调用站点也需要完整的声明。IE:

class X;  // forward for two legal declarations 
X returnsX();
void XAcceptor(X);

XAcepptor( returnsX() );  // X declaration needs to be known here
Run Code Online (Sandbox Code Playgroud)

我认为有一个重要的原则,即标头应该提供足够的信息来使用它,而无需依赖其他标头。这意味着标头应该能够包含在编译单元中,而在使用它声明的任何函数时不会导致编译器错误。

除了

  1. 如果这种外部依赖是期望的行为。您可以不使用条件编译,而需要有详细记录的要求,要求它们提供自己的标头声明 X。这是使用 #ifdefs 的替代方法,并且可以是引入模拟或其他变体的有用方法。

  2. 重要的区别是一些模板技术,您明确不希望实例化它们,提及只是为了避免有人对我尖酸刻薄。

  • 您正在谈论何时“应该”(或不应该)使用前向声明。但这完全不是这个问题的重点。这是关于了解(例如)想要打破循环依赖问题时的技术可能性。 (4认同)
  • “我不同意 Luc Touraille 的回答”因此,如果您需要长度,请给他写一条评论,包括博客文章的链接。这并没有回答所提出的问题。如果每个人都思考关于 X 如何工作的问题,并给出不同意 X 这样做的合理答案,或者争论我们应该限制使用 X 的自由的限制,那么我们几乎没有真正的答案。 (2认同)