博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

前向申明 相互include

Posted on 2016-01-13 11:17  bw_0927  阅读(652)  评论(0)    收藏  举报

http://fpcfjf.blog.163.com/blog/static/55469793200991392121101

https://www.cnblogs.com/my_life/articles/5165419.html

 

在构造自己的类时,有可能会碰到两个类之间的相互引用问题,例如:定义了类A类B,A中使用了B定义的类型,B中也使用了A定义的类型
class A
{
    int i;
    B b;
}

class B
{
    int i;
    A* a;
}

请注意上面的定义内容,一般情况下是不能出现类A,类B相互引用都定义对象,即如下的样子:
class A
{
  int i;
  B  b;
}

class B
{
  int i;
  A   a;
}
在这种情况下,想想能够有a.b.a.b.a.b.a.b.a.b…………,很有点子子孙孙无穷尽之状,那么我的机器也无法承受。最主要的还是这种关系很难存在,也很难管理。这种定义方式类同程式中的死循环。所以,一般来说,两者的定义,至少有一方是使用指针,或两者都使用指针,但是决不能两者都定义实体对象。只有这样才不会死循环,因为指针或引用需要自己赋值。

言归正传,那么,在定义时因为相互引用肯定会需要相互包含头文档,假如仅仅只是在各自的头文档中包含对方的头文档,是通但是编译的,如下:
//class A.h
#include "B.h"
class A
{
  int i;
  B   b;
}

//class B.h
#include "A.h"
class B
{
  int i;
  A *a;
}

如上的包含方式可能会造成编译器有错误提示:A.h文档中使用了示知类型B。
怎么办?
一般的做法是:两个类的头文档之中,选一个包含另一个类的头文档,但另一个头文档中只能采用class *;的申明形式,而在实现文档中(*.cpp)中包含头文档,如下:
//class A.h
#include "B.h"
class A
{
  int i;
  B   b;
}

//class B.h
class A;

class B
{
  int i;
  A *a;
}

//B.cpp
//在B.cpp中的文档包含处要有下面语句,否则不能调用成员a的任何内容
#include "A.h"
B::B()
{
……
}

 

 

 class A 的 cpp 文件第一个包含的头文件应该是 class A 的头文件

 


https://blog.csdn.net/fengbingchun/article/details/54957885

类的前向声明带来的问题【has incomplete type

error: variable has incomplete type

即使在源文件中include了前向申明的头文件,在源文件中依然无法使用类的对象,只能通过指针和引用。

所以想使用前向申明,得保障.h 和.cpp中都不会使用类对象, 只能以指针或引用的方式使用

 

在头文件中,只能使用引用或者指针;    在源文件中依旧只能使用指针或者引用来访问类内的成员

 


https://stackoverflow.com/questions/19962812/error-member-access-into-incomplete-type-forward-declaration-of

// forward
class B;

class{       
   void doSomething(B * b) {    //类内定义的是内联函数
      b->add();
   }
};

class B {
   void add() {
      ...
   }
};

The forward does not work, I cannot compile.

I get this error:

error: member access into incomplete type 'B'
note: forward declaration of 'B'


 

Move doSomething definition outside of its class declaration and after B and also make add accessible to A by public-ing it or friend-ing it.

class B;

class A
{
    void doSomething(B * b);
};

class B
{
public:
    void add() {}
};

void A::doSomething(B * b)
{
    b->add();
}

 

前置声明是指对类、函数、模板或者结构体进行声明,仅仅是声明,不包含相关具体的定义。在很多场合我们可以用前置声明来代替#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://stackoverflow.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://stackoverflow.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_

GitHub:https://github.com/fengbingchun/Messy_Test
————————————————
版权声明:本文为CSDN博主「fengbingchun」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/fengbingchun/article/details/54957885