C++中前置声明介绍

时间:2023-03-09 19:04:58
C++中前置声明介绍

前置声明是指对类、函数、模板或者结构体进行声明,仅仅是声明,不包含相关具体的定义。在很多场合我们可以用前置声明来代替#include语句。

类的前置声明只是告诉编译器这是一个类型,但无法告知类型的大小,成员等具体内容。在未提供完整的类之前,不能定义该类的对象,也不能在内联成员函数中使用该类的对象。而头文件则一一告之。

如:class Screen;

前置声明,也称前向声明(forward declaration)。在声明之后,定义之前,类Screen是个不完整类型(incomplete type),即已知Screen是一个类型,但是不知道包含哪些成员。

不完全类型只能以有限方式使用。不能定义该类型的对象。不完全类型只能用于定义指向该类型的指针及引用,或者用于声明(而不是定义)使用该类型作为形参类型或返回类型的函数。

可以通过前置声明配合指针或引用类型声明来减少编译依赖。

Never #include a header when a forward declaration will suffice.

前置声明的作用:

(1)、可以减少编译依赖、减少编译时间(如果头文件被修改,会导致多次重新编译);

(2)、可以隐藏细节;

(3)、可以减少类大小(前置声明会告诉这个类的存在,而不用提供类定义的所有细节);

(4)、减少include,防止类间相互引用形成依赖,造成编译不通过.

以下是在Google C++风格指南中对前置声明的介绍

尽可能地避免使用前置声明。使用#include 包含需要的头文件即可。

所谓前置声明(forward declaration)是类、函数和模板的纯粹声明,没伴随着其定义.

优点:

(1)、前置声明能够节省编译时间,多余的 #include 会迫使编译器展开更多的文件,处理更多的输入。

(2)、前置声明能够节省不必要的重新编译的时间。 #include 使代码因为头文件中无关的改动而被重新编译多次。

缺点:

(1)、前置声明隐藏了依赖关系,头文件改动时,用户的代码会跳过必要的重新编译过程。

(2)、前置声明可能会被库的后续更改所破坏。前置声明函数或模板有时会妨碍头文件开发者变动其 API.例如扩大形参类型,加个自带默认参数的模板形参等等。

(3)、前置声明来自命名空间std:: 的 symbol 时,其行为未定义。

(4)、很难判断什么时候该用前置声明,什么时候该用 #include 。极端情况下,用前置声明代替 includes 甚至都会暗暗地改变代码的含义.

结论:

(1)、尽量避免前置声明那些定义在其他项目中的实体.

(2)、函数:总是使用#include.

(3)、类模板:优先使用#include.

以下摘自《Using Incomplete(Forward) Declarations》:

An incomplete declaration(an incomplete declaration is often called a forward declaration) is the keyword class or struct followed by the name of a class or structure type.It tells the compiler that the named class or struct type exists, but doesn't say anything at all about the member functions or variables of the class or struct; this omission means that it is a (seriously) incomplete declaration of the type. Since an incomplete declaration doesn't tell the compiler what is in the class or struct, until the compiler gets the complete declaration, it won't be able to compile code that refers to the members of the class or struct, or requires knowing the size of a class or struct object (to know the size requires knowing the types of the member variables).

Use an incomplete declaration in a header file whenever possible. By using an incomplete declaration in a header file, we can eliminate the need to #include the header file for the class or struct, which reduces the coupling, or dependencies,between modules, resulting in faster compilations and easier development. If the .cpp file needs to access the members of the class or struct, it will then #include the header containing the complete declaration.

When will an incomplete declaration work in a header file:

(1)、If the class type X appears only as the type of a parameter or a return type in a function prototype.

class X;
X foo(X x);

(2)、If the class type X is referred to only by pointer (X*) or reference (X&), even as a member variable of a class declared in A.h.

class X;
class A {
/* other members */
private:
	X* x_ptr;
	X& x_ref;
};

(3)、If you are using an opaque type X as a member variable of a class declared in A.h.This is a type referred to only through a  pointer,and whose complete declaration is not supposed to be available, and is not in any header file. Thus an incomplete declaration of the type is the only declaration your code will ever make or need either in A.h or A.cpp.

When will an incomplete declaration not work in a header file:

(1)、If your A.h header file declares a class A in which the incompletely declared type X appears as the type of a member variable.

class X;
class A {
private:
	X x_member; // error- can't declare a member variable of incomplete type!
};

(2)、If your A.h header file declares a class A in which the incompletely declared type X is abase class (A inherits from X).

class X;
class A : public X { // error - baseclass is incomplete type!

(3)、If you don't actually know the name of the type. You can't forward declare a type unless you know its correct name. This can be a problem with some of the types defined in the Standard Library, where the normal name of the type is actually a typedef for a particular template instantiated with some other type, usually with multiple template parameters. For example, the following will not work to incompletely declare the std::string class:

class std::string;

在Google C++风格指南中,指出尽可能地避免使用前置声明。而在《Using Incomplete(Forward) Declarations》中,指出能用前置声明代替#include的时候,应尽量用前置声明。

以下的内容是摘自:http://*.com/questions/553682/when-can-i-use-a-forward-declaration

#ifndef FBC_MESSY_TEST_FORWARD_DECLARATION_HPP_
#define FBC_MESSY_TEST_FORWARD_DECLARATION_HPP_

// reference: http://*.com/questions/553682/when-can-i-use-a-forward-declaration

/*
 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.

// 1. What you can do with an incomplete type:
// 1.1 Declare a member to be a pointer or a reference to the incomplete type:
class Foo_1 {
	X *pt1;
	X &pt2;
};

// 1.2 Declare functions or methods which accept/return incomplete types:
void f1(X);
X f2();

/* 1.3 Define functions or methods which accept/return pointers/references to
 the incomplete type (but without using its members): */
void f3(X*, X&) {}
X& f4() { X* x = nullptr; return *x; }
X* f5() { X* x = nullptr; return x; }

// 2. What you cannot do with an incomplete type:
// 2.1 Use it as a base class
// class Foo_2 : X {} // compiler error!

// 2.2 Use it to declare a member:
/* class Foo_2 {
	X m; // compiler error!
}; */

// 2.3 Define functions or methods using this type
// void f6(X x) {} // compiler error!
// X f7() {} // compiler error!

/* 2.4 Use its methods or fields,
 in fact trying to dereference a variable with incomplete type */
/* class Foo_3 {
	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.
*/

/*
 "In computer programming, a forward declaration is a declaration of an identifier
 (denoting an entity such as a type, a variable, or a function) for which the
 programmer has not yet given a complete definition."
 In C++, you should forward declare classes instead of including headers.
 Don’t use an #include when a forward declaration would suffice.
 When you include a header file you introduce a dependency
 that will cause your code to be recompiled whenever the header file changes.
 If your header file includes other header files, any change to those files will
 cause any code that includes your header to be recompiled.
 Therefore, you should prefer to minimize includes,
 particularly includes of header files in other header files.
 You can significantly reduce the number of header files
 you need to include in your own header files by using forward declarations.
*/

#endif // FBC_MESSY_TEST_FORWARD_DECLARATION_HPP_

GitHubhttps://github.com/fengbingchun/Messy_Test