C++ 我什么时候可以使用转发声明?

C++ 我什么时候可以使用转发声明?,c++,forward-declaration,c++-faq,C++,Forward Declaration,C++ Faq,我正在寻找允许我在另一个类的头文件中向前声明一个类的定义: 是否允许我为基类、作为成员持有的类、通过引用传递给成员函数的类等执行此操作?只要您不需要定义(考虑指针和引用),就可以不使用前向声明。这就是为什么大多数情况下,您会在头文件中看到它们,而实现文件通常会为相应的定义提取头文件。主要规则是,您只能转发声明其内存布局(以及成员函数和数据成员)在转发声明文件中不需要已知的类 这将排除基类和通过引用和指针使用的类以外的任何类。当您希望使用其他类型(类)作为类的成员时,通常需要在类头文件中使用前向声

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


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

只要您不需要定义(考虑指针和引用),就可以不使用前向声明。这就是为什么大多数情况下,您会在头文件中看到它们,而实现文件通常会为相应的定义提取头文件。

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


这将排除基类和通过引用和指针使用的类以外的任何类。

当您希望使用其他类型(类)作为类的成员时,通常需要在类头文件中使用前向声明。在头文件中不能使用前向声明类方法,因为C++在那个时候还不知道该类的定义。这是您必须移入.cpp文件的逻辑,但是如果您使用的是模板函数,您应该将它们减少为仅使用模板的部分,并将该函数移入标头中

向前声明将使您的代码得以编译(obj已创建)。但是,除非找到定义,否则链接(exe创建)将不会成功

在文件中,您只使用指向类的指针或引用。不应调用任何成员/成员函数来调用这些指针/引用

具有
类Foo//转发声明

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

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


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

以及指向不完整类型的指针和引用,您还可以声明函数原型,以指定不完整类型的参数和/或返回值。但是,不能定义参数或返回类型不完整的函数,除非它是指针或引用

示例:

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

把你自己放在编译器的位置上:当你向前声明一个类型时,编译器只知道这个类型存在;它对其大小、成员或方法一无所知。这就是为什么它被称为不完整类型。因此,不能使用类型来声明成员或基类,因为编译器需要知道类型的布局

假设以下为远期申报

class X;
以下是你能做和不能做的

不完整类型的处理方法:

  • 将成员声明为指向不完整类型的指针或引用:

    class Foo {
        X *p;
        X &r;
    };
    
  • 声明接受/返回不完整类型的函数或方法:

    void f1(X);
    X    f2();
    
    template <typename T>
       void      f1(X<T>);
    template <typename T>
       X<T>    f2();
    
  • 定义接受/返回指向不完整类型(但不使用其成员)的指针/引用的函数或方法:

无法使用不完整的类型执行的操作:

  • 将其用作基类

    class Foo : X {} // compiler error!
    
    class Foo : X<int> {} // compiler error!
    
  • 使用它来声明成员:

    class Foo {
        X m; // compiler error!
    };
    
    class Foo {
        X<int> m; // compiler error!
    };
    
  • 使用此类型定义函数或方法

    void f1(X x) {} // compiler error!
    X    f2()    {} // compiler error!
    
    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
    }
    
  • 使用它的方法或字段,实际上尝试取消引用类型不完整的变量

    class Foo {
        X *m;            
        void method()            
        {
            m->someMethod();      // compiler error!
            int i = m->someField; // compiler error!
        }
    };
    
    class Foo {
        X<int>* m;            
        void method()            
        {
            m->someMethod();      // compiler error!
            int i = m->someField; // compiler error!
        }
    };
    

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

例如,
std::vector
要求其参数为完整类型,而
boost::container::vector
则不要求。有时,只有在使用某些成员函数时才需要完整类型,比如说


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

我遵循的一般规则是不包括任何头文件,除非我必须这样做。因此,除非我将一个类的对象存储为我的类的成员变量,否则我不会包含它,我只使用forward声明。

区分类的用法

class X;
  • 仅在名义上(转发声明足够)和
  • 在大小上(需要类定义)

  • 我从未见过它的发音如此简洁:)

    我写这篇文章是作为一个单独的答案,而不仅仅是一个评论,因为我不同意Luc Touraille的答案,不是基于合法性,而是因为强大的软件和误解的危险

    具体地说,我对你期望你的界面用户必须知道的隐含契约有一个问题

    如果您正在返回或接受引用类型,那么您只是说它们可以传递指针或引用,而它们可能只通过转发声明知道这些指针或引用

    class X;
    
    返回不完整的类型时
    xf2()然后你说你的呼叫者必须有完整的X类型规范。他们需要它来在呼叫站点创建LHS或临时对象

    类似地,如果接受不完整的类型,则调用者必须构造作为参数的对象。即使该对象作为另一个不完整类型从函数返回,调用站点也需要完整声明。i、 e:

    class X;  // forward for two legal declarations 
    X returnsX();
    void XAcceptor(X);
    
    XAcepptor( returnsX() );  // X declaration needs to be known here
    

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

  • 如果此外部依赖是所需的行为。而不是
    void      f3(X<int>*, X<int>&) {}
    X<int>&   f4(X<int>& in) { return in; }
    X<int>*   f5(X<int>* in) { return in; }
    
    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.
    
    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.
    
    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
    }
    
    class Foo : X<int> {} // compiler error!
    
    class Foo {
        X<int> m; // compiler error!
    };
    
    void      f1(X<int> x) {}            // compiler error!
    X<int>    f2() {return X<int>(); }   // compiler error!
    
    class Foo {
        X<int>* m;            
        void method()            
        {
            m->someMethod();      // compiler error!
            int i = m->someField; // compiler error!
        }
    };
    
    template struct X<int>;
    
    void  f6(X*)       {}
    void  f7(X&)       {}
    void  f8(X* x_ptr, X& x_ref) { f6(x_ptr); f7(x_ref); }