C++ 函數重載,函數模板和函數模板重載,選擇哪一個?

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); // 顯示具體化
4
5 blot ink = {25, "spots"};
6 recycle(ink); //使用顯示具體化
術語“最具體”並不一定意味着顯示具體化,而是指編譯器推斷使用哪種類型時執行的轉換最少。例如:

1 struct blot {int a; char b[10]};
2 template void recycle(Type t); // #1
3 template void recycle(Type * t); // #2
4
5 blot ink = {25, "spots"};
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
3
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;
12
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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章