在所有情况下都不应该将显式的前置声明视为一般性的准则。前向声明本质上是复制和粘贴或拼写错误的代码,如果在其中发现一个bug,需要到使用前向声明的每个位置都去修复这个问题,这可能会出错。为了避免“前向”声明和其定义之间的不匹配,在头文件中放置声明,并将该头文件包含在定义和使用声明的源文件中。
然而,在特定的情况下,只有一个不透明类被前置声明,这个前向声明就可以使用,但是通常来说,“尽可能使用前向声明而不是包含头文件”,就像本主题的标题所说,可能会带来相当大的风险。
以下是涉及前向声明的“隐形风险”的一些例子(隐形风险=编译器或链接器未检测到的声明不匹配的风险):
- 表示数据的符号的显式前向声明可能是不安全的,因为这样的前向声明可能需要正确了解数据类型的占用空间(大小)。
- 表示函数的符号的显式前向声明也可能是不安全的,例如参数类型和参数数量。
下面的示例说明了这一点,例如两个危险的数据以及一个函数的前向声明:
文件a.c:
#include <iostream>
char data[128][1024];
extern "C" void function(short truncated, const char* forgotten) {
std::cout << "truncated=" << std::hex << truncated
<< ", forgotten=\"" << forgotten << "\"\n";
}
文件 b.c:
#include <iostream>
extern char data[1280][1024];
extern "C" void function(int tooLarge);
int main() {
function(0x1234abcd);
std::cout << "accessing data[1270][1023]\n";
return (int) data[1270][1023];
}
使用g++ 4.7.1编译程序:
> g++ -Wall -pedantic -ansi a.c b.c
注意:存在看不见的危险,因为g++没有编译器或链接器错误/警告。
注意:省略
extern "C"
会导致
function()
的链接错误,因为c++名称混淆。
运行程序:
> ./a.out
truncated=abcd, forgotten="♀♥♂☺☻"
accessing data[1270][1023]
Segmentation fault
delete
一个指针,但是如果该类实际上具有非平凡析构函数,则会导致未定义行为。因此,如果“仅使用指针”就能够进行delete
操作,那么这是有原因的,否则就不那么重要。 - Steve Jessop