首页 > 编程语言 >【C++】前置声明导致的代码含义改变

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

时间:2024-01-26 18:13:07浏览次数:22  
标签:头文件 struct 含义 前置 C++ include 声明 void

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

// 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对应头文件呢?

 

标签:头文件,struct,含义,前置,C++,include,声明,void
From: https://www.cnblogs.com/ciaoss/p/17990373

相关文章

  • 【C++】 select函数介绍
    在使用C++语言开发网络应用程序时,常常需要用到select函数。select函数是一种多路复用机制,可以同时监听多个文件描述符上可读、可写、异常等事件,从而让程序能够高效地处理多个连接。下面详细介绍C++中的select函数。1.select函数的定义和作用select函数的定义为:intselect(intn......
  • C++ RALL机制理解
    #########################RALL机制(将资源的生命周期与对象的生命周期所绑定(构造获取资源/析构释放资源,利用了栈上的变量在离开作用域的时候会析构的特性)RAII的做法是使用一个对象,在其构造时获取对应的资源,在对象生命期内控制对资源的访问,使之始终保持有效,最后在对象析构的时候,......
  • C++ 单例模式
    单例模式写法:注意:静态成员使用指针的话,程序退出时无法指针类的析构函数,在类内添加回收单例的类,析构的时候将该单例对象析构,就可以了例如:classSingleton{public: staticSingleton*getInstance() { if(m_pInstance==nullptr)//静态成员使用指......
  • C转C++速成浅入浅出系列——STL之queue
    本系列为应付考研复试用,知识浅入浅出,很多地方不深究细节原理;如有谬误,欢迎大家指出。queue【queue:队伍,队列】(学过数据结构的熟的不能再熟了吧)理解为队列。特点是①先入先出②只能对队伍的队首进行出队操作,对队伍的队尾进行入队操作。需提供头文件#include<queue>由于队列的......
  • C转C++速成浅入浅出系列——STL之stack
    本系列为应付考研复试用,知识浅入浅出,很多地方不深究细节原理;如有谬误,欢迎大家指出。stack【stack:栈】(学过数据结构的熟的不能再熟了吧)理解为栈。特点是①先入后出②只能操作栈顶元素需提供头文件#include<stack>由于栈的特性,只能对栈顶元素进行操作,故不可使用.begin()方法......
  • 在Linux Ubuntu系统中部署C++环境与Visual Studio Code软件
      本文介绍在LinuxUbuntu操作系统下,配置VisualStudioCode软件与C++代码开发环境的方法。  在文章VMware虚拟机部署LinuxUbuntu系统的方法中,我们介绍了LinuxUbuntu操作系统的下载、安装方法;本文则基于前述基础,继续介绍在LinuxUbuntu操作系统中配置VisualStudioCode软......
  • 【C++入门到精通】C++入门 —— list (STL)
    @TOC前言文章绑定了VS平台下std::list的源码,大家可以下载了解一下......
  • C++类的继承
    类的继承的概念及定义继承的概念类的继承即对类设计层次的复用,可以在保持原有类结构的基础上对类进行进一步拓展,完成类的继承后,原有的类称为基类/父类,继承的类称为派生类/子类如何定义一个继承类定义一个继承类的基本语法如下classPerson{};classStudent:publicPerson{};......
  • 01 | C++关键字const/volatile
    const与volatileconst从字面意思上是表示“常量”。最基础的用法就是定义程序用到的数字、字符串常量,代替宏定义。constintMAX_LEN=1024;conststd::stringNAME="metroid";不过从C++程序的生命周期角度来看的话,const定义的常量和宏定义还是有本质的区别:const定义......
  • 关于使用c++制作蓝牙连接,Windows版本
    1#define_CRT_SECURE_NO_WARNINGS2#pragmawarning(disable:4995)3#include<iostream>4#include<string>5#include<vector>6#include<sstream>7#include<iomanip>8#include<windows.h......