题记:什么事情都要追问一个为什么,真正理解了为什么,才能活学活用。
代码1
下面的代码能编译通过吗?
#include <stdio.h>
#include <stdlib.h>
class X
{
public:
int *get() { return new int(); }
double *get() { return new double(); }
};
int main()
{
int * v1 = X().get();
double * v2 = X().get();
return 0;
}
答案肯定是编译不过。因为下面两个函数的“签名”是一样的:
int *get();
double *get();
在 C++ 语言中,函数签名包含函数名称、函数参数类型、函数参数个数等信息,但是不包含返回值类型。
代码2
下面的代码能编译通过吗?
#include <stdio.h>
#include <stdlib.h>
class X
{
public:
template <class T>
T *get() { return new T(); }
};
int main()
{
int * v1 = X().get<int>();
double * v2 = X().get<double>();
return 0;
}
答案是可以编译通过!这是为什么呢????难道 X 这个模板对象展开后,不是展开成类似下面的样子吗:
class X
{
public:
int *get() { return new int(); }
double *get() { return new double(); }
};
按照 代码1 的分析和结论,这样是编译不过的才对呀!
解惑
C++ 标准里是明确定义了,函数签名不包含返回值类型。但是,为什么要这样定义呢?原因如下:
虽然,下面的调用理论上编译器是可以帮忙选出正确函数:
int * v1 = X().get();
double * v2 = X().get();
但是,下面的场景编译器就会犯糊涂:
X().get();
不使用返回值的调用方式 C++ 是允许的,但是这种情况下,编译器应该为它链接哪一个函数呢?编译器也不知道!正是因为存在这种不好解析的场景存在,C++才把“返回值类型”从函数签名中剔除。
明白了这个道理,就不难理解 ** 代码2 ** 为何可以编译通过了。下面的调用不存在任何二义性,所以编译器才允许模板函数中使用模板参数定义返回值类型。
X().get<int>();
X().get<double>();
使用 nm 工具可以看到,编译之后类型信息也加入到符号中了:
你学废了吗?
补充资料
并不是所有语言都不允许返回值类型作为函数签名的。比如 Swift 语言。那么它是如何解决二义性问题的呢?很简单,针对 X().get()
这种调用直接报错!除非提供足够信息帮助编译器正确推导出完整签名。
func some() -> Bool {
return true;
}
func some() -> Int {
return 1;
}
// 编译成功,编译器可以找到正确的函数
let valBool: Bool = some()
let valInt: Int = some()
// 编译失败,编译器无法确定应该使用哪个函数
some()
参考资料:Stack Overflow: Why is the return type of method not included in the method-signature?