Forward declarations like C/C++

Two important purposes of "separation of interface and implementation" in C++ are "reducing compilation dependencies between files" and "hiding the implementation details of objects". The key technology to achieve this goal is the Pimpl mode (pointer to implementation) , that is, all the implementation details of a class are "proxy" to another class to complete, and they are only responsible for providing the interface. The key to realize the "Pimpl pattern" is " depending on the declaration of the object (declaration) rather than the definition (definition) ". So why can the "Pimpl pattern" be achieved through the declaration of the dependent object, thereby realizing the separation of interface and implementation?

Question: Define a class class A, which uses the object b of class B, and then defines a class B, which also contains an object a of class A, it becomes like this:

Compile A.cpp, it does not pass. Compile B.cpp again, but it still fails. The compiler is confused. The compiler compiles Ah and finds that Bh is included, so it compiles Bh. When compiling Bh, it is found that Ah is included, but Ah has been compiled (in fact, it has not been compiled, maybe the compiler has made a record and Ah has been compiled, so as to avoid falling into an infinite loop. Compilation errors are always better than infinite loops) , it will continue to compile without compiling Ah again. Later, it was found that the definition of A was used. This is good. The definition of A has not been compiled, so the definition of A cannot be found, and the compilation error occurs.

There is a mutual inclusion problem between the two files, which is not allowed by C++. The solution is to use forward declarations instead of referencing the header file directly .

 

The advantages of forward declarations

(1) Reduce the size of the class .

(2) The compilation speed is improved , because the compiler only needs to know that the class has been defined without knowing the details of the definition.

Using forward declarations can reduce the number of header files that need to be included. When a header file is included, it is equivalent to introducing a new dependency. As long as the dependent header file is modified, the code will be recompiled. And this dependency is recursively passed, that is, when the header file A is changed, the header file B that includes the header file A and all the header files that include the header file B will be recompiled. Therefore, the occurrence of this situation should be appropriately reduced.

(3) " Separation of interface and implementation " can be achieved through "forward declaration " . We divide the classes that need to be provided to customers into two classes: one only provides the interface, and the other is responsible for the implementation!

2. When to use forward declarations and #include

First, why do we include header files? The answer is simple, usually we need to get a definition of a type. So the next question is, under what circumstances do we need a definition of a type, and under what circumstances do we only need a declaration? The answer is that when we need to know the size of the type or need to know its function signature, we need to get its definition. As follows, which require the definition of C:

  • A inherits from C
  • A has a member variable of type C
  • A has a member variable of type C pointer
  • A has a referenced member variable of type C
  • A has a member variable of type std::list<C>
  • A has a function whose signature has both parameters and return value of type C
  • A has a function whose parameters and return values ​​are of type C in its signature. It calls a function of C, and the code is in A's header file.
  • A has a function whose parameters and return values ​​in its signature are of type C (including type C itself, reference type of C and pointer type of C), and it will call another function using C, the code is written directly in A in the header file
  • C and A are in the same namespace
  • C and A are in different namespaces

Case 1: You must know the definition of C, because A, as a subclass, must know the content of C in order to inherit

Case 2: The definition of C must be known, because the size of A needs to be determined according to C, which is generally improved by Pimpl mode.

Case 3 and Case 4: You don't need to know the definition of C, you only need the forward declaration. A reference is also physically a pointer, and the effect is the same as a pointer. Even if there is no definition of C, there is no problem with A.

情况五:不需要知道C的定义,有可能老式的编译器需要。标准库里面的容器(如:list、vector、map),在包括一个list<C>,vector<C>,map<C, C>类型的成员变量的时候,都不需要C的定义。因为它们内部其实也是使用C的指针作为成员变量,它们的大小一开始就是固定的了,不会根据模版参数的不同而改变。

情况六:不需要知道C的定义

情况七:必须要知道C的定义,需要知道调用函数的签名。

情况八:对于引用和指针情况一样

例如:

类C中有:C& CdoSomething(C&);

类A中有:C& AdoSomething (C& c) { return CdoSomething (c);};

以上情况,不需要知道C的定义,但是对上面的函数任意一个C&换成C,比如像下面的几种示例:

类C中有:C& CdoSomething (C&);

类A中有:C& AdoSomething (C c) {return CdoSomething (c);};

类C中有:C& CdoSomething (C);

类A中有:C& AdoSomething (C& c) {return CdoSomething (c);};

类C中有:C CdoSomething (C&);

类A中有:C& AdoSomething (C& c) {return CdoSomething (c);};

类C中有:C& CdoSomething (C&);

类A中有:C AdoSomething (C& c) {return CdoSomething (c);};

那么就必须要C的定义。无论哪一种,其实都隐式包含了一个拷贝构造函数的调用,比如1中参数c由拷贝构造函数生成,3中CdoSomething的返回值是一个由拷贝构造函数生成的匿名对象。因为我们调用了C的拷贝构造函数,所以以上无论那种情形都需要知道C的定义。

情况九和情况十:不需要知道C的定义。

二、结论

(1)前置声明只能作为指针引用,不能定义类的对象,自然也就不能调用对象中的方法了。

(2)而且需要注意,如果将类A的成员变量B* b;改写成B& b;的话,必须要将b在A类的构造函数中,采用初始化列表的方式初始化,否则也会出错。

(3)尽量不要在头文件中包含另外的头文件。尽量在头文件中使用类前置声明程序下文中要用到的类,实在需要包含其它的头文件时,可以把它放在我们的类实现文件(cpp)中。

正常结构的C++如下:

// House.h

classCBed; // 盖房子时:现在先不买,肯定要买床

classCHouse

{

private:

CBed &bed; // 我先给床留个位

// CBed bed; // 编译出

public:

CHouse(void);

CHouse(CBed &bedTmp);

virtual~CHouse(void);

voidGoToBed();

};

 

// House.cpp

#include"Bed.h"

#include"House.h"// 等房子开始装修了,要买床

 

CHouse::CHouse(void)

: bed(*newCBed()) // 这里对引用的赋

{

CBed *bedTmp = newCBed(); // 把床放进房

bed = *bedTmp;

}

CHouse::CHouse(CBed &bedTmp)

: bed(bedTmp)

{

}

 

CHouse::~CHouse(void)

{

delete &bed;

}

 

voidCHouse::GoToBed()

{

bed.Sleep();

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325379048&siteId=291194637