java定义异常的头文件_c++ 声明定义都在头文件中怎么include?

本文详细解析了C++中的One Definition Rule(ODR)问题,通过实例分析了为何在头文件中定义函数会导致ODR违规,并提出了使用`inline`关键字和优化头文件管理的解决方案。此外,还介绍了声明与定义的区别以及include guard的作用。
摘要由CSDN通过智能技术生成

瀉藥, @李毅 老大已經點名你出錯的地方了, @felix 老大也指出是ODR的問題, 看來窩除了能在上面說下原理沒什麼做了, 哈哈. 不過既然兩位老大都沒有將原理和你的庫結合, 那麼這個微小工作就由窩踩在兩位老大的肩膀上來完成吧.

科普基礎知識

窩給你結合標準文檔從頭梳理一些c++裏面必須知道的入門常識性概念, 這些概念可能你會覺得過有點多餘, 但是這些是每一個寫c++的必須知道的基礎. 並且窩在這裏會剔除些標準裏的wording, 只會提及此處需要用到的概念.

以下引用皆出自N4741, 歌詞大意可以理解爲Informally的簡略版解釋(可能有錯誤的私貨, 如有老大看出, 望告知)

Include guard

這在標準裏面沒有, 只是c++程序員爲了實現ODR的一個慣用法罷了, 通過conditional inclusion在頭文件裏面定義大部分時間都可以用#pragma once來代替, 不過窩有一次被一個大佬警告過其可移植性, 但是窩查了下發現msvc, clang, gcc, icc, xl都zici呀...可能是有標準潔癖吧.

Translation unit

The text of the program is kept in units called source files in this document. A source file together with all the headers (20.5.1.2) and source files included (19.2) via the preprocessing directive#include, less any source lines skipped by any of the conditional inclusion (19.1) preprocessing directives, is called a translation unit.

歌詞大意: 每一個源文件(.cpp/.cc/.cxx等)在使用了include guard後展開頭文件(.hh/.hpp/.h等)(即複製頭文件所有內容進源文件)

Declarations and definitions

Each entity declared by a declaration is also defined by that declaration, unless..........

歌詞大意, $$\mathsf{definitions} \subset \mathsf{declarations}$$, 這就是這兩者的關係, 不過窩在某個dlang的群裏發現很多寫了c++多年的選手依然不知道這一點, 這是很致命的, 比如不熟悉其區別可能就會產生窩這樣的困惑:https://stackoverflow.com/que... 還可能有其它危害, 下文也會提及.

One Definition Rule(ODR)

在n4741中, ODR分爲了12大點, 以後還可能擴充或修改, 前些日子在so上看到一個語言律師在odr-use上發現了自相矛盾的地方. 不過這裏只需要說下兩點就行了.

1) No translation unit shall contain more than one definition of any variable, function, class type, enumeration type, or template.

ODR-use

本來不想說odr-used的, 但是發現還是脫不開它.

A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless applying the lvalue-to-rvalue conversion (7.1) to x yields a constant expression (8.6) that does not invoke any non-trivial functions and, if x is an object,ex is an element of the set of potential results of an expressione, where either the lvalue-to-rvalue conversion (7.1) is applied toe, or e is a discarded-value expression (8.2).

歌詞大意: 先要瞭解potentially-evaluated expression是什麼, 花個1分鐘看下這個帖子: https://stackoverflow.com/que... 好, 當ex滿足potentially-evaluated expression的性質時, 除非做了左值->右值的轉換(如x作爲返回值, 但是是按值返回的), 亦或是x本身不是object, 比如x可以是引用(另一種情況這不解釋了, 不然牽扯的概念就太多了). 好, 說這些可能會有點混, 其實, 你需要知道的是odr-use的意思就是如字面所說--需要definition的存在, 而不僅僅是declaration(現在你應該明白爲什麼窩在一開始就要區分definition和declaration及其子集關係了吧).

繼續回到One definition rule的定義

10) Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program outside of a discarded statement (9.4.1); no diagnostic required. The definition can appear explicitly in the program, it can be found in the standard or a user-defined library, or (when appropriate) it is implicitly defined (see 15.1, 15.4 and 15.8). An inline function or variable shall be defined in every translation unit in which it is odr-used outside of a discarded statement.

歌詞大意: 對於函數或者變量, 在整個程序中也要遵循odr原則, 但是很明顯頭文件會被很多源文件包含, 該怎麼辦呢? inline用來開洞解決這個問題, 加了inline, 對函數的odr檢查就被無視了(當然, 很多時候會自動inline, 比如類內定義, friend等).

根據原理剖析錯因.

窩不知道你自己的程式結構, 那麼我就沿用@李毅 老大給出的文件組織了:

├── a.cpp

├── a.h

├── aip-cpp-sdk-0.4.0

│ ├── base

│ │ ├── base64.h

│ │ ├── base.h

│ │ ├── http.h

│ │ └── utils.h

│ ├── face.h

│ ├── image_censor.h

│ ├── image_classify.h

│ ├── image_search.h

│ ├── kg.h

│ ├── nlp.h

│ ├── ocr.h

│ ├── README.md

│ └── speech.h

├── main.cpp

└── Makefile

很明顯, translation parsion之後我們由a.cpp和main.cpp兩個翻譯單元存在, 好, 我們通過展開這兩個翻譯單元來分析爲什麼你自己寫的(即版本二)會違背ODR, 而@李毅 老大給出的第一個版本就不會. 爲了進一步簡化問題, 我們把#include 不予考慮, 規定base/base.h的內容除去include guard僅有#include "base64.h一行, base/base64.h除去include guard有

include "iostream"

namespace aip { void f() { std::cout << "hello"; }

speech.h除去include guard有

#include "base/base.h"

namespace aip {

void f_s() {

aip::f_i();

}

}

main.c有:

#include "a.h"

int main()

{

f_a();

}

版本一

a.cpp(稍作簡化)

#include "aip-cpp-sdk-0.4.0/speech.h"

void f_a()

{

aip::f_s();

}

$$=>$$

#include "aip-cpp-sdk-0.4.0/base/base.h"

namespace aip {

void f_s() {

aip::f_i();

}

}

void f_a()

{

aip::f_s();

}

$$=>$$

#include "aip-cpp-sdk-0.4.0/base/base64.h"

namespace aip {

void f_s() {

aip::f_i();

}

}

void f_a()

{

aip::f_s();

}

$$=>$$

include

namespace aip { void f() { std::cout << "hello"; }

namespace aip {

void f_s() {

aip::f_i();

}

}

void f_a()

{

aip::f_s();

}

main.cpp

#include "a.h"

int main()

{

f_a();

}

$$=>$$

bool f_a();

int main()

{

f_a();

}

小結

可見這個版本展開到最後沒有違背ODR的部分.

版本二

a.cpp

#include "a.h"

void f_a()

{

aip::f_s();

}

$$=>$$

#include "aip-cpp-sdk-0.4.0/base/base64.h"

void f_a();

void f_a()

{

aip::f_s();

}

接下來的步驟幾乎於版本一相同了, 直接貼最後結果:

void f_a();

include

namespace aip { void f() { std::cout << "hello"; }

namespace aip {

void f_s() {

aip::f_i();

}

}

void f_a()

{

aip::f_s();

}

main.cpp

#include "a.h"

int main()

{

f_a();

}

只要展開a.h, 上面也有此步驟, 所以直接給結果:

void f_a();

include

namespace aip { void f() { std::cout << "hello"; }

namespace aip {

void f_s() {

aip::f_i();

}

}

int main()

{

f_a();

}

小結

好, 其實我們很容易發現, 版本二的兩個翻譯單元都會包含aip::f_i()和aip::f_i的definition, 再結合原理部分的最後一段, 這明顯違反了ODR`, 所以是錯誤的

解決方案

兩位老大已經給出了方案了, 要麼你自己的頭文件和源文件小心處理, 理清依賴關係, 要麼你就給庫函數加上inline, 屏蔽ODR檢查.

誰背鍋?

c++. 沒有module, 只能依靠這種落伍的include guard和inline來解決這種包含和符號檢查, 很容易會混亂心智

c. 歷史殘留

庫的作者, 窩懷疑他們自己都沒有用過自己寫的這個庫.

@藤壶女御_ 老大誤以爲窩是臺灣人, 真是夭壽啦:P 畢竟窩吃不起茶葉蛋, 用着799的碎屏紅米(碎1年了), 穿着迪卡農的鞋子, 筆記本的價格也只有1700RMB(3年了已經), 只是最近喜歡上繁體字了, 所以窩用byvoid老大的Open CC提供簡體版:

泻药, @李毅 老大已经点名你出错的地方了, @felix 老大也指出是ODR的问题, 看来窝除了能在上面说下原理没什么做了, 哈哈. 不过既然两位老大都没有将原理和你的库结合, 那么这个微小工作就由窝踩在两位老大的肩膀上来完成吧.

科普基础知识

窝给你结合标准文档从头梳理一些c++里面必须知道的入门常识性概念, 这些概念可能你会觉得过有点多余, 但是这些是每一个写c++的必须知道的基础. 并且窝在这里会剔除些标准里的wording, 只会提及此处需要用到的概念.

以下引用皆出自N4741, 歌词大意可以理解为Informally的简略版解释(可能有错误的私货, 如有老大看出, 望告知)

Include guard

这在标准里面没有, 只是c++程序员为了实现ODR的一个惯用法罢了, 通过conditional inclusion在头文件里面定义大部分时间都可以用#pragma once来代替, 不过窝有一次被一个大佬警告过其可移植性, 但是窝查了下发现msvc, clang, gcc, icc, xl都zici呀...可能是有标准洁癖吧.

Translation unit

The text of the program is kept in units called source files in this document. A source file together with all the headers (20.5.1.2) and source files included (19.2) via the preprocessing directive#include, less any source lines skipped by any of the conditional inclusion (19.1) preprocessing directives, is called a translation unit.

歌词大意: 每一个源文件(.cpp/.cc/.cxx等)在使用了include guard后展开头文件(.hh/.hpp/.h等)(即复制头文件所有内容进源文件)

Declarations and definitions

Each entity declared by a declaration is also defined by that declaration, unless..........

歌词大意, $$\mathsf{definitions} \subset \mathsf{declarations}$$, 这就是这两者的关系, 不过窝在某个dlang的群里发现很多写了c++多年的选手依然不知道这一点, 这是很致命的, 比如不熟悉其区别可能就会产生窝这样的困惑:https://stackoverflow.com/que... 还可能有其它危害, 下文也会提及.

One Definition Rule(ODR)

在n4741中, ODR分为了12大点, 以后还可能扩充或修改, 前些日子在so上看到一个语言律师在odr-use上发现了自相矛盾的地方. 不过这里只需要说下两点就行了.

1) No translation unit shall contain more than one definition of any variable, function, class type, enumeration type, or template.

ODR-use

本来不想说odr-used的, 但是发现还是脱不开它.

A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless applying the lvalue-to-rvalue conversion (7.1) to x yields a constant expression (8.6) that does not invoke any non-trivial functions and, if x is an object,ex is an element of the set of potential results of an expressione, where either the lvalue-to-rvalue conversion (7.1) is applied toe, or e is a discarded-value expression (8.2).

歌词大意: 先要了解potentially-evaluated expression是什么, 花个1分钟看下这个帖子: https://stackoverflow.com/que... 好, 当ex满足potentially-evaluated expression的性质时, 除非做了左值->右值的转换(如x作为返回值, 但是是按值返回的), 亦或是x本身不是object, 比如x可以是引用(另一种情况这不解释了, 不然牵扯的概念就太多了). 好, 说这些可能会有点混, 其实, 你需要知道的是odr-use的意思就是如字面所说--需要definition的存在, 而不仅仅是declaration(现在你应该明白为什么窝在一开始就要区分definition和declaration及其子集关系了吧).

继续回到One definition rule的定义

10) Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program outside of a discarded statement (9.4.1); no diagnostic required. The definition can appear explicitly in the program, it can be found in the standard or a user-defined library, or (when appropriate) it is implicitly defined (see 15.1, 15.4 and 15.8). An inline function or variable shall be defined in every translation unit in which it is odr-used outside of a discarded statement.

歌词大意: 对于函数或者变量, 在整个程序中也要遵循odr原则, 但是很明显头文件会被很多源文件包含, 该怎么办呢? inline用来开洞解决这个问题, 加了inline, 对函数的odr检查就被无视了(当然, 很多时候会自动inline, 比如类内定义, friend等).

根据原理剖析错因.

窝不知道你自己的程式结构, 那么我就沿用@李毅 老大给出的文件组织了:

├── a.cpp

├── a.h

├── aip-cpp-sdk-0.4.0

│ ├── base

│ │ ├── base64.h

│ │ ├── base.h

│ │ ├── http.h

│ │ └── utils.h

│ ├── face.h

│ ├── image_censor.h

│ ├── image_classify.h

│ ├── image_search.h

│ ├── kg.h

│ ├── nlp.h

│ ├── ocr.h

│ ├── README.md

│ └── speech.h

├── main.cpp

└── Makefile

很明显, translation parsion之后我们由a.cpp和main.cpp两个翻译单元存在, 好, 我们通过展开这两个翻译单元来分析为什么你自己写的(即版本二)会违背ODR, 而@李毅 老大给出的第一个版本就不会. 为了进一步简化问题, 我们把#include 不予考虑, 规定base/base.h的内容除去include guard仅有#include "base64.h一行, base/base64.h除去include guard有

include "iostream"

namespace aip { void f() { std::cout << "hello"; }

speech.h除去include guard有

#include "base/base.h"

namespace aip {

void f_s() {

aip::f_i();

}

}

main.c有:

#include "a.h"

int main()

{

f_a();

}

版本一

a.cpp(稍作简化)

#include "aip-cpp-sdk-0.4.0/speech.h"

void f_a()

{

aip::f_s();

}

$$=>$$

#include "aip-cpp-sdk-0.4.0/base/base.h"

namespace aip {

void f_s() {

aip::f_i();

}

}

void f_a()

{

aip::f_s();

}

$$=>$$

#include "aip-cpp-sdk-0.4.0/base/base64.h"

namespace aip {

void f_s() {

aip::f_i();

}

}

void f_a()

{

aip::f_s();

}

$$=>$$

include

namespace aip { void f() { std::cout << "hello"; }

namespace aip {

void f_s() {

aip::f_i();

}

}

void f_a()

{

aip::f_s();

}

main.cpp

#include "a.h"

int main()

{

f_a();

}

$$=>$$

bool f_a();

int main()

{

f_a();

}

小结

可见这个版本展开到最后没有违背ODR的部分.

版本二

a.cpp

#include "a.h"

void f_a()

{

aip::f_s();

}

$$=>$$

#include "aip-cpp-sdk-0.4.0/base/base64.h"

void f_a();

void f_a()

{

aip::f_s();

}

接下来的步骤几乎于版本一相同了, 直接贴最后结果:

void f_a();

include

namespace aip { void f() { std::cout << "hello"; }

namespace aip {

void f_s() {

aip::f_i();

}

}

void f_a()

{

aip::f_s();

}

main.cpp

#include "a.h"

int main()

{

f_a();

}

只要展开a.h, 上面也有此步骤, 所以直接给结果:

void f_a();

include

namespace aip { void f() { std::cout << "hello"; }

namespace aip {

void f_s() {

aip::f_i();

}

}

int main()

{

f_a();

}

小结

好, 其实我们很容易发现, 版本二的两个翻译单元都会包含aip::f_i()和aip::f_i的definition, 再结合原理部分的最后一段, 这明显违反了ODR`, 所以是错误的

解决方案

两位老大已经给出了方案了, 要么你自己的头文件和源文件小心处理, 理清依赖关系, 要么你就给库函数加上inline, 屏蔽ODR检查.

谁背锅?

c++. 没有module, 只能依靠这种落伍的include guard和inline来解决这种包含和符号检查, 很容易会混乱心智

c. 历史残留

库的作者, 窝怀疑他们自己都没有用过自己写的这个库.

@藤壶女御_ 老大误以为窝是台湾人, 真是夭寿啦:P 毕竟窝吃不起茶叶蛋, 用着799的碎屏红米(碎1年了), 穿着迪卡农的鞋子, 笔记本的价格也只有1700RMB(3年了已经), 只是最近喜欢上繁体字了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值