慎用C++重載,小心隱式類型轉(zhuǎn)換

在包裝socket API的過程中,突然發(fā)現(xiàn)有個(gè)問題,那就是重載可能造成歧義,參考下列簡(jiǎn)化后的代碼

// 原始API
void send(const char* buf, size_t len, int flags) {
    cout << "buf:    " << std::string(buf, buf + len) << " | ";
    cout << "flags:  " << flags << endl;
}

// 重載
namespace test {

void send(const char* buf, size_t len, int flags = 0) {
    cout << "重載1: " ;
    ::send(buf, len, flags);
}

void send(const char* buf, int flags = 0) {
    cout << "重載2: ";
    ::send(buf, strlen(buf), flags);
}

void send(const std::string& buf, int flags = 0) {
    cout << "重載3: ";
    ::send(buf.data(), buf.size(), flags);
}

}

我的目的是讓send支持直接發(fā)送字符串字面值或者C++標(biāo)準(zhǔn)庫字符串,而不是指定緩沖區(qū)和緩沖區(qū)長(zhǎng)度。由于這里的send是模擬socket API的send,所以附帶了flags參數(shù),因?yàn)橐话愣詾?,所以我把它設(shè)為默認(rèn)參數(shù)。
來嘗試調(diào)用重載函數(shù)。

    test::send(buf, strlen(buf), 10);  // 1. 使用全部參數(shù)
    test::send(buf, strlen(buf));      // 2. 使用缺省flags參數(shù)
    test::send("hello");               // 3. 發(fā)送字符串字面值
    test::send("hello", 10);           // 4. 發(fā)送字符串字面值,自定義flags
    test::send(std::string(buf));      // 5. 發(fā)送std::string

運(yùn)行結(jié)果如下

重載1: buf:    hello | flags:  10
重載1: buf:    hello | flags:  0
重載2: buf:    hello | flags:  0
重載2: buf:    hello | flags:  10
重載3: buf:    hello | flags:  0

但是,注意第4種發(fā)送方式,如果我們是想使用重載1,發(fā)送緩沖區(qū),正確的方式如下

    char buf[] = "hello";
    test::send(buf, strlen(buf));

其中strlen(buf)返回類型是size_t,和重載1的類型對(duì)應(yīng),但是假如緩沖區(qū)不是這么直白定義的,而是拼接的,有時(shí)候我們并不會(huì)把緩沖區(qū)長(zhǎng)度聲明為size_t類型,比如下列場(chǎng)景

    char buf[1024];
    auto len = snprintf(buf, sizeof(buf), "hello: %s", "cpp");
    len += snprintf(buf + len, sizeof(buf) - len, "; %s", "END");
    test::send(buf, len);

結(jié)果為

重載2: buf:    hello: cpp; END | flags:  15

snprintf返回int(為了在調(diào)用錯(cuò)誤時(shí)返回-1),所以這里用auto關(guān)鍵字也會(huì)推斷為int,于是這里會(huì)調(diào)用重載2,把len當(dāng)成flags參數(shù)。
比較理想的情況是那些返回ssize_t類型的API,此時(shí)編譯器干脆會(huì)報(bào)錯(cuò),至少不會(huì)讓你調(diào)用錯(cuò)誤的重載。

    char buf[] = "hello";
    ssize_t buflen = strlen(buf);
    test::send(buf, buflen);
# g++ -std=c++11 overload.cc 
overload.cc: In function ‘int main()’:
overload.cc:37:27: error: call of overloaded ‘send(char [6], ssize_t&)’ is ambiguous
     test::send(buf, buflen);
                           ^
overload.cc:17:6: note: candidate: void test::send(const char*, size_t, int)
 void send(const char* buf, size_t len, int flags = 0) {
      ^
overload.cc:22:6: note: candidate: void test::send(const char*, int)
 void send(const char* buf, int flags = 0) {
      ^
overload.cc:27:6: note: candidate: void test::send(const string&, int)
 void send(const std::string& buf, int flags = 0) {

重載的大坑可以參考知乎的回答:C++ 隱式類型轉(zhuǎn)換重載決議的具體優(yōu)先級(jí)是怎樣的?
來看看陳碩舉的例子

~# cat overload.cc 
// overload.cc
#include <iostream>
#include <string>
using namespace std;

void foo(const string& name) { cout << name << endl; }
void foo(bool on) { cout << on << endl; }

int main() {
    foo("C++");
    return 0;
}
~# g++ -std=c++11 overload.cc 
~# ./a.out 
1

按照【直接匹配>類型提升轉(zhuǎn)換(float->double這種)>隱式轉(zhuǎn)換>類類型轉(zhuǎn)換】的思路,這里的字符串字面值"C++"直接匹配的類型是const char*,轉(zhuǎn)換成std::string是通過類類型轉(zhuǎn)換,轉(zhuǎn)換成bool則是隱式轉(zhuǎn)換,所以優(yōu)先轉(zhuǎn)換成bool。

這種大坑也是C++11使用nullptr的原因,比如對(duì)上述foo的兩個(gè)重載版本,傳入nullptr是無法通過編譯的,因?yàn)?code>nullptr類型是nullptr_t,無法隱式轉(zhuǎn)換成整型,不像NULL之前的定義(0或者(void*)0),可能被隱式轉(zhuǎn)換成int

void foo(nullptr_t) { cout << "nullptr" << endl; }
void foo(const char*) { cout << "char*" << endl; }

foo("C++");   // char*
foo(nullptr); // nullptr
//foo(NULL);  // error: call of overloaded ‘foo(NULL)’ is ambiguous
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容