C++ 函數重載,函數模闆和函數模闆重載,選擇哪一個?
重載解析#
在C++中,對于函數重載、函數模闆和函數模闆重載,C++需要有一個良好的政策,去選擇調用哪一個函數定義(尤其是多個參數時),這個過程稱為重載解析。
(這個過程将會非常複雜,但願不要遇到一定要寫這種代碼的時候。)
大緻步驟#
Ⅰ.建立候選函數清單(其中包含與候選函數相同名稱的函數和模闆函數)。
Ⅱ.使用候選函數清單建立可行函數清單(要求參數數目正确,為此有一個隐式類型轉換序列,其中包括實參類型與相應的形參類型完全比對的情況。例如,使用float參數的函數調用可以将該參數轉換為double類型,進而與double比對,而模闆函數可以為float類型生成一個函數執行個體)。
Ⅲ.确定是否有最佳的可行函數(如果有則調用,沒有則報錯)。
我們以隻有一個參數的函數為例:
1 may('B'); // 函數調用
2
3 /以下是一系列候選函數聲明/
4 void may(int); // #1
5 float may(float, float = 3); // #2
6 void may(char); // #3
7 char may(const char ); // #4
8 char may(const char &); // #5
9 template void may(const T &); // #6
10 template void may(T *); // #7
這些函數聲明都會進入函數清單(因為名稱相同),接下來考慮特征标(參數數量與類型),不考慮傳回值。其中#4和#7不可行,因為整數無法隐式類型轉換為指針類型。#6可用來生成具體化,其中T被替換為char類型,此時還剩下5個可行的函數(#1、#2、#3、#5、#6)。如果此時隻剩下一個,那麼任何一個都可以正确使用。
接下來,到了擇優環節。這一步主要考量的是函數調用參數與可行的候選函數的參數比對所需要進行的轉換。通常,從最佳到最差的順序如下:
1、完全比對,函數優于模闆。
2、提升轉換(例如,char和short自動轉換為int,float自動轉換為double)。
3、标準轉換(例如,int轉換為char,long轉換為double)。
4、使用者定義的轉換,如類聲明中定義的轉換。
在剩餘的5個函數中,#1優于#2,因為char到int是提升轉換,而char到float是标準轉換(此時還剩#1,#3,#5,#6)。#3、#5、#6優于#1和#2,因為他們是完全比對(還剩#3,#5,#6)。#3和#5優于#6,因為#6是模闆(還剩#3和#5)。
這時,會出現兩個問題,完全比對到底是什麼?如果有兩個完全比對(#3和#5)該怎麼辦?通常有兩個完全比對是一種錯誤,但這一規則有兩個例外。
完全比對和最佳比對#
進行完全比對時,C++允許某些“無關緊要的轉換”,下表列出了這些轉換——Type表示任意類型。例如,int到int &,注意,Type可以是char &這樣的類型,是以,這些規則也包括char &到const char &的轉換。
完全比對允許的無關緊要的轉換
從實參 到形參
Type Type &
Type & Type
Type[] * Type
Type(參數清單) Type(*)(參數清單)
Type const Type
Type volatile Type
Type * const Type
Type volatile Type
假設有如下代碼:
1 struct blot {int a; char b[10]};
2 blot ink = {25, "spots"};
3 recycle(ink);
4
5 // 下面的原型完全比對
6 void recycle(blot); // #1 blot to blot
7 void recycle(const blot); // #2 blot to const blot
8 void recycle(blot &); // #3 blot to blot &
9 void recycle(const blot &); // #4 blot to const blot &
如果有多個完全比對的原型,則無法完成重載解析過程,如果沒有最最佳的可行函數,編譯器将報錯。
然而,有這樣的例外規則,首先,指向非const資料的指針和引用優先于非const指針和引用,在上例中,如果隻定義了#3和#4,将選擇#3,因為ink沒有被聲明為const,然而const和非const之間的差別隻适用于指針和引用指向的資料,也就是說,如果隻定義了#1和#2,将出現二義性錯誤。
一個完全比對優于另一個的另一種情況是,其中一個是非模闆函數而另一個不是,這種情況下,非模闆函數将優先于模闆函數(包括顯式具體化)。
如果兩個完全比對的函數都是模闆函數,則較具體的模闆函數優先,這意味着顯示具體化将優于模闆隐式生成的具體化。
1 struct blot {int a; char b[10]};
2 template void recycle(Type t); // 模闆
3 template <> void recycle(blot & t); // 顯示具體化
5 blot ink = {25, "spots"};
6 recycle(ink); //使用顯示具體化
術語“最具體”并不一定意味着顯示具體化,而是指編譯器推斷使用哪種類型時執行的轉換最少。例如:
2 template void recycle(Type t); // #1
3 template void recycle(Type * t); // #2
6 recycle(&ink); // 使用#2,因為轉換最少,#2被認為是更具體的
用于找出最具體的模闆的規則被稱為部分排序規則。
部分排序規則#
1 template
2 void show(T arr[], int n); // #1
3
4 template
5 void show(T * arr[], int n); // #2
6
7 struct debts
8 {
9 char name[50];
10 double amount;
11 };
12
13 ......
14
15 int things[6] = {13,31,103,301,310,130};
16 debts mr[3] =
17 {
18 {"aaa", 24.1},
19 {"bbb", 25.2},
20 {"ccc", 26.3}
21 };
22 double * pd[3];
23
24 for(int i=0; i<3; i++)
25 {
26 pd[i] = &mr[i].amount;
27 }
28
29 show(things, 6); // 使用#1
30 show(pd, 3); // 使用#2
things是一個int數組,與#1比對,其中T被替換為int。pd是一個double 數組,與#1比對時,T被替換為double ,與#2比對時,T被替換為double。在這兩個模闆中,#2更加具體,因為它做了特定的假設,數組内容是指針,是以被使用。如果将#2從程式中删除,那麼使用#1,将顯示出位址,而不是值。
總之,重載解析将尋找最比對的函數,如果隻存在一個這樣的函數,則選擇它;如果存在多個這樣的函數,但其中隻有一個非模闆函數,則選擇它;入伏哦存在多個合适的函數且都為模闆函數,但其中隻有一個函數比其他函數更具體,則選擇它。其他情況(有多個非模闆或模闆函數,但沒有一個比其他更具體,或根本不存在比對的函數)均為錯誤。
建立自定義選擇#
在有些情況下,可以引導編譯器做出你希望的選擇。
1 template
2 T lesser(T a, T b); // #1
4 int lesser(int a, int b); // #2
5
6 ......
7
8 int m = 20;
9 int n = -30;
10 double x = 15.5;
11 double y = 25.9;
13 lesser(m, n); // 使用#2
14 lesser(x, y); // 使用#1,T被轉換為double類型
15 lesser<>(m, n); // <>提示編譯器,使用模闆函數,使用#1
16 lesser(x, y); // 顯式執行個體化,将使用執行個體化後的函數x,y被強制轉換為int類型
多個參數的函數#
将有多個參數的函數調用與有多個參數的原型進行比對時,情況将非常複雜。編譯器必須考慮所有參數的比對情況。如果找到比其他可行函數都合适的函數,則選擇該函數。一個函數要比其他函數都合适,其所有參數的比對程度都必須不必其他函數差,同時至少有一個參數的比對程度比其他函數高。
作者: Dylan~
出處:
https://www.cnblogs.com/Dylan7/p/12826456.html