developer tip

앞으로 선언은 언제 사용할 수 있습니까?

optionbox 2020. 10. 3. 10:32
반응형

앞으로 선언은 언제 사용할 수 있습니까?


다른 클래스의 헤더 파일에서 클래스를 앞으로 선언 할 수있는시기에 대한 정의를 찾고 있습니다.

기본 클래스, 멤버로 보유 된 클래스, 참조로 멤버 함수에 전달 된 클래스 등에 대해 수행 할 수 있습니까?


컴파일러의 입장에 서십시오. 유형을 앞으로 선언 할 때 컴파일러는이 유형이 존재한다는 것을 알고 있습니다. 크기, 멤버 또는 방법에 대해 아무것도 모릅니다. 이것이 불완전 유형 이라고 불리는 이유 입니다. 따라서 컴파일러가 형식의 레이아웃을 알아야하므로 형식을 사용하여 멤버 또는 기본 클래스를 선언 할 수 없습니다.

다음과 같은 전방 선언을 가정합니다.

class X;

할 수있는 것과 할 수없는 것은 다음과 같습니다.

불완전한 유형으로 수행 할 수있는 작업 :

  • 멤버를 불완전한 유형에 대한 포인터 또는 참조로 선언하십시오.

    class Foo {
        X *p;
        X &r;
    };
    
  • 불완전한 유형을 허용 / 반환하는 함수 또는 메소드를 선언하십시오 .

    void f1(X);
    X    f2();
    
  • 불완전한 유형에 대한 포인터 / 참조를 수락 / 반환하는 함수 또는 메서드를 정의 합니다 (하지만 멤버를 사용하지 않음).

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

불완전한 유형으로 할 수없는 작업 :

  • 기본 클래스로 사용

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

템플릿과 관련하여 절대적인 규칙은 없습니다. 불완전한 유형을 템플릿 매개 변수로 사용할 수 있는지 여부는 유형이 템플릿에서 사용되는 방식에 따라 다릅니다.

예를 들어, std::vector<T>매개 변수는 완전한 유형이어야하지만 boost::container::vector<T>그렇지 않습니다. 때로는 특정 멤버 함수를 사용하는 경우에만 완전한 유형이 필요합니다. 예를 들어이 경우입니다std::unique_ptr<T> .

잘 문서화 된 템플릿은 완전한 유형이어야하는지 여부를 포함하여 매개 변수의 모든 요구 사항을 문서에 표시해야합니다.


주요 규칙은 메모리 레이아웃 (따라서 멤버 함수 및 데이터 멤버)을 포워드 선언하는 파일에서 알 필요가없는 클래스 만 포워드 선언 할 수 있다는 것입니다.

이것은 기본 클래스와 참조 및 포인터를 통해 사용되는 클래스를 제외한 모든 것을 배제합니다.


Lakos 는 클래스 사용을 구별합니다.

  1. 이름 전용 (정방향 선언으로 충분 함) 및
  2. in-size (for which the class definition is needed).

I've never seen it pronounced more succinctly :)


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 define a 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

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.
    
  • Define function 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
    }
    

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!
    };
    
  • Define functions 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>;
    

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

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

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

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


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.

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 must have the full type specification of X. They need it in order to create the LHS or temporary object at the call site.

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.:

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 desired behaviour. Instead of using conditional compilation you could have a well-documented requirement 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.


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.


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).


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 methods in 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.


Take it that forward declaration will get your code to compile (obj is created). Linking however (exe creation) will not be successfull unless the definitions are found.


I just want to add one important thing you can do with a forwarded class not mentioned in the answer of Luc Touraille.

What you can do with an incomplete type:

Define functions or methods which accept/return pointers/references to the incomplete type and forward that pointers/references to another function.

void  f6(X*)       {}
void  f7(X&)       {}
void  f8(X* x_ptr, X& x_ref) { f6(x_ptr); f7(x_ref); }

A module can pass through an object of a forward declared class to another module.


As, Luc Touraille has already explained it very well where to use and not use forward declaration of the class.

I will just add to that why we need to use it.

We should be using Forward declaration wherever possible to avoid the unwanted dependency injection.

As #include header files are added on multiple files therefore, if we add a header into another header file it will add unwanted dependency injection in various parts of source code which can be avoided by adding #include header into .cpp files wherever possible rather than adding to another header file and use class forward declaration wherever possible in header .h files.

참고URL : https://stackoverflow.com/questions/553682/when-can-i-use-a-forward-declaration

반응형