我刚参加了一次面试。我被问到什么是“前向声明(forward declaration)”后,被问及是否存在与前向声明相关的危险。
我对第二个问题无法回答。在网上搜索也没有找到任何有趣的结果。
那么,有人知道使用前向声明是否存在任何危险吗?
我刚参加了一次面试。我被问到什么是“前向声明(forward declaration)”后,被问及是否存在与前向声明相关的危险。
我对第二个问题无法回答。在网上搜索也没有找到任何有趣的结果。
那么,有人知道使用前向声明是否存在任何危险吗?
除了重复问题以外...
...标准中至少有一个痛点。
如果你在指向不完整类型的指针上调用delete
,则会产生未定义行为。实际上,析构函数可能不会被调用。
我们可以通过以下命令和示例在LiveWorkSpace上看到:
// -std=c++11 -Wall -W -pedantic -O2
#include <iostream>
struct ForwardDeclared;
void throw_away(ForwardDeclared* fd) {
delete fd;
}
struct ForwardDeclared {
~ForwardDeclared() {
std::cout << "Hello, World!\n";
}
};
int main() {
ForwardDeclared* fd = new ForwardDeclared();
throw_away(fd);
}
诊断:
Compilation finished with warnings:
source.cpp: In function 'void throw_away(ForwardDeclared*)':
source.cpp:6:11: warning: possible problem detected in invocation of delete operator: [enabled by default]
source.cpp:5:6: warning: 'fd' has incomplete type [enabled by default]
source.cpp:3:8: warning: forward declaration of 'struct ForwardDeclared' [enabled by default]
source.cpp:6:11: note: neither the destructor nor the class-specific operator delete will be called, even if they are declared when the class is defined
难道你不想感谢编译器提醒你吗 ;) ?
前置声明是C++缺少模块的症状(在C++17中将被修复?),通过头文件包含使用,如果C++有了模块,就根本不需要前置声明。
前置声明不亚于“合同”,通过使用它,您实际上承诺会提供某些东西的实现(在同一源文件中之后,或者稍后链接二进制文件)。
这样做的缺点是,您实际上必须遵循您的合同,这不是什么大问题,因为如果您不遵循您的合同,编译器将会提前发出警告,但在一些语言中,代码可以在不需要“承诺其存在”的情况下执行(说的是动态类型语言)。
请考虑
template<class X = int> class Y;
int main()
{
Y<> * y;
}
//actual definition of the template
class Z
{
};
template<class X = Z> //vers 1.1, changed the default from int to Z
class Y
{};
类Z
后来作为默认的模板参数进行了更改,但原始的前向声明仍然使用int
。
定义:
//3rd party code
namespace A
{
struct X {};
}
以及前向声明:
//my code
namespace A { struct X; }
//3rd party code
namespace B
{
struct X {};
}
namespace A
{
using ::B::X;
}
这显然使我的代码无效了,但错误并不在实际位置,所做的修复措施可以说是靠运气。
delete
,则可能会忽略operator delete
重载。仅在以下情况下,提前声明某些内容会存在风险:当您在头文件之外或非共享头文件中进行提前声明时,并且提前声明的签名与实际被提前声明的内容的签名不同。如果您在 extern "C"
中这样做,则没有名称重整来检查链接时间的签名,因此当签名不匹配时,可能会出现未定义行为。
It can be difficult to determine whether a forward declaration or a full #include is needed. Replacing an #include with a forward declaration can silently change the meaning of code:
// 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*)
If the #include was replaced with forward decls for B and D, test() would call f(void*).
class B
(应该在b.h和b.cpp中),但实际上在a.cpp中包含了声明不同的class B
的b2.h文件,则会导致未定义行为。前置声明本身并不危险,但它是一种代码异味。如果您需要前置声明,则意味着两个类紧密耦合,这通常是不好的。因此,这表明您的代码可能需要重构。
有些情况下,紧密耦合是可以接受的,例如状态模式实现中的具体状态可能会紧密耦合。我认为这是可以接受的。但在大多数其他情况下,在使用前置声明之前,我会改进我的设计。