原文:http://blog.csdn.net/SpriteLW/article/details/965702
作者:
SpriteLW
差不多一年时间没用过C++写过程序了,由于工作的需要,我又回到了C++的阵形。在工作的过程中遇到了很多麻烦,当我往工程里加一个类,而且那个类又与工程里的类相关,如有那个类型的成员变量。情况如下
//////A.h///////////
class A
{
.......
};
////////B.h//////////
class B:A
{
....
A member;
}
结果,编译就会出错,说找不到类形A。解决的办法是在B.h里#include “A.h”。但是有时候不用#include
“A.h”,只要在classB:A前加class A;就可以了。更严重的是不但要#include “A.h”,还要class
A;。
起初觉得没问题,因为这样搞来搞去总会编译通过的,而且不会让程序变大,因为
有#ifndef...#endif和#pragma
once控制。直到有一次,我需要那些常量放到一个文件中“const.h”,然后include到其它需要它的类中,结果怎么也编译不成功(因为文件多
了,而且每个文件都这样互相include,把我也蒙了)
直到今天终于从《Effective
C++》里找到原理。现向大家分享一下,首先我以下面这个类结构作例子。(先不管我为什么不加一个Woman,为什么Man就有child,我只是作例子解说,绝没有性别歧视。
代码如下:
////////////main.h//////////////
#include "stdafx.h"
#include "man.h"
int main(){
Man m;
return 0;
}
////////////Person.h/////////////
#pragma once
class Person
{
public:
Person(void);
~Person(void);
};
////////Person.cpp///////////
#include "StdAfx.h"
#include "./person.h"
Person::Person(void){
}
Person::~Person(void){
}
/////////Man.h///////////
#pragma once
#include "person.h"
class Man : public
Person
{
public:
Man(void);
~Man(void);
private:
Person child;
};
/////////////Man.cpp//////////////
#include "StdAfx.h"
#include "./man.h"
Man::Man(void){
}
Man::~Man(void){
}
上述代码,编译运行一切正常。现在我作以下修改:
/////////Man.h///////////
#pragma once
//#include
"person.h"
//去掉
class Man : public
Person
{
public:
Man(void);
~Man(void);
private:
Person child;
};
|
/////////Man.h///////////
#pragma once
//#include
"person.h"
//去掉
class Person;
//加入
class Man:public
Person
{
public:
Man(void);
~Man(void);
private:
Person child;
};
|
error C2504: “Person” : 未定义基类
|
error C2504: “Person” : 未定义基类
|
/////////Man.h///////////
#pragma once
//#include
"person.h"
//去掉
class Person;
//加入
class Man:public
Person
{
public:
Man(void);
~Man(void);
private:
Person
*child;
//改为指针
};
|
/////////Man.h///////////
#pragma once
//#include
"person.h"
//去掉
class Person;
//加入
class Man
//去掉:Person
{
public:
Man(void);
~Man(void);
private:
Person
*child;
//改为指针
};
|
error C2504: “Person” : 未定义基类
|
编译通过
|
要讲解上面的代码还要一些预备知备,看下面代码:
int main()
{
int x;
Person p;//用C++时编译不通过;
}
当编译器看到x定义式时,它们知道必须配置足够的空间以放置一个int。没
问题,每个编译器都知道int有多大。然而当编译器看到p的定义式时,虽然它们也知道必须配置足够空间以放置一个Person,但一个Person对象有
多大呢?编译器获得这项信息的唯一办法就是询问class定义式。然而class的定义式可以合法地不列出实现细节(如:
只写出class
Person;)那么编译器又如何知道该配置多少空间呢?
对Java等语言对此问题的解法是,当程序定义出一个对象时,只配置足够空间给一个“指向该对象的指针”使用,如
public Person;
public static
void main(String[]
args)
{
Person p;
}
对于C++就如下那样:
class Person;
int main()
{
Person *p;//编译器当要配置一个指针大小的空间的指针给p就可以了。
//Person
&p2; 这个理论上也可以,但references
object必须“言之有物”
return 0;
}
看回刚才那段代码为什么“Person p;//用C++时编译不通过;”呢?因为它要调用Person
constructor。那就是Person的实现细节。
现在可以解说上面的表格了,我的目的是 去掉#include
“Person.h”并加入class Person;
所以要做有:
1.
将Person child改为Person
*child。因为child也是Man的成员,Man的大小与Child相关,而child不是内部类型,它的大小编译器不知道。
2.
将:public
Person去掉。因为Man继承Person,所以编译器也要知道Person是怎样实现的,那样才能构造出正确的Man来(为了编译成功,我忍痛割爱了)。
同时我也要对原码作一下解释:
/////////Man.h///////////
#pragma once
#include "person.h"
class Man : public
Person
{
public:
Man(void);
~Man(void);
private:
Person child;
};
这里#include
“person.h”不但包含了Person的定义,也包含了Person的实现细节,所以是编译成功的。
结论:
1.
当不需要调用类的实现时,包括constructor,copy constructor,assignment
operator,member function,甚至是address-of
operator时,就不用#include,只要forward declaration就可以了。
2.
当要用到类的上面那些“方法”时,就要#include
扩充:
为了加深认识,我分享遇到的另一情况。
////////////Person.h/////////////
#pragma
once
class
Person
{
public:
Person(void);
~Person(void);
virtual void addChild(Person p) = 0;//将Person变为抽象类
};
|
/////////Man.h///////////
#pragma
once
//#include
"person.h"
//去掉
class
Person;
//加入
class
Man
//去掉:Person
{
public:
Man(void);
~Man(void);
private:
Person *child;
void addChild(Person
p);//相应地在Man.cpp中加上这个空函数
};
|
|
error C2259: “Person” : 不能实例化抽象类
|
/////////Man.h///////////
#pragma
once
#include
"person.h" //加回来
class
Person;
//加不加入也没所谓
class
Man
//去掉:Person
{
public:
Man(void);
~Man(void);
private:
Person *child;
void addChild(Person
p);//相应地在Man.cpp中加上这个空函数
};
|
/////////Man.h///////////
#pragma
once
#include
"person.h" //加回来
class
Person;
//加不加入也没所谓
class
Man
//去掉:Person
{
public:
Man(void);
~Man(void);
private:
Person *child;
void addChild(Person
*p);//将形参变为Person*
};
|
error C2259: “Person” : 不能实例化抽象类
|
编译成功
|
/////////Man.h///////////
#pragma
once
#include
"person.h" //加回来
class
Person;
//加不加入也没所谓
class
Man
//去掉:Person
{
public:
Man(void);
~Man(void);
private:
Person *child;
void addChild(Person
&p);//将形参变为Person&
};
|
|
编译成功
|
|
为什么出现不能实例化抽象类?我并没有实例化过它。
这是参数的传递问题。当一个变量传给函数时,我们说是实参传给形参(pass-by-value),形参是通过copy
constructor建立的,所以就是实例化了一个抽象类。而pass-by-reference和传指针就没问题了。(全文完)
参考资料:
候捷:《Effective C++》
网址:http://www.cnblogs.com/ashboy/archive/2013/01/18/2867112.html
作者:
Ash_boy
Compiler Error C2504
C2504:'class' : base class undefined
一般出现了这个错误,后面将跟随若干个成员函数,成员变量未定义的错误,这是因为这个undefined base
class中的成员不能被识别造成的。
首先给出MSDN上的解释:
This error can be caused by a missing include file or an
external base class that was not declared with the extern
specifier.
就是说,没有包含头文件或者是未用extern声明的外部的基类。
1,没有include头文件的话include "相应的头文件"。
2,而未用extern声明的话,我觉得应该比较少见,也许个别版本存在可以使用extern声明外部基类,6.0版本应该不存在。C++讲究封装性,extern明显的破坏了封装性,而且只是声明了对象,不能理解跟基类未定义有什么关。
其他的几种可能:
3,
在编写函数或类的时候,括号匹配出现错误,少了右括号(‘}’,‘)’),比较难发现。一般是在一个文件文件中少写了‘}’,而再基类的头文件中包含了这
个文件,这可能使得基类定义那一部分被包含到前面的文件中,因为编译器认为一个类或函数结束的标志是‘}’,直到找到'‘}’。
4,头文件包含出现错误。比如,基类头文件包含了派生类的头文件。这种情况可能比较难找到错误的根源,特别是在类比较多,结构比较复杂的时候,这就需要我们平时十分的细心和耐心了。
加载中,请稍候......