【C++】前置声明导致的代码含义改变

真的有这么离谱的事哈哈哈哈。

// F.h

struct F { };
struct S : F { };

// User.h

#include <iostream>
struct F;
struct S;

struct User {
  void f(F*) { std::cout << "F" << std::endl; }
  void f(void*) { std::cout << "void" << std::endl; }
  void test(S* x) { f(x); }
};

 // main.cpp

#include "F.h"
#include "User.h"

int main() {
  S* x{};
  User u;
  u.test(x);
  return 0;
}

输出:"F"

改一下main.cpp中 #include 两个头文件的顺序:

// main.cpp

#include "User.h"
#include "F.h"

int main() {
  S* x{};
  User u;
  u.test(x);
  return 0;
}

输出:"void"

当空指针处理了直接。

Google代码规范中如此描述:

尽可能地避免使用前置声明。使用 #include 包含需要的头文件即可。
定义:
所谓「前置声明」(forward declara?on)是类、函数和模板的纯粹声明,没伴随着其定义.
优点:
·前置声明能够节省编译时间,多余的 #include 会迫使编译器展开更多的文件,处理更多的输入。
·前置声明能够节省不必要的重新编译的时间。 #include 使代码因为头文件中无关的改动而被重新编译多次。
缺点:
·前置声明隐藏了依赖关系,头文件改动时,用户的代码会跳过必要的重新编译过程。
·前置声明可能会被库的后续更改所破坏。前置声明函数或模板有时会妨碍头文件开发者变动其 API. 例如扩大形参类型,加个自带默认参数的模板形参等等。
·前置声明来自命名空间 std:: 的 symbol 时,其行为未定义。
·很难判断什么时候该用前置声明,什么时候该用 #include 。极端情况下,用前置声明代替 includes 甚至都会暗暗地改变代码的含义:
// 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 被 B 和 D 的前置声明替代, test() 就会调用 f(void*) .
·前置声明了不少来自头文件的 symbol 时,就会比单单一行的 include 冗长。
·仅仅为了能前置声明而重构代码(比如用指针成员代替对象成员)会使代码变得更慢更复杂.
结论:
尽量避免前置声明那些定义在其他项目中的实体.
函数:总是使用 #include .
类模板:优先使用 #include .

 

OK吧,所以还是尽量用include包含头文件吧。

 

————————2024-1-26————————

前置声明是为了降低编译依赖,防止修改一个头文件引发多米诺效应;

所以到底什么时候需要用前置声明,什么时候应该include对应头文件呢?

 

posted on 2024-01-26 17:58  Ciaoss  阅读(23)  评论(0)    收藏  举报

导航