【从 C 向 C++ 进阶】- 类 - 17. 类的提前声明

从 C 向 C++ 进阶系列导航


1. 问题描述

在博文【从 C 向 C++ 进阶】- 类 - 16. 类类型转换中最后的实验时,一直遇到了 “error: invalid use of incomplete type ‘class Test_B’” 这个编译错误,报错的源码如下:

class Test_B;

class Test_A
{
private:
	int m_var;

public:
	Test_A(int num)
	{
		m_var = num;
	}
	explicit Test_A(Test_B& obj)
	{
		m_var = obj.GetVar();     // error: invalid use of incomplete type ‘class Test_B’
	}
	int GetVar()
	{
		return m_var;
	}
	friend class Test_B;
};

class Test_B
{
private:
	int m_var;
		
public:
	Test_B(int num = 0)
	{
		m_var = num;
	}
	int GetVar()
	{
		return m_var;
	}
	operator Test_A()
	{
		Test_A obj = 0;
		obj.m_var = m_var;
		return obj;
	}
};

int main(int argc, char *argv[])
{	
	Test_B obj_B(1);
	Test_A obj_A = obj_B;	
	cout << "obj_A.GetVar() = " << obj_A.GetVar() << endl;
}

程序的本意是验证转换构造函数与类型转换函数的冲突的,然而冲突问题在函数前加上关键字 explicit 后就应该解决了,但类型不完整这个报错怎么都解决不了。

可很明显地,在 Test_A 前对 Test_B 进行了类的声明,按理来说应该是能找到类的实现的,但为什么编译器还是提示类型不完整呢?


2. 问题分析

其实原因很简单,是我们对类类型的声明的理解有偏差。

编译器对代码是从前到后编译的。当编译器遇到类类型声明时,表示编译器知道了这个类类型的存在,会把该类的名称加载到符号表中,然后继续编译。当遇到类的具体实现时,则会把实现与符号表中的类名称关联起来。但如果在遇到类的实现前,程序使用了该类类型,编译器会发现虽然符号表中存在该类的名称,但找不到具体的类实现,于是提示类型不完整。


3. 解决方案

既然是编译器在遇到类实现前,发现程序中使用了该类类型时会报错提示类型不完整,那么把类的实现放到使用地方的前面不就解决问题了吗?这个解决方法是正确的,但仅仅如此还是不够的。假设 Test_A 类的成员函数使用 Test_B 类类型,Test_B 类的成员函数又使用 Test_A 类类型,这样交叉使用类类型的情况,无论怎么放置两个类的相对位置也无法满足我们的解决方法了。

所以,正确的做法是把类函数的声明与实现进行分离,即在类中仅声明成员函数,而函数体在类外实现。因为函数声明也只是把函数名称进入符号表中,并不使用类类型,只要保证函数体在所使用的其他类类型类的实现之后即可。例如 Test_A 类的成员函数体放在 Test_B 类的实现之后,那么 Test_A 类的成员函数便可使用 Test_B 类类型。

需要注意的是,假设在函数的声明中有使用其他类类型,即其他类类型作为形参类型或返回类型时,必须在成员函数声明前即类的实现前声明所使用的其他类,否则编译器根本不知道所使用的其他类类型为我们自定义的类类型,这也称为类的提前声明。例如 Test_A 类的成员函数返回值类型为 Test_B 类类型,那么必须在 Test_A 类前声明 Test_B 类。

其实这也是为什么类的实现一般放在头文件,而类的成员函数的实现会单独放在一个文件原因。把类的实现与成员函数的实现分离之后,不但能够方便地对类进行管理与阅读,也保证了类的实现在使用类类型的程序之前。因此,为了减少这类类型不完整的 BUG,工程中对于类的形态应遵循这样的分离规则。

修改后的源码如下:

class Test_B;

class Test_A
{
private:
	int m_var;

public:
	Test_A(int num)
	{
		m_var = num;
	}
	explicit Test_A(Test_B& obj);
	int GetVar()
	{
		return m_var;
	}
	friend class Test_B;
};

class Test_B
{
private:
	int m_var;
		
public:
	Test_B(int num = 0)
	{
		m_var = num;
	}
	int GetVar()
	{
		return m_var;
	}
	operator Test_A()
	{
		Test_A obj = 0;
		obj.m_var = m_var;
		return obj;
	}
};

Test_A::Test_A(Test_B& obj)
{
	var = obj.GetVar();
}

int main(int argc, char *argv[])
{	
	Test_B obj_B(1);
	Test_A obj_A = obj_B;
	cout << "obj_A.GetVar() = " << obj_A.GetVar() << endl;  // obj_A.GetVar() = 1
}
发布了60 篇原创文章 · 获赞 36 · 访问量 5943

猜你喜欢

转载自blog.csdn.net/qq_35692077/article/details/97118794
今日推荐