引言
int f(int param) {return d(param);
}
上面这段代码看着是不是很不合理,为什么不直接调用d
函数,而非要通过f
函数来调用d
函数?
正文
意义
引言部分抛出的问题如何解答?
其实,抛开代码的语义和业务功能,单从语法层面来说,这种写法没有问题,但不合理。
但是,我们实际开发场景中最重要的就是要实现业务功能,在这个基础上来优化代码的语义。
语义清晰性
在实际的程序中,因为封装的层级不同,函数的命名会有很大的区别,比如:
底层模块提供了一个接口是calculateHashSum
,用来计算哈希值;
但是在业务层需要通过calculateHashSum
来实现验证数据的功能,那么我们在业务层调用时往往会封装一个名为validateData
的接口,在这个接口中调用calculateHashSum
函数,代码如下:
bool validateData(string data) {return calculateHashSum(data);
}
从代码语义来看,业务层不关心底层是通过什么方式实现的验证功能,所以这种方式能够大大的提高代码语义的清晰性。
当然,这种方法也存在缺点:
- 如果函数没有内联,它可能会创建额外的副本
- 如果有多个参数需要传递给底层接口,那么就需要大量烦人的代码
- 调试时,需要执行额外的步骤
C++11:函数别名
C++11提供了一种函数别名的方法来解决创建额外副本的副作用:
const auto validateData = calculateHashSum;
这种方式就是给函数取了一个别名,在实际调用时不是调用validateData
函数,而是调用calculateHashSum
函数。
注意:不能取消
const
!如果取消const
会使validateData
能够随时变更指向其他函数,容易产生异常。
原理
在C++中,const auto
关键词被用来自动推断validateData
的类型,并将其声明为一个常量。这意味着一旦validateData
被赋值为 calculateHashSum
后,它不能再被赋值为其他的函数或值。
calculateHashSum
是一个函数,而在C++中,函数名本身可以被视为一个指向该函数的指针。当你写const auto validateData = calculateHashSum;
时,你实际上是创建了一个新的函数指针validateData
,并让它指向calculateHashSum
函数的地址。因此,validateData
成为了calculateHashSum
的一个别名,你可以通过validateData
调用calculateHashSum
函数。
底层实现
-
函数指针:在底层,函数别名是通过函数指针实现的。函数指针是一个变量,存储着一个函数的内存地址。当你将一个函数赋值给另一个变量时,你实际上是在复制该函数的内存地址到新变量中。这样,通过新变量调用函数时,它会跳转到这个地址执行代码。
-
内联替换:编译器可能会在某些情况下优化这种函数指针的使用。如果编译器能够确定通过别名调用的函数,并且这种调用非常频繁,它可能会选择内联这个函数的调用,这意味着函数调用会被替换为函数体本身的代码,以减少函数调用的开销。
-
类型安全:在C++中,使用
auto
关键字可以确保类型安全,因为编译器会自动推断正确的类型。这样可以避免由于类型不匹配导致的编译错误或运行时错误。
C++14:模板函数别名
如果我们想为模板函数添加别名要怎么办?
template<typename T>
void d(T) {}
添加别名:
template<typename T>
const auto f = d<T>;
注意:这是C++14加入的功能