在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*)。