Re: [問題] inline和.h寫定義
※ 引述《QQ29 (我愛阿蓉)》之銘言:
: 請教一個問題...
: 之前版上看到
: "寫在.h 定義一定是inline"...
: 但是我爬文後也找不到相關能佐證的文章...
: 請問這正確嗎??
: 如果自己寫inline這修飾字 也只是"建議" inline
: 如果定義寫在.h "一定"是inline嗎?
你需要的是類似 UNIX 下一個叫 nm 的工具。
它可以用來查詢 object file 裡有哪些 symbol 以及 symbol 的屬性,
其中 -C 參數還可以做 demangling。
很遺憾的是,
我不知道 MS 平台的這個工具叫什麼名字。
inline 除了建議 compiler 做 inline 外,
它也告訴 compiler 在 inline 失敗時要做對應的防範措施。
GNU 的 C++ compiler 常見的做法就是使用 weak symbol。
用高階語言的角度來說,
它就是允許同一個 identifier 能被重複定義多次的功能。
原本它的用途是讓 library 的實作能被 override;
但是因為 inline 通常由 programmer 保證實作碼一致,
所以每份實作互相 override 對方其實也沒差。
你的 linker 在最後隨便挑任一份實作來用都沒問題。
以這個程式碼來說:
inline void foo()
{
}
int main()
{
foo();
return 0;
}
用 g++ -c 編譯成 object file,
再拿 nm 去看可以看到這兩個 symbols:
0000000000000000 W foo()
0000000000000000 T main
W 就是說這是一個 weak symbol。
T 是說它位於 text section。
大寫字母代表這是 external symbol,
小寫字母代表這是 local symbol (static function 會變成這東西)。
雖然 compiler 也可以用 local symbol 實現 (從大 W 變成小 t),
但是這樣你最後的執行檔就註定要很肥。
好奇其它字母的意義就自己查手冊吧,
總之有這工具你就可以做很多實驗去看 compiler 的行為。
當然你直接讓 compiler 輸出組語檔來看也行。
這裡不是在教你萬事都要靠實驗,
只是這原本就是語言保留給 compiler 去煩惱的東西。
語言只說 compiler 要自行保證不會出事,
實際上 compiler 怎麼做到就要觀察了
回到正題。
因為你在 header 裡標了 inline,
所以 g++ 才會把那個 function 標成 weak symbol。
所以就算 inline 失敗,
你還是不會在 linking time 收到 multiple defintions 的 error。
不過 weak symbol 這功能也常讓知道它存在的人便宜行事。
譬如被看不懂 linker 錯誤訊息的人拿來解那個 error,
因為 GCC 允許你手動為函式或變數加上 weak 屬性,如:
void foo() __attribute__((weak));
結果就是程式出現非預期行為,
或是他程式交接給別人維護以後常在停車場發現自己的車胎被放氣。
weak symbol 是 ELF 格式的東西,
所以我不清楚 Windows 的 PE 格式有沒有等價物。
你好奇的話可能要自己去摸才知道。
另外補充一點,
function template 在 header files 不需要標 inline。
C++ compiler 必須想辦法讓它不會造成 multiple definitions,
而 GCC 的做法也是使用 weak symbol。
: 假如我class一坨private data.....
: 大家都會針對每個private data 寫一份get and set function
: 所以說有x個private data 就該有 2*x個function?
: 感覺這樣好累 但是不是好的設計一定要這樣寫??
你需要自己寫一個程式產生器。
也許事後你可能會發現,
你的 client code 其實並不需要直接存取這麼多東西。
比方說到處都有這種重複的 code:
value1 = obj->get1() * x;
value2 = obj->get2() / y;
...
result = value1 * value2 + ...;
obj->set15(result);
這個時候你就可以把這些 code 搬回 obj 所屬的 class 內,
client code 可以變成這樣一行:
obj->calculateSomething(x, y);
最後也許你發現這個 class 真正要提供的是 calculateSomething(),
然後那些奇奇怪怪從頭到尾都用不到的 getters/setters 就可以刪了。
如果你一開始是從物件導向分析和設計走過來的,
需要這類重構的機率就會大幅減小。
不過現在大家都不吃預先設計這一套,
所以事後回頭重新整理自己的 code 是必要的。
: 至於這種小function 都寫inline修飾 或直接寫在.h的話
: 不就違反
: .h不要寫定義的精神嗎???
又不是在 header 寫定義就會被抓去槍斃。
之所以會有這種說法,
是因為要讓人看到你的 class 一眼就明白提供什麼。
你放了一堆以數十行為單位的實作碼在裡面,
這樣別人是要怎麼看?
但是 int getValue() { return value_; } 這種一行的,
其實也沒差了。
還有放在 class 的大括號內自動就是 inline,
你不用另外寫 inline。
: 如果"定義寫在.h一定inline" 成立
: 我把定義寫在.h + lib 的話
: 給對方header file他不就看的出來我一些定義 雖說這些function都是簡單get set...
: 我該如何在.h不要寫定義 和 inline做取捨呢?
More Effective C++ 這本舊到爛掉的書,
有講過一個叫 80/20 法則的東西。
雖然這是商科那邊的名詞,
不過跟資工這邊學到的 Amdahl's law 是同一本質。
總而言之很多書都會告訴你,
inline 這種最佳化不要太早做。
等效能分析器告訴你效能瓶頸出在那邊,
你再考慮把它改成 inline。
這就是取捨的方法。
當然如果只是 getters/setters,
你直接丟 header file 也沒差。
只是當你打算改變它們的行為時,
要重編譯的檔案可能會很多。
常見的一個狀況是,
你可能會在開發期間陸續插入一些 assert() 做檢查。
之所以強調封裝要確實,
也是方便你加入一些檢查,
以及新舊版本實作之間的銜接或相容等等。
inline 基本上是一個最佳化工具,
而不是方便你寫程式的工具。
如果你不是因為效能考量而是方不方便寫,
那麼也就沒有什麼取捨的問題。
你覺得高興就好了。
我的話是會產生分離的宣告和定義,
所以在 class 定義式內看不到 getters/setters 的實作。
因為要關閉 assert() 以 gcc 來說是在編譯選項加 -DNDEBUG,
而這種 Makefile 層的修改要讓它被偵測到會比較麻煩。
這讓我必須重新編譯整個專案,
才能確保 getters/setters 的行為一致。
雖說直接在 header 上 #define NDEBUG 也行,
但就這就要保佑下次 commit source code 之前記得刪掉。
不然可能會有人跑來罵。
: ps. 看教學都是寫 宣告 和 定義 都要寫上inline在前面 如果一邊不寫會怎麼取捨呢?
: http://www.greenend.org.uk/rjk/2003/03/inline.html
: 這網頁有提到static inline 但光看他描述實在不懂 且為啥他沒寫inline這字眼呢?
這網頁主要是在講 C 語言的 inline。
那是一段黑歷史。
你現階段是以 MS Windows 的 C++ compiler 為主,
所以不用知道也沒差。
用純 C compiler 才有必要知道 inline 和 static inline 在 C99 前後的差別。
這個故事夭壽的長,
所以不想講。
反正你只會用到 inline 就對了,
static inline 請無視。
: 我在想 之前在學校都有講概念說 class封裝 可以達到資料 隱藏阿之類的
: 我現在突然無法理解他的涵義
: 我給他.h 他把我private:這字眼全刪掉 改成public: 那不就可以任憑他亂搞嘛?
: 我那些get set形同虛設
我記得是 Inside the C++ Object Model 還是哪本書有說,
class A {
public:
int a;
int b;
inb c;
};
這種東西的 memory layout 會和純 C 這樣寫等價:
struct A {
public:
int a;
int b;
int c;
};
所以當你混合連結 C 和 C++ 的 object files 或 libraries 時,
你可以在 C++ 程式裡用前者代替後者。
而前者即使擁有 non-virtual member functions,
它們還是完全等價的東西。
但是如果你改成:
class A {
public:
int a;
private:
int b;
int c;
};
書上是說,
不同存取權的 sections 先後順序 compiler 不保證。
雖然現今所有的 compiler 都是 layout 成 a -> b -> c,
但是 b -> c -> a 也是被允許的一種 layout。
只是這種 layout 方式我也從來沒看過就是了。
而且如果全部丟到 public: 下的話,
也不會以這個問題。
但是這些都不重要。
存取權修飾字只是拿來防止 programmer 不小心犯錯,
不是拿來把 programmer 當賊防。
他真的硬要搞的話,
還是可以用 pointer 去改值。
封裝的用途前面也提很多了,
這裡就不再提。
: 如果是 針對 code實作不想被別人看到要隱藏 編成lib....
: 那也不用針對class來講這觀念.....
: 但就算編程lib .h一坨private他還是可以直接改成public 在自己來亂搞
: 這觀念在這邊請教一下......
你忘了 pimpl idiom。
因為它真正負責實作的 class 是被直接定義在實作檔裡,
所以在你做成 lib 後別人就真的完全摸不到。
想做到的話,
幾乎只有直接幹走你的 source code 或反組譯這幾招。
C++ 爸爸寫的聖經本還有提到所謂的內部/外部 header files,
這個方法也是不錯,
應用在 pimpl idiom 上也能讓實作碼不必全擠在一個檔案裡。
我覺得你誤解了資訊隱藏的原意。
它的目的是讓你的同事或使用者「不需要」知道實作細節,
而不是「不可以」知道實作細節。
這完全是兩碼子的事。
--
Ling-hua Tseng (uranus@tinlans.org)
Department of Computer Science, National Tsing-Hua University
Interesting: C++, Compiler, PL/PD, OS, VM, Large-scale software design
Researching: Software pipelining for VLIW architectures
Homepage: http://www.tinlans.org
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 118.160.105.214
※ 編輯: tinlans 來自: 118.160.105.214 (09/15 04:34)
推
09/15 04:45, , 1F
09/15 04:45, 1F
→
09/15 04:45, , 2F
09/15 04:45, 2F
→
09/15 04:49, , 3F
09/15 04:49, 3F
※ 編輯: tinlans 來自: 118.160.105.214 (09/15 06:03)
推
09/15 08:33, , 4F
09/15 08:33, 4F
推
09/15 09:50, , 5F
09/15 09:50, 5F
推
09/15 11:13, , 6F
09/15 11:13, 6F
推
09/15 11:22, , 7F
09/15 11:22, 7F
推
09/15 11:32, , 8F
09/15 11:32, 8F
推
09/15 13:34, , 9F
09/15 13:34, 9F
→
09/15 13:35, , 10F
09/15 13:35, 10F
→
09/15 13:36, , 11F
09/15 13:36, 11F
→
09/15 13:36, , 12F
09/15 13:36, 12F
推
09/15 13:40, , 13F
09/15 13:40, 13F
→
09/15 17:13, , 14F
09/15 17:13, 14F
推
09/15 21:19, , 15F
09/15 21:19, 15F
推
09/15 21:28, , 16F
09/15 21:28, 16F
推
09/15 21:36, , 17F
09/15 21:36, 17F
→
09/15 21:36, , 18F
09/15 21:36, 18F
→
09/15 23:36, , 19F
09/15 23:36, 19F
→
09/15 23:44, , 20F
09/15 23:44, 20F
推
09/16 00:41, , 21F
09/16 00:41, 21F
推
09/16 06:11, , 22F
09/16 06:11, 22F
→
09/16 06:50, , 23F
09/16 06:50, 23F
推
09/16 21:47, , 24F
09/16 21:47, 24F
推
09/18 12:11, , 25F
09/18 12:11, 25F
推
09/18 22:41, , 26F
09/18 22:41, 26F
討論串 (同標題文章)