C++ 我什么时候可以使用前向声明?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/553682/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 15:55:40  来源:igfitidea点击:

When can I use a forward declaration?

c++forward-declarationc++-faq

提问by Igor Oks

I am looking for the definition of when I am allowed to do forward declaration of a class in another class's header file:

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

Am I allowed to do it for a base class, for a class held as a member, for a class passed to member function by reference, etc. ?

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

回答by Luc Touraille

Put yourself in the compiler's position: when you forward declare a type, all the compiler knows is that this type exists; it knows nothing about its size, members, or methods. This is why it's called an incomplete type. Therefore, you cannot use the type to declare a member, or a base class, since the compiler would need to know the layout of the type.

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

Assuming the following forward declaration.

假设以下前向声明。

class X;

Here's what you can and cannot do.

这是您可以做什么和不可以做什么。

What you can do with an incomplete type:

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

  • Declare a member to be a pointer or a reference to the incomplete type:

    class Foo {
        X *p;
        X &r;
    };
    
  • Declarefunctions or methods which accept/return incomplete types:

    void f1(X);
    X    f2();
    
  • Definefunctions or methods which accept/return pointers/references to the incomplete type (but without using its members):

    void f3(X*, X&) {}
    X&   f4()       {}
    X*   f5()       {}
    
  • 将成员声明为指向不完整类型的指针或引用:

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

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

    void f3(X*, X&) {}
    X&   f4()       {}
    X*   f5()       {}
    

What you cannot do with an incomplete type:

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

  • Use it as a base class

    class Foo : X {} // compiler error!
    
  • Use it to declare a member:

    class Foo {
        X m; // compiler error!
    };
    
  • Definefunctions or methods using this type

    void f1(X x) {} // compiler error!
    X    f2()    {} // compiler error!
    
  • Use its methods or fields, in fact trying to dereference a variable with incomplete type

    class Foo {
        X *m;            
        void method()            
        {
            m->someMethod();      // compiler error!
            int i = m->someField; // compiler error!
        }
    };
    
  • 将其用作基类

    class Foo : X {} // compiler error!
    
  • 用它来声明一个成员:

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

    void f1(X x) {} // compiler error!
    X    f2()    {} // compiler error!
    
  • 使用它的方法或字段,实际上试图取消引用类型不完整的变量

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


When it comes to templates, there is no absolute rule: whether you can use an incomplete type as a template parameter is dependent on the way the type is used in the template.

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

For instance, std::vector<T>requires its parameter to be a complete type, while boost::container::vector<T>does not. Sometimes, a complete type is required only if you use certain member functions; this is the case for std::unique_ptr<T>, for example.

例如,std::vector<T>要求它的参数是一个完整的类型,而boost::container::vector<T>没有。有时,只有在使用某些成员函数时才需要完整类型;例如,就是这种情况std::unique_ptr<T>

A well-documented template should indicate in its documentation all the requirements of its parameters, including whether they need to be complete types or not.

一个有据可查的模板应该在其文档中指明其参数的所有要求,包括它们是否需要是完整的类型。

回答by Timo Geusch

The main rule is that you can only forward-declare classes whose memory layout (and thus member functions and data members) do not need to be known in the file you forward-declare it.

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

This would rule out base classes and anything but classes used via references and pointers.

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

回答by Marc Mutz - mmutz

Lakosdistinguishes between class usage

Lakos区分类用法

  1. in-name-only(for which a forward declaration is sufficient) and
  2. in-size(for which the class definition is needed).
  1. in-name-only(前向声明就足够了)和
  2. in-size(需要类定义)。

I've never seen it pronounced more succinctly :)

我从未见过它更简洁地发音:)

回答by j_random_hacker

As well as pointers and references to incomplete types, you can also declare function prototypes that specify parameters and/or return values that are incomplete types. However, you cannot definea function having a parameter or return type that is incomplete, unless it is a pointer or reference.

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

Examples:

例子:

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

回答by R Sahu

None of the answers so far describe when one can use a forward declaration of a class template. So, here it goes.

到目前为止,没有任何答案描述何时可以使用类模板的前向声明。所以,它来了。

A class template can be forwarded declared as:

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

template <typename> struct X;

Following the structure of the accepted answer,

按照接受的答案的结构,

Here's what you can and cannot do.

这是您可以做什么和不可以做什么。

