在包裝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