有一定C++开发经验的朋友可能会遇到这样的场景:两个类A与B是强耦合关系,类A要引用B的对象,类B也要引用类A的对象。好的,不难,我的第一直觉让我写出这样的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
<span style=
"color: #ff0000;"
><strong>
// A.h
#include "B.h" </strong> </span>
class
A
{
B b;
public
:
A(
void
);
virtual
~A(
void
);
};
//A.cpp
#include "A.h"
A::A(
void
)
{
}
A::~A(
void
)
{
}
<strong><span style=
"color: #ff0000;"
>
// B.h
#include "A.h" </span> </strong>
class
B
{
A a;
public
:
B(
void
);
~B(
void
);
};
// B.cpp
#include "B.h"
B::B(
void
)
{
}
B::~B(
void
)
{
}
|
好的,完成,编译一下A.cpp,不通过。再编译B.cpp,还是不通过。编译器都被搞晕了,编译器去编译A.h,发现包含了B.h,就去编译 B.h。编译B.h的时候发现包含了A.h,但是A.h已经编译过了(其实没有编译完成,可能编译器做了记录,A.h已经被编译了,这样可以避免陷入死循 环。编译出错总比死循环强点),就没有再次编译A.h就继续编译。后面发现用到了A的定义,这下好了,A的定义并没有编译完成,所以找不到A的定义,就编 译出错了。提示信息如下:
1>d:/vs2010/test/test/a.h(5): error C2146: syntax error : missing ';' before identifier 'b'
1>d:/vs2010/test/test/a.h(5): error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
1>d:/vs2010/test/test/a.h(5): error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
那怎么办?有办法,C++为我们提供了前置声明。前置声明是什么?举个形象点的例子,就是我要盖一个屋子(CHOuse),光有屋子还不行啊,我还得有床 (CBed)。但是屋子还没盖好,总不能先买床吧,床的大小我定了,改天买。先得把房子盖好,盖房子的时候我先给床留个位置,等房子盖好了,我再决定买什 么样的床。前置声明就是我在声明一个类(CHouse)的时候,用到了另外一个类的定义(CBed),但是CBed还没有定义呢,而且我还先不需要 CBed的定义,只要知道CBed是一个类就够了。那好,我就先声明类CBed,告诉编译器CBed是一个类(不用包含CBed的头文件):
- class CBed;
然后在CHouse中用到CBed的,都用CBed的指针类型代(因为指针类型固定大小的,但是CBed的大小只用知道了CBed定义才能确定)。等到要实现CHouse定义的时候(在.h 中不用include 对应类的头文件,直接声明为 指针对象,而在对应的cpp中需要初始化时,才在cpp中include对应类的头文件),就必须要知道CBed的定义了,那是再包好CBed的头文件就行了。
前置声明有时候很有用,比如说两个类相互依赖的时候要。还有前置声明可以减少头文件的包含层次,减少出错可能。上面说的例子。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
// House.h
class
CBed; /<strong>/ 盖房子时:现在先不买,肯定要买床的</strong> 不用先#include CBed.h
class
CHouse
{
CBed<span style=
"color: #ff0000;"
><strong>*</strong></span> bed;
// 我先给床留个位置 <span style="color: #ff0000;"><strong>,必须声明为指针或者引用,如果要声明为对象,则必须要#include CBed.h</strong></span>
public
:
CHouse(
void
);
virtual
~CHouse(
void
);
void
GoToBed();
};
// <span style="color: #ff0000;"><strong>House.cpp </strong>
<strong>#include
"Bed.h"
</strong> </span>
#include "House.h" // 等房子开始装修了,要买床了
CHouse::CHouse(
void
)
{
<span style=
"color: #ff0000;"
><strong> bed =
new
CBed();
// 把床放进房子 </strong> </span>
}
CHouse::~CHouse(
void
)
{
}
void
CHouse::GoToBed()
{
bed->Sleep();
}
// Bed.h
class
CBed
{
public
:
CBed(
void
);
~CBed(
void
);
void
Sleep();
};
// Bed.cpp
#include "Bed.h"
CBed::CBed(
void
)
{
}
CBed::~CBed(
void
)
{
}
void
CBed::Sleep()
{
}
|
前置声明中的陷阱
注意这里有陷阱:
1、CBed* bed;必须用指针或引用
引用版本:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
// House.h
class
CBed;
// 盖房子时:现在先不买,肯定要买床的
class
CHouse
{
CBed& bed;
// 我先给床留个位置
<strong>
// CBed bed; // 编译出错 </strong> 必须要include CBed.h 才能直接声明对象,因为不允许在定义对象之前就声明...,但是允许在定义对象之前先声明对象指针(空指针)
public
:
CHouse(
void
);
CHouse(CBed& bedTmp);
virtual
~CHouse(
void
);
void
GoToBed();
};
// House.cpp
<span style=
"color: #ff0000;"
><strong> #include
"Bed.h"
</strong> </span>
#include "House.h" // 等房子开始装修了,要买床了
CHouse::CHouse(
void
)
: bed(*
new
CBed())
{
CBed* bedTmp =
new
CBed();
// 把床放进房子
bed = *bedTmp;
}
CHouse::CHouse(CBed& bedTmp)
: bed(bedTmp)
{
}
CHouse::~CHouse(
void
)
{
delete
&bed;
}
void
CHouse::GoToBed()
{
bed.Sleep();
}
|
2、不能在CHouse的声明中使用CBed的方法
使用了未定义的类型CBed;
bed->Sleep的左边必须指向类/结构/联合/泛型类型
1
2
3
4
5
6
7
8
9
10
11
12
13
|
class
CBed;
// 盖房子时:现在先不买,肯定要买床的
class
CHouse
{
CBed* bed;
// 我先给床留个位置
// CBed bed; // 编译出错
public
:
CHouse(
void
);
virtual
~CHouse(
void
);
void
GoToBed()
{
bed->Sleep();
// 编译出错,床都没买,怎么能睡
}
};
|
3、在CBed定义之前调用CBed的析构函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
|
// House.h
class
CBed;
// 盖房子时:现在先不买,肯定要买床的
class
CHouse
{
CBed* bed;
// 我先给床留个位置
// CBed bed; // 编译出错
public
:
CHouse(
void
);
virtual
~CHouse(
void
);
void
GoToBed();
void
RemoveBed()
{
delete
bed;
// 我不需要床了,我要把床拆掉。还没买怎么拆?
}
};
// House.cpp
#include "Bed.h"
#include "House.h" // 等房子开始装修了,要买床了
CHouse::CHouse(
void
)
{
bed =
new
CBed();
// 把床放进房子
}
CHouse::~CHouse(
void
)
{
int
i = 1;
}
void
CHouse::GoToBed()
{
bed->Sleep();
}
// Bed.h
class
CBed
{
int
* num;
public
:
CBed(
void
);
~CBed(
void
);
void
Sleep();
};
// Bed.cpp
#include "Bed.h"
CBed::CBed(
void
)
{
num =
new
int
(1);
}
CBed::~CBed(
void
)
{
delete
num;
// 调用不到
}
void
CBed::Sleep()
{
}
//main.cpp
#include "House.h"
int
main()
{
CHouse house;
house.RemoveBed();
}
|
前置声明解决两个类的互相依赖
接下来,给出开篇第一个问题的答案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
// A.h
class
B;
class
A
{
B* b;
public
:
A(
void
);
virtual
~A(
void
);
};
//A.cpp
#include "B.h"
#include "A.h"
A::A(
void
)
{
b =
new
B;
}
A::~A(
void
)
{
}
// B.h
class
A;
class
B
{
A a;
public
:
B(
void
);
~B(
void
);
};
// B.cpp
#include "A.h"
#include "B.h"
B::B(
void
)
{
a = New A;
}
B::~B(
void
)
{
}
|
前置声明在友元类方法中的应用
《C++ Primer 4Edition》在类的友元一章节中说到,如果在一个类A的声明中将另一个类B的成员函数声明为友元函数F,那么类A必须事先知道类B的定义;类B的成 员函数F声明如果使用类A作为形参,那么也必须知道类A的定义,那么两个类就互相依赖了。要解决这个问题必须使用类的前置声明。例如: