反對反對使用了ifndef就不需要pragma once的說法。

現代編譯器(甚至包括較新的 MSVC)能夠識別作為 include guard 的 ifndef ,在發現 ifndef 覆蓋了文件中的所有 token 時避免重複讀取文件。因此在現代編譯器上 ifndef 本身的編譯速度就足夠快,不需要 pragma once。


我自己不喜歡#pragma once,因為它不是 100% 可靠,防止的是重複引入相同文件,而不是防止重複引入相同內容。

自己寫一般不會傻到搞出這種問題,但如果依賴了其它庫,控制權就不在你手裡了。很可能你所依賴的庫會導致你的工程里存在兩份另一個庫的頭文件。

比如你可能會同時依賴 fmtlib 和 spdlog,而 spdlog 又自帶一份 fmtlib。你的某處代碼可能在直接 include 了 fmtlib 的 format.h 的同時,又因為使用 spdlog 而間接 include 了 spdlog 里的那份 format.h。如果 format.h 里用的是 pragma once,就直接重定義了……


vc6 你添加的class頭文件就會自動加上ifndef和pragma once,如 @SuperSodaSea 說得一樣,的確是為了兼容性。後來的ide就直接拋棄ifndef了,畢竟分析它也是要耗時間的。gcc也有對ifndef的優化。實際上,編譯器對頭文件的處理是有優化的,並不是你include了就會實實在在的走系統調用open一次,所以從性能上來說,應該沒啥差別。pragma once還有一個好處,就是不用擔心ifndef重名的問題


反對使用了ifndef就不需要pragma once的說法。現在主流的編譯器都有對progma once的支持,並可由此提高編譯速度(使用pragma once的編譯速度比ifndef的寫法快),加上ifndef是為了確保兼容性,可以在支持和不支持pragma once的編譯器。因此兩種都使用可以在提高編譯速度的前提下保證兼容性。

可以不同時使用的。

我一般就是使用其中一個。不會同時使用兩個。

以前#pragma once很多編譯器不支持,但是即便不支持,其本身並不會引起錯誤,所以對於跨平台代碼就兩個一起用咯,最安全。

現在已經沒有太大必要了。


同時使用二者是比較好的實踐。#ifndef是完全符合標準的,所有編譯器都能正確理解它並完成你的意圖。但是只用#ifndef的話,缺點不單單是需要多寫兩行代碼和想出一個合適的#define名字,更大的缺點是它要求編譯器一直掃描到文件尾,找到對應的#endif,才能確定你的意圖是整個頭文件都要略過。然而這時候已經花費了許多CPU時間(和電能),前面做的那麼多都是白忙。

#pragma once不是標準,通用性不那麼強,但是即使不支持的編譯器也僅僅忽略它,不會導致錯誤,這是標準的規定,遇到不認識的#pragma必須忽略。它的優點是只要出現並被編譯器識別出來,編譯器就直接放棄對整個頭文件的分析,當這個頭文件是空文件,能提高編譯效率。所以,應該把#pragma once視為非標但經常有用(偶爾無效)且不會壞事的加速器,把它跟標準的方法一起用,這樣能達到最佳效果。

最後說一句,#pragma once應該放在頭文件的第一行,不要包在#ifndef/#endif裡面。為啥?這是一個留給初學者的簡單的思考題。

//---------- 補充一點

有人說應該用_Pragma代替#pragma。這是對C99和C++11標準的誤解。_Pragma只是比#pragma更靈活,針對once,它們同樣是非標的。


#pragma once是微軟的最早開始用的,並不是所有編譯器都支持,不過現在的主流編譯器一般都支持支持的編譯器可以參考如下鏈接pragma once它的好處是,它一般寫在第一行,支持的編譯器預處理期間可以很快掃描並識別,效率上略高一點。#ifndef格式的,基本上通用於所有編譯器,兼容性最好。但是,如果多次包含同一個文件的預處理效率略低因此,就出現了同時使用兩種的情況,兼顧效率和兼容性。

因為pragma once不標準,怕有的編譯器不支持,所以還用了ifndef。

要我說,用pragma once就夠了,不用怕。

現代編譯器沒有不支持的。

ifndef寫起來太麻煩,不要忘了文件尾還有一個undef。而且我習慣改名,不管是代碼裡面的名字,還是文件名,經常改。ifndef要與文件名匹配,不然看起來太不舒服了,這樣就很麻煩。還是pragma once方便。


#pragma once是屬於廣泛應用卻沒有被納入標準的東西, 個人認為是極其好用的, 因為用#ifndef需要考慮命名問題, 如果是多個文件層次的編譯結構, 容易撞車. (num/img和resource/img).

所以如果你在PC上開發, 妥妥用#pragma once吧. 然後實在是不兼容, 再用腳本統一處理頭文件添加#ifndef.

幾年前還小心翼翼的兩個都用,現在只用#pragma once。背景:c++主流跨平台開發,pc,mobile和一些嵌入式系統。至今沒遇到不支持的編譯器,如果真遇到不支持的,寧願生成代碼或想辦法換編譯器,如一些arm平台可以自己折騰編譯器。


以下是以前寫的一點材料,這裡貼一下,希望有所幫助。


我寫的issue原文:

標題:Consider changing include guards to pragma once.

We can use the _Pragma directive for header file guard.

example:

#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_
...
#endif // FOO_BAR_BAZ_H_

change to

_Pragma("once")

There are several factors to support the lowcase file/directory name.

portability Cross-platform problems will not occur, compiler implement standard, they will support it. readability Generally, if you just want to do some guard, you should not care about the name and the directory of this file. and the naming of the guard is yet another big question, nonstandard things always end up in a fight. convenience just copy paste for the _Pragma("once"), here and there.

Computer industry set the standards, so does our company.

feel free to refer to tencent cplusplus code standard, 「2.2.【必須】 頭文件保護」: https://git.code.oa.com/standards/cpp#22%E5%BF%85%E9%A1%BB-%E5%A4%B4%E6%96%87%E4%BB%B6%E4%BF%9D%E6%8A%A4

More importantly, the _Pragma directive is the standard level in C++, instead of the a specific compiler extension like GCCs # pragma, which maximizes stability and compatibility.

see:


我寫的wiki原文(工程標準 -&> C++ Coding Guideline):

標題:The _Pragma("once") Guard

_Pragma操作符

在C/C++標準中,#pragma是一條預處理的指令(preprocessor directive)。簡單地說,#pragma是用來向編譯器傳達語言標準以外的一些信息。舉個簡單的例子,如果我們在代碼的頭文件中定義了以下語句:

#pragma once

要達到與上例#pragma類似的效果,則只需要如下代碼即可。

_Pragma("once");

而相比預處理指令#pragma,由於_Pragma是一個操作符,因此可以嵌套在宏中。我們可以看看下面這個例子:

#define CONCAT(x) PRAGMA(concat on #x)
#define PRAGMA(x) _Pragma(#x)
CONCAT( ..concat.dir )

這裡,CONCAT( ..concat.dir )最終會產生_Pragma(concat on "..concat.dir")這樣的效果。而#pragma則不能在宏中展開,因此從靈活性上來講,標準的 _Pragma具有更大的靈活性

注意,為什麼強調「標準」,我指的是

_Pragma("once")

而不是

#pragma once

以上,可以看出,不管從可移植性、可讀性、便利性,還是規範角度看,_Pragma("once")都有優勢。使用新標準可以提高開發效率,用最自然的思維方式、最簡單的代碼,表達大量內容。

因為前者是語言層面的標準,而後者是編譯器的擴展,可能在標準通過_Pragma操作符之前,GCC就已經實現了#pragma,這個時候,windows,macOS系統的編譯器可能是不認識#pragma的,他可能是別的foo, bar。但是,標準通過之後,所有編譯器都將遵循標準。

所以,使用標準,會是最通用的方式,也是我們的方式。

詳見n3690.pdf:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3690.pdf


不過有觀點認為,我們不該使用_Pragma("once"),因為編譯器的實現有bug。(https://stackoverflow.com/questions/1143936/pragma-once-vs-include-guards)

我們確立一個標準,提高開發效率的同時,也要確認該標準是否穩定可用,為了防止引入未知的問題。

網上的資料不多,就直接看了下內核、編譯器的實現。

早期存在的問題在於,比如foo.h -&> /path/to/foo.h鏈接會共享相同的inode,導致編譯器無法區分。(詳見附錄「內核實現分析」)

在問題相關的上下游,有如下若干事實: 編譯器層面,gcc在3.4.0版本的時候已經修復了上述問題。(詳見附錄「編譯器實現分析」) 開發者層面,絕大多數項目都不會存在上述問題。

這讓我們相信標準,且相信標準的實現。

極端假設,即使編譯器仍存在這個所謂的bug,並且我們刻意觸發了,那麼會不會有嚴重問題呢?答案是否定的。因為,它和ifndef造成的macro衝突問題是同一級別的,而且更容易在最早期的編譯階段被發現。換言之,既然你都可以接受ifndef,那麼為什麼不能接受_Pragma("once")呢,後者即便是在最邪惡的情況下,也不會比前者錯得更多。

結論:我們可以使用標準的_Pragma操作符

大家已經將這個結論,作為我們的共識。在代碼規範中已更新。

具體的編碼,可以直接遵循規範:

如果存在ifndef頭文件保護,那麼麻煩進行如下替換:

#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_
...
#endif // FOO_BAR_BAZ_H_

改成

_Pragma("once")

如果不存在,請直接使用

_Pragma("once")


內核實現分析(linux-5.7.0):

編譯器實現分析(gcc-9.2.0):


可以關注公眾號「開發者技術精選」,獲取更多優質內容。

這裡精選計算機科學技術,幫助程序員成長。 發文內容有技術:數據結構、演算法、編程語言、操作系統、資料庫、存儲、計算機體系結構等。 有思想:思考成果、哲學、邏輯、讀書等; 頻率較低,當前每月三篇,寧缺毋濫、以質量先行。


標準中沒有規定,剩下的就是個人喜好,畢竟代碼最終都由個體寫出來的。代碼可能在多種編譯器下被編譯。


pragma once 是按編譯器偏好實現的,兼容性不一定有保證。比如 gcc 至少在 unix 下就很粗暴地只比較了文件的 md5 和 last modified time


once是微軟最初用的,後被gcc兼容,並未納入標準,早期gcc會彈warning但依然兼容。

ifdef寫法更符合規範,_Pragma由c99引入,當然單獨討論once的話,我建議ifdef寫法。


cpp真是個垃圾,猴年馬月還得考慮這些問題


每個人都需要安全感
推薦閱讀:
相关文章