在包装socket
API的过程中,突然发现有个问题,那就是重载可能造成歧义,参考下列简化后的代码
// 原始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
支持直接发送字符串字面值或者C++标准库字符串,而不是指定缓冲区和缓冲区长度。由于这里的send
是模拟socket
API的send
,所以附带了flags
参数,因为一般而言为0,所以我把它设为默认参数。
来尝试调用重载函数。
test::send(buf, strlen(buf), 10); // 1. 使用全部参数
test::send(buf, strlen(buf)); // 2. 使用缺省flags参数
test::send("hello"); // 3. 发送字符串字面值
test::send("hello", 10); // 4. 发送字符串字面值,自定义flags
test::send(std::string(buf)); // 5. 发送std::string
运行结果如下
重载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种发送方式,如果我们是想使用重载1,发送缓冲区,正确的方式如下
char buf[] = "hello";
test::send(buf, strlen(buf));
其中strlen(buf)
返回类型是size_t
,和重载1的类型对应,但是假如缓冲区不是这么直白定义的,而是拼接的,有时候我们并不会把缓冲区长度声明为size_t
类型,比如下列场景
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);
结果为
重载2: buf: hello: cpp; END | flags: 15
snprintf
返回int
(为了在调用错误时返回-1),所以这里用auto
关键字也会推断为int
,于是这里会调用重载2,把len
当成flags
参数。
比较理想的情况是那些返回ssize_t
类型的API,此时编译器干脆会报错,至少不会让你调用错误的重载。
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++ 隐式类型转换重载决议的具体优先级是怎样的?
来看看陈硕举的例子
~# cat overload.cc
// overload.cc
#include
#include
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
按照【直接匹配>类型提升转换(float->double这种)>隐式转换>类类型转换】的思路,这里的字符串字面值"C++"
直接匹配的类型是const char*
,转换成std::string
是通过类类型转换,转换成bool
则是隐式转换,所以优先转换成bool
。
这种大坑也是C++11使用nullptr
的原因,比如对上述foo
的两个重载版本,传入nullptr
是无法通过编译的,因为nullptr
类型是nullptr_t
,无法隐式转换成整型,不像NULL
之前的定义(0
或者(void*)0
),可能被隐式转换成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