前置声明/超前声明(forward declaration)问题

    在C++中,对于类(或者结构体),一般我们会在.h文件中定义类,在相应的.cpp文件中实现类内定义的方法,这是前人留下的智慧,最好不要改动,否则就可能会遇到forward declaration问题。

前置声明/超前声明(forward declaration)————问题一

    如果提前声明了一个类,但在此之前并没有定义它,请使用指针或者引用,如下:

class B;
class A {
public:
	void print(B b) {
		cout << "This is A, use print(B b) function" << endl;
	}
};

class B {
public:
	void print() {
		cout << "this is B" << endl;
	}

};

int main() {
	A a;
	B b;
	a.print(b);

	return 0;
}

如上图所示,这是一个错误的代码,在编译之前编译器并不会提醒有任何的语法错误,但是当你点击编译,就会遇到

上面那种错误,发生在void 函数那一行,所以正确的写法是

class B;
class A {
public:
	void print1(B* b);
    void print2(B& b);
};

class B {
public:
	void print() {
		cout << "this is B" << endl;
	}

};

int main() {
	A a;
	B b;
	a.print1(&b);
    a.print2(b);

	return 0;
}

这样进行编译是不会报错的,但依然不能够运行,很显然,A类中的print1和print2没有实现,会遇到LINK error。

前置声明/超前声明(forward declaration)————问题二

    上面代码虽然已经编译通过,但是还没有实现函数功能,在实现函数功能时,要注意函数实现功能的位置。

class B;
class A {
public:
	void print1(B* b) {
		cout << "class A, call function b->print()" << endl;
		b->print();
	}
	void print2(B& b) {
		cout << "class A, call function b.print()" << endl;
		b.print();
	}

};

class B {
public:
	void print() {
		cout << "this is B" << endl;
	}
};

int main() {
	A a;
	B b;
	a.print1(&b);
	a.print2(b);

	return 0;
}

如果这样写的话,你会重新遇到编译错误,就像这样

在163行和167行报出错误。

如果这么写依然会遇到编译错误:

class B;
class A {
public:
	void print1(B* b);
	void print2(B& b);

};

void A::print1(B* b) {
	cout << "class A, call function b->print()" << endl;
	b->print();
}

void A::print2(B& b) {
	cout << "class A, call function b.print()" << endl;
	b.print();
}

class B {
public:
	void print() {
		cout << "this is B" << endl;
	}
};

int main() {
	A a;
	B b;
	a.print1(&b);
	a.print2(b);

	return 0;
}

问题错误情况和上面一样,在print那一行。

正确的做法是

class B;
class A {
public:
	void print1(B* b);
	void print2(B& b);

};

class B {
public:
	void print() {
		cout << "this is B" << endl;
	}
};

void A::print1(B* b) {
	cout << "class A, call function b->print()" << endl;
	b->print();
}

void A::print2(B& b) {
	cout << "class A, call function b.print()" << endl;
	b.print();
}

int main() {
	A a;
	B b;
	a.print1(&b);
	a.print2(b);

	return 0;
}

可以正确运行。

前置声明/超前声明(forward declaration)————问题三

    即使遵循开始的那个原则,类定义和函数实现分离,但是依然会遇到问题,如下面所示。

testA.h文件

#pragma once
# include "testB.h"
//class B;
class A {
public:
	A(int a, int b) : data1(a), data2(b),
		data3(a), data4(b), data5(a), data6(b) {}

	~A() {}

	friend void B::visit1(A* a);

	int data1;
	int data2;

protected:
	int data3;
	int data4;

private:
	int data5;
	int data6;
};

testB.h

#pragma once
#include "testA.h"

class B {
public:
	void visit1(A* a);
};

testB.cpp

#include "testA.h"
#include "testB.h"
#include <iostream>

using namespace std;

void B::visit1(A* a) {
	cout << "outof class a, data access: " << endl \
		<< "data1: " << a->data1 << endl \
		<< "data2: " << a->data2 << endl;

	cout << "outof class a, data access: " << endl \
		<< "data1: " << a->data3 << endl \
		<< "data2: " << a->data4 << endl;

	cout << "outof class a, data access: " << endl \
		<< "data1: " << a->data5 << endl \
		<< "data2: " << a->data6 << endl;
}

main函数文件

# include "testA.h"
# include "testB.h"
# include <iostream>
using namespace std;

int main() {
	A a(1 ,2);
	B b;
	b.visit1(&a);

	return 0;
}

所以正确的做法是修改testB.h文件,修改成

#pragma once
// #include "testA.h"
class A;
class B {
public:
	void visit1(A* a);
};

然后main函数就可以成功运行了。上面的错误一般称之为循环include,所以避免循环inclde的办法就是采用前置声明。

 那么为什么不在testA.h中使用前置声明,在testA.h使用是会报错的,因为A中声明friend void B::visit1(A* a);已经不仅仅是使用B这个类,而且用到了B中的函数,所以,需要在A之前定义B类中的成员函数。

前置声明/超前声明(forward declaration)————问题四

最后,如果真的遇到了这种循环include和前置声明,最好的办法就是重构代码逻辑(不要把一切搞得这么复杂,好吗?宝贝)。

还有,这种前置声明还会带来逻辑问题,如下

// b.h:
struct B {};
struct D : B {};

// good_user.cc:
#include "b.h"
void f(B*);
void f(void*);
void test(D* x) { f(x); }  // calls f(B*)

上述示例来自知乎大佬回答(知乎回答——夏洋),若把#include换成前置声明,由于声明时不知道D是B的子类,test()中f(x)就会导致f(void*)被调用,而不是f(B*)。

  • 10
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值