2017年8月31日 星期四

[轉] 比較安全的 C++ 虛擬函式寫法:C++11 override 與 final

很久之前,Heresy 有介紹過一些 C++11(當時還是 C++0x)的新語法,而之後也有陸續寫過一些新的 STL 的介紹(參考《C++11 / Boost C++ Libraries 目錄》)。
而雖然隔了很久,不過現在還是再回過頭來、記錄一下最近在看的、C++11 裡面和 class 的繼承有關的新語法吧~這次紀錄的,是用來讓類別繼承時的虛擬函式撰寫更安全一點的「override」這個新個語法。
首先,一般在寫繼承的時候,大部分會是像下面的寫法:
class CA
{
public:
void func1()
{
std::cout << “func1 in CA" << std::endl;
}
virtual void func2()
{
std::cout << “func2 in CA" << std::endl;
}
};
class CB : public CA
{
public:
void func1()
{
std::cout << “func1 in CB" << std::endl;
}
void func2()
{
std::cout << “func2 in CB" << std::endl;
}
};
在上面的例子裡面,CB 這個類別繼承自 CA 這個基礎類別,而由於 CA 的 func2() 有加上 virtual,所以會變成一個虛擬函式,也因此在呼叫 CA 的 func2() 的時候,他會去確認是要執行物件本身的型別,如果是衍生類別的話、是否有重新實作這個虛擬函式。
這類的程式,通常使用的狀況會是類似下面這樣:
CA* pObjA = new CA();
CB* pObjB = new CB();
pObjA->func1(); // func1 in CA
pObjA->func2(); // func2 in CA
pObjB->func1(); // func1 in CB
pObjB->func2(); // func2 in CB
CA* pObjB2 = pObjB;
pObjB2->func1(); // func1 in CA
pObjB2->func2(); // func2 in CB
這樣的好處,基本上就是可以透過一個共通的介面(CA),來操作不同類型的物件。
但是,在撰寫虛擬函式的時候,其實常常會碰到幾個問題,導致程式出錯;其中最主要的問題,就是
「以為自己是在重新實作基礎類別的虛擬函式、但是實際上沒有」!
這種問題發生的原因有很多種,包括了:
  • 基礎類別的函式忘了加上 virtual(例如上例中的 func1()
  • 虛擬函式的介面不一致
  • 某個類別的虛擬函式的介面修改後,忘了修改其他類別中的函式
下面就是兩個可能會發生的狀況:
class CA
{
public:
virtual void func1(int)
{
std::cout << “func1 in CA" << std::endl;
}
};
class CB : public CA
{
public:
void func1(intconst
{
std::cout << “func1 in CB" << std::endl;
}
};
class CC : public CA
{
public:
void func1(float)
{
std::cout << “func1 in CC" << std::endl;
}
};
以 CB 的 func1() 來說,雖然看來和 CA 的一樣,但是由於他有加上 const,所以還是不同;而 CC 的 func1() 則是由於參數型別不同,所以也不會被視為同一個虛擬函式。
所以,在執行下面的程式的時候,就都只會呼叫到 CA::func1(),而不會呼叫到各自的實作了。
CA* pObjA = new CA();
CA* pObjB = new CB();
CA* pObjC = new CC();
pObjA->func1(0); // func1 in CA
pObjB->func1(0); // func1 in CA
pObjC->func1(0); // func1 in CA
而為了避免這類的問題發生,C++11 提供了一個新的語法:「override」,來在編譯階段就可以確定衍生類別的函式的覆寫是否有成功。
它的使用方法也很簡單,只要在衍生類別裡面、要覆寫函式後面加上「override」、告訴編譯器這個函式是要用來覆寫基礎類別的虛擬函式就可以了。而如果加上了「override」,編譯器就會在編譯階段去檢查他是否有真的覆寫到基礎類別的虛擬函式。
如果把上面的例子都加上「override」的話,就變成:
class CA
{
public:
virtual void func1(int)
{
std::cout << “func1 in CA" << std::endl;
}
};
class CB : public CA
{
public:
void func1(intconst override
{
std::cout << “func1 in CB" << std::endl;
}
};
class CC : public CA
{
public:
void func1(floatoverride
{
std::cout << “func1 in CC" << std::endl;
}
};
而這樣的話,由於 CB 和 CC 的 func1() 都沒辦法成功地覆寫到基礎類別的虛擬函式,所以在編譯的時候,就會直接出現編譯錯誤、而不會等到執行階段才發現有問題了!
由於是編譯階段就會發現的錯誤,所以會讓問題及早被發現;如果專案寫得很大、繼承的類別很多的話,就算需要改動基礎類別的虛擬函式,只要衍生類別都有乖乖加上「override」的話,也就可以很方便地找到所有要做對應修改的地方了!

而除了 override 外,C++11 另外也還有一個指示字「final」,可以用來避免類別被繼承、或是虛擬函式被複寫。
在類別的繼承上,他的用法是:
class CA final
{
};
class CB : public CA
{
};
在上面的例子裡面,因為 CA 這個類別有加上「final」這個設定,所以它是不能被當成基礎類別而被繼承的;因此,在上面的程式裡面,CB 試圖去繼承 CA 的寫法,是會造成編譯階段的錯誤的。
而如果用在虛擬函式上的話,則會像下面這樣:
class CA
{
public:
virtual void func1() final;
};
class CB : public CA
{
public:
void func1() override;
};
在這邊,由於 CA 的 func1() 有加上「final」,編譯器會知道這個虛擬函式不能被衍生類別的函式覆寫;所以當 CB 試著去覆寫的時候,編譯器也會在編譯階段就產生錯誤。
透過這樣的機制,基本上應該是可以更好地去控制、也可以更明確地表達那些函式是要被覆寫、那些函式不可以被覆寫的。

[轉] source insight快捷鍵及使用技巧

source insight快捷鍵及使用技巧
退出程序                             : Alt+F4
重畫屏幕                             : Ctrl+Alt+Space
完成語法                             : Ctrl+E
複製一行                             : Ctrl+K
恰好複製該位置右邊的該行的字符       : Ctrl+Shift+K
複製到剪貼板                         : Ctrl+Del
剪切一行                             : Ctrl+U
剪切該位置右邊的該行的字符           : Ctrl+;
剪切到剪貼板                         : Ctrl+Shift+X
剪切一個字                           : Ctrl+,
左邊縮進                             : F9
右邊縮進                             : F10
插入一行                             : Ctrl+I
插入新行                             : Ctrl+Enter
加入一行                             : Ctrl+J
從剪切板粘貼                         : Ctrl+Ins
粘貼一行                             : Ctrl+P
重複上一個動作                       : Ctrl+Y
重新編號                             : Ctrl+R
重複輸入                             : Ctrl+
替換                                 : Ctrl+H
智能重命名                           : Ctrl+'
關閉文件                             : Ctrl+W
關閉所有文件                         : Ctrl+Shift+W
新建                                 : Ctrl+N
轉到下一個文件                       : Ctrl+Shift+N
打開                                 : Ctrl+O
重新裝載文件                         : Ctrl+Shift+O
另存為                               : Ctrl+Shift+S
顯示文件狀態                         : Shift+F10
激活語法窗口                         : Alt+L
回到該行的開始                       : Home
回到選擇的開始                       : Ctrl+Alt+[
到塊的下面                           : Ctrl+Shift+]
到塊的上面                           : Ctrl+Shift+[
書籤                                 : Ctrl+M
到文件底部                           : Ctrl+End, Ctrl+(KeyPad) End
到窗口底部                           : (KeyPad) End (小鍵盤的END)
到一行的尾部                         : End
到選擇部分的尾部                     : Ctrl+Alt+]
到下一個函數                         : 小鍵盤 +
上一個函數                           :   小鍵盤 -
後退                                 : Alt+,, Thumb 1 Click
後退到索引                           : Alt+M
向前                                 : Alt+., Thumb 2 Click
轉到行                               : F5, Ctrl+G
轉到下一個修改                       : Alt+(KeyPad) +
轉到下一個鏈接                       : Shift+F9, Ctrl+Shift+L
回到前一個修改                       : Alt+(KeyPad) -
跳到連接(就是語法串口列表的地方)     : Ctrl+L
跳到匹配                             : Alt+]
下一頁                               : PgDn, (KeyPad) PgDn
上一頁                               : PgUp, (KeyPad) PgUp
向上滾動半屏                         : Ctrl+PgDn, Ctrl+(KeyPad) PgDn, (KeyPad) *
向下滾動半屏                        : Ctrl+PgUp, Ctrl+(KeyPad) PgUp, (KeyPad) /
左滾                                 : Alt+Left
向上滾動一行                         : Alt+Down
向下滾動一行                         : Alt+Up
右滾                                 : Alt+Right
選擇一塊                             : Ctrl+-
選擇當前位置的左邊一個字符           : Shift+Left
選擇當前位置右邊一個字符             : Shift+Right
選擇一行                             : Shift+F6
從當前行其開始向下選擇               : Shift+Down
從當前行其開始向上選擇               : Shift+Up
選擇上頁                             : Shift+PgDn, Shift+(KeyPad) PgDn
選擇下頁                             : Shift+PgUp, Shift+(KeyPad) PgUp
選擇句子(直到遇到一個 . 為止)        : Shift+F7, Ctrl+.
從當前位置選擇到文件結束             : Ctrl+Shift+End
從當前位置選擇到行結束               : Shift+End
從當前位置選擇到行的開始             : Shift+Home
從當前位置選擇到文件頂部             : Ctrl+Shift+Home
選擇一個單詞                         : Shift+F5
選擇左邊單詞                         : Ctrl+Shift+Left
選擇右邊單詞                         : Ctrl+Shift+Right
到文件頂部                           : Ctrl+Home, Ctrl+(KeyPad) Home
到窗口頂部                           : (KeyPad) Home
到單詞左邊(也就是到一個單詞的開始)   : Ctrl+Left
到單詞右邊(到該單詞的結束)           : Ctrl+Right
排列語法窗口(有三種排列方式分別按1,2,3次)        : Alt+F7
移除文件                             : Alt+Shift+R
同步文件                             : Alt+Shift+S
增量搜索(當用Ctrl + F 搜索,然後按F12就會轉到下一個匹配)      : F12
替換文件                             : Ctrl+Shift+H
向後搜索                             : F3
在多個文件中搜索                     : Ctrl+Shift+F
向前搜索                             : F4
搜索選擇的(比如選擇了一個單詞,shift+F4將搜索下一個)    : Shift+F4
搜索                                 : Ctrl+F
瀏覽本地語法(彈出該文件語法列表窗口,如果你光標放到一個變量/函數等,那麼列出本文件該變量/函數等的信息)    : F8
瀏覽工程語法                         : F7, Alt+G
跳到基本類型(即跳到原型)             : Alt+0
跳到定義出(也就是聲明)               : Ctrl+=, Ctrl+L Click (select), Ctrl+Double L Click
檢查引用                             : Ctrl+/
語法信息(彈出該語法的信息)           : Alt+/, Ctrl+R Click (select)
高亮當前單詞                         : Shift+F8
語法窗口(隱藏/顯示語法窗口)          : Alt+F8
關閉窗口                             : Alt+F6, Ctrl+F4
最後一個窗口                         : Ctrl+Tab, Ctrl+Shift+Tab


幾個較常用的快捷鍵
默認情況下,SI已經定義了很多非常實用的快捷鍵:
F5
指定行號,實現行跳轉,在遇到編譯錯誤的時候,能特別方便的找到出錯行。
Shift+F8
高亮顯示指定標識,快速瀏覽標識的使用情況。
Ctrl+鼠標點擊標識
直接跳轉至標識定義處。
Ctrl+F
本文件內查找。
F3
本文件查找結果的上一個。
F4
本文件查找結果的下一個。
F7
打開Browse Project Symbols窗口,快速瀏覽工程內標識定義。
Ctrl+M
創建或查找書籤,方便下次找回此位置。

Source Insight技巧收集

1、背景色選擇
    要改變背景色Options->preference->windows background->color設置背景色
2、解決字符等寬對齊問題。
    SIS默認字體是VERDANA,很漂亮。這網頁上應該也是用的VERDANA字體。但由於美觀的緣故,VERDANA字體是不等寬的。比如下面兩行
    llllllllll
    MMMMMMMMMM
    同樣10個字符,長度差多了.用VERDANA來看程序,有些本應該對齊的就歪了。解放方法是使用等寬的字體,但肯定比較醜。比較推薦的是用Courier New。
3、解決TAB鍵縮進問題
    Options-> Document Options裡面的右下角Editing Options欄裡,把Expand tabs勾起來,然後確定。OK,現在TAB鍵的縮進和四個空格的縮進在SIS裡面看起來就對齊咯
4、SI中的自動對齊設置:
    在C程序裡, 如果遇到行末沒有分號的語句,如IF, WHILE, SWITCH等, 寫到該行末按回車,則新行自動相對上一行縮進兩列。
    Option->Document option下的Auto Indient中Auto Indient Type有三種類型 None,Simple,Smart。個人推薦選用Simple類型。
5、向項目中添加文件時,只添加特定類型的文件(文件類型過濾器)
    編輯彙編代碼時,在SIS裡建立PROJECT並ADD TREE的時候,根據默認設置並不會把該TREE裡面所有彙編文件都包含進來
    只加了.inc和.asm後綴的,.s後綴的沒有。而且用SIS打開.s的文件,一片黑白沒有色彩,感覺回到DOS的EDIT時代了……
    解決方法是在Options->Document Options裡面,點左上的Document Type下拉菜單,選擇x86 Asm Source File,
    然後在右邊的File filter裡*.asm;*.inc;的後面加上*.s;接著CLOSE就可以了。
    上面問題解決了,但注意加入*.s後還需要重新ADD TREE一遍才能把這些彙編加到PROJECT裡面。
6、添加文件類型
    用戶可以定義自己的類型,Options->Document Options->add type,定義文件類型名以及文件名後綴。
    勾選include when adding to projects在添加目錄下文件到工程是該類文件就會添加進SI的工程。
    如果需要將所有文件添加進SI的工程,可以定義一種文件類型*.*。
7、恢復ctrl+a的全選功能
    通過關鍵詞save 找到save all,更改為ctrl+shift+a,通過關鍵詞select找到select all,更改為ctrl +a

Source Insight常用的快捷鍵:
    Ctrl+= :Jump to definition
    Alt+/ :Look up reference
    F3 : search backward
    F4 : search forward
    F5: go to Line
    F7 :Look up symbols
    F8 :Look up local symbols
    F9 :Ident left
    F10 :Ident right
    Alt+, :Jump backword
    Alt+. : Jump forward
    Shift+F3 : search the word under cusor backward
    Shift+F4 : search the word under cusor forward
    F12 : incremental search
    Shift+Ctrl+f: search in project
    shift+F8 : hilight word
Source Insight的窗口操作:
    project window Ctrl+O打開
    symbol window Alt+F8打開和關閉
    Contex Window 自定義鍵打開和關閉
    Relation Window 自定義鍵打開 先鎖定再刷新聯繫
在Source Insight中添加自定義功能的步驟如下:
1.Source Insight中,Options->Custom Commands...->Add...,New Command name 隨便寫,我的是"Edit with Vim"
2.Run中寫入: "C:Program FilesVimvim63gvim.exe" --remote-silent +%l %f
意思是在當前已經打開的gvim窗口裡面打開當前的文件,並且跳轉到指定行
%l為當前的行號,%f為文件名
使用 --remote-silent 的作用是,如果已經打開了對應文件,就不會打開第二次,而是在已經打開的文件裡跳轉到對應行
3.還是同一個對話框裡面,選擇Keys->Assign New Key...->按F12,如果你已經將F12設置給其他命令,選擇其他的按鍵就行了
下面是一些常用自定義功能:( CUSTOM COMMANDS )
打開資源管理器並選中當前文件
ShellExecute open explorer /e,/select,%f
查看log
"C:Program FilesTortoiseSVN inTortoiseProc.exe" /command:log /path:%f /notempfile /closeonend
diff
"C:Program FilesTortoiseSVN inTortoiseProc.exe" /command:diff /path:%f /notempfile /closeonend
取得鎖定(check out)
"C:Program FilesTortoiseSVN inTortoiseProc.exe" /command:lock /path:%f /notempfile /closeonend
提交(check in)
"C:Program FilesTortoiseSVN inTortoiseProc.exe" /command:commit /path:%f /notempfile /closeonend
更新(update)
"C:Program FilesTortoiseSVN inTortoiseProc.exe" /command:update /path:%f /notempfile /closeonend
更新整個目錄(update all)
"C:Program FilesTortoiseSVN inTortoiseProc.exe" /command:update /path:*.* /notempfile /closeonend
取消鎖定(undo check out)
"C:Program FilesTortoiseSVN inTortoiseProc.exe" /command:revert /path:%f /notempfile /closeonend
在ultriEdit中編輯
"C:Program FilesUltraEdit-32/uedit32" %f
在vim中編輯並定位到當前行
"C:Program FilesVimvim63gvim.exe" --remote-silent +%l %f
匯總其他小技巧:
讓{ 和 } 不縮進:
Options->Document Options->Auto Indent->Indent Open Brace/Indent Close Brace
hao space: SourceInsight 小技巧
1、按住"ctrl", 再用鼠標指向某個變量,點擊一下,就能進入這個變量的定義。
2、今天把一個用sourceinsight排版整齊的C文件,偶然用VC打開一看,全亂了。研究了半天,發現SI對每個字符的寬度不太一致。
    請教同事發現選上"view --> draft view", 就可以讓每個字符的寬度一致了。快捷鍵是 "Alt + F12"
3、"shift+F8" 標亮所有文本中光標所在位置的單詞
4、跳到某一行:"ctrl + g"
Source Insight是閱讀和編寫代碼的好東東,基本上也算得上是經典之作了,雖然還有一點點小bug,不過對於我們這些C程序員來說可是一旦擁有別無所求。下列小技巧是在工作中同事整理總結的,對提高工作效率多少有點幫助,其中有些是對應於SVN的,沒有使用SVN做版本管理的人就不要白費力氣了。
ShellExecute open explorer /e,/select,%f
        /*作用是在資源管理器中打開當前編輯文件並選中*/
        /*可以設置快捷鍵如ctrl+e,這樣能很方便的在資源管理器打開對應的文件,並進行tortoiseSVN的相關操作*/
X:Progra~1TortoiseSVN inTortoiseProc.exe /command:log /path:% /notempfile /closeonend
        /*使用前注意更改對應的bin安裝路徑*/
        /*作用是直接查看當前文件的svn log*/
        /*可以設置快捷鍵如ctrl+l*/
X:Progra~1TortoiseSVN inTortoiseProc.exe /command:diff /path:% /notempfile /closeonend
        /*使用前注意更改對應的bin安裝路徑*/
        /*作用是直接查看當前文件和基準版本的比較*/
        /*可以設置快捷鍵如ctrl+d*/
                                       Source Insight中的文件過濾器
遇到在新建工程的時候,需要加入一些除了.c .h 等之外的文件,比如.s,.scf ,Makefile和ReleaseNotes等文件,而每次新建工程的時候,即使取消了shown only known document types,和在點擊Add All之後選擇了Recusively add lower sub-directories ,也還是不能正常識別這類文件,也就不能加入進工程,只能我們自己手動雙擊添加到工程中,如果文件少還無所謂,但是包含了很多子文件夾的大工程,這樣實在不可行。
百度了下,終於發現解決辦法了:
打開Source Insight,在新建工程之前,進入
Options -> Document Options... Alt-T    -> 點擊Document Type的下拉框,然後選擇Make File,在右邊的File Filter中,在原先的*.mak後面加上一個分號,即多個不同過濾規則以分號間隔開,再加上*makefile,變成 *.mak;*makefile,並且選中Include when adding to projects,這樣,以後再新建工程的時候,就可以識別makefile或Makefile了(好像此處Source Insight並不區分大小寫)。
類似的原理,給其他你想要加入的不同的類型的文件,分別加入到原先的一些文件類型後面,注意要用分號隔開,或者直接新建一個文件類型,然後寫上對應的顧慮規則,比如
點擊 Add Type,填入新文件類型的名字Scatter File,File Filter中寫上*.scf,注意再選中下面的Include when adding to projects,這樣就建立了一個新的文件類型, 以後新建工程加入文件時候,系統就能夠識別後綴是scf的文件了。
當然感興趣的,還可以對你新建立的文件類型進行一些格式化設置。包括Parsing,Tab等等設置。


==========================================================================

1. 自訂新的快捷組合鍵
    Option -> Key Assignments ->
    View: Project File List (這裡請選你要更改的快捷功能)->
    Assign New Key...

2. 將Tab鍵取代成四個空格:
    Option -> Document Options ->
    Expand Tabs打勾 ->
    Tab Width內輸入4

3. 在當前檔案,找出XXX字串 (基本)
    Search XXX: Ctrl +  F

    找完字串後,可用F3跳到上一個找到的XXX字串;用F4跳到下一個找到的XXX字串
    註:但在大多數的編輯器或瀏覽器(例:Notepad++,Source Insight或Chrome)中,F3是用來跳到下一個找到的XXX字串,而用Shift + F3跳到上一個找到的XXX字串

4. 在所有Project中的檔案,找出XXX字串
    (比起Find Reference,我比較常用這)
    Search XXX in Files: Ctrl + Alt + F
  
    跨檔案找完字串後,可用Ctrl + Up跳到上一個找到的XXX字串;用Ctrl + Down跳到下一個找到的XXX字串

5. 叫出Project視窗,這是我自訂的快捷組合,方法請參考第一項
    快捷功能:View: Project File List
    Find File: Alt + R
    可快速用檔名找你想要的檔案

6. 叫出Symbol視窗,這是我自訂的快捷組合,方法請參考第一項
    快捷功能:Navigation: Activate Symbol Window
    Find Symbol: Alt + A
    可快速在當前檔案,找你想要的Function名或變數名

7. 選取游標當前的字 (Function名或變數名),這是我自訂的快捷組合,方法請參考第一項
    快捷功能:Navigation: Select Word
    Select Word: Alt + W
    註:在Visual Studio中,是用Ctrl + W來選取字,但在大多數的編輯器或瀏覽器(例:Notepad++,Source Insight或Chrome)中,Ctrl + W是用來關閉當前分頁。

8. 跳到上(下)一個游標停過的位置
    Go Back: Alt + <
    Go Forward: Alt + >

9. 跳至當前游標所在的變數或Function的定義
    Jump to Definition: Ctrl + =

10. 找出XXX變數有被用到的地方
    Find Reference: Ctrl + /


11. 在嘗試加檔案給當前的project時,有選想要的資料夾,但即使勾選了"Recursively add lower sub-directories",很多想要加的原始碼都沒有被加入。
      Ans: 嘗試在Options->Document Options裡,先選你要的Document Type,然後留意File filter內有你要的附檔案名,例如,我現要為C語言的案子加檔案,我先在Document Type先選:C Source File。並在File filter中確認至少有*.c,*.h。如果沒有,自己輸入。這時關掉Document Options視窗,再嘗試加檔案即可。