What you can do with an incomplete type:

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

  • Declare a member to be a pointer or a reference to the incomplete type in another class template:

    template <typename T>
    class Foo {
        X<T>* ptr;
        X<T>& ref;
    };
    
  • Declare a member to be a pointer or a reference to one of its incomplete instantiations:

    class Foo {
        X<int>* ptr;
        X<int>& ref;
    };
    
  • Declare function templates or member function templates which accept/return incomplete types:

    template <typename T>
       void      f1(X<T>);
    template <typename T>
       X<T>    f2();
    
  • Declare functions or member functions which accept/return one of its incomplete instantiations:

    void      f1(X<int>);
    X<int>    f2();
    
  • Define function templates or member function templates which accept/return pointers/references to the incomplete type (but without using its members):

    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; }
    
  • Define functions or methods which accept/return pointers/references to one of its incomplete instantiations (but without using its members):

    void      f3(X<int>*, X<int>&) {}
    X<int>&   f4(X<int>& in) { return in; }
    X<int>*   f5(X<int>* in) { return in; }
    
  • Use it as a base class of another template class

    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.
    
  • Use it to declare a member of another class template:

    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.
    
  • Definefunction templates or methods using this type

    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
    }
    
  • 将成员声明为指向另一个类模板中不完整类型的指针或引用:

    template <typename T>
    class Foo {
        X<T>* ptr;
        X<T>& ref;
    };
    
  • 将成员声明为指针或对其不完整实例之一的引用:

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

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

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

    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; }
    
  • 定义接受/返回指向其不完整实例之一的指针/引用的函数或方法(但不使用其成员):

    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
    }
    

What you cannot do with an incomplete type:

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

  • Use one of its instantiations as a base class

    class Foo : X<int> {} // compiler error!
    
  • Use one of its instantiations to declare a member:

    class Foo {
        X<int> m; // compiler error!
    };
    
  • Definefunctions or methods using one of its instantiations

    void      f1(X<int> x) {}            // compiler error!
    X<int>    f2() {return X<int>(); }   // compiler error!
    
  • Use the methods or fields of one of its instantiations, in fact trying to dereference a variable with incomplete type

    class Foo {
        X<int>* m;            
        void method()            
        {
            m->someMethod();      // compiler error!
            int i = m->someField; // compiler error!
        }
    };
    
  • Create explicit instantiations of the class template

    template struct X<int>;
    
  • 使用其实例之一作为基类

    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>;
    

回答by Andy Dent

I'm writing this as a separate answer rather than just a comment because I disagree with Luc Touraille's answer, not on the grounds of legality but for robust software and the danger of misinterpretation.

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

Specifically, I have an issue with the implied contract of what you expect users of your interface to have to know.

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

If you are returning or accepting reference types, then you are just saying they can pass through a pointer or reference which they may in turn have known only through a forward declaration.

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

When you are returning an incomplete type X f2();then you are saying your caller musthave the full type specification of X. They need it in order to create the LHS or temporary object at the call site.

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

Similarly, if you accept an incomplete type, the caller has to have constructed the object which is the parameter. Even if that object was returned as another incomplete type from a function, the call site needs the full declaration. i.e.:

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

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

XAcepptor( returnsX() );  // X declaration needs to be known here

I think there's an important principle that a header should supply enough information to use it without a dependency requiring other headers. That means header should be able to be included in a compilation unit without causing a compiler error when you use any functions it declares.

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

Except

除了

  1. If this external dependency is desiredbehaviour. Instead of using conditional compilation you could have a well-documentedrequirement for them to supply their own header declaring X. This is an alternative to using #ifdefs and can be a useful way to introduce mocks or other variants.

  2. The important distinction being some template techniques where you are explicitly NOT expected to instantiate them, mentioned just so someone doesn't get snarky with me.

  1. 如果这种外部依赖是所需的行为。相反,使用条件编译的,你可以有一个证据充分的需求为他们提供自己的头,宣布X.这是使用的#ifdefs的替代性和可引入嘲笑或其它变体的有效途径。

  2. 重要的区别是一些模板技术,你明确不希望实例化它们,提到只是为了让别人不会对我嗤之以鼻。

回答by yesraaj

In file in which you use only Pointer or Reference to a class.And no member/member function should be invoked thought those Pointer/ reference.

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

with class Foo;//forward declaration

class Foo;//前向声明

We can declare data members of type Foo* or Foo&.

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

We can declare (but not define) functions with arguments, and/or return values, of type Foo.

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

We can declare static data members of type Foo. This is because static data members are defined outside the class definition.

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

回答by Naveen

The general rule I follow is not to include any header file unless I have to. So unless I am storing the object of a class as a member variable of my class I won't include it, I'll just use the forward declaration.

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

回答by dirkgently

As long as you don't need the definition (think pointers and references) you can get away with forward declarations. This is why mostly you'd see them in headers while implementation files typically will pull the header for the appropriate definition(s).

只要您不需要定义(想想指针和引用),您就可以摆脱前向声明。这就是为什么大多数情况下您会在标题中看到它们,而实现文件通常会为适当的定义提取标题。

回答by Patrick Glandien

You will usually want to use forward declaration in a classes header file when you want to use the other type (class) as a member of the class. You can not use the forward-declared classes methodsin the header file because C++ does not know the definition of that class at that point yet. That's logic you have to move into the .cpp-files, but if you are using template-functions you should reduce them to only the part that uses the template and move that function into the header.

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