explorer

万丈高楼平地起,勿在浮沙筑高台

0%

Effective C++ :不要创建通用引用的重载函数

创建通用引用的重载函数,但很多时候还是会调用到通用引用函数,这会让人很迷惑。

普通版本

假设我们要实现一个函数,用于存入传递进来的std::string,普通版本如下:

1
2
3
4
5
6
7
std::multiset<std::string> names;     // global data structure
void logAndAdd(const std::string& name) {
auto now = // get current time
std::chrono::system_clock::now();
log(now, "logAndAdd"); // make log entry
names.emplace(name); // add name to global data
} // structure;

在使用的时候如下:

1
2
3
4
std::string petName("Darla");
logAndAdd(petName); // pass lvalue std::string
logAndAdd(std::string("Persephone")); // pass rvalue std::string
logAndAdd("Patty Dog"); // pass string literal

第一种情况传入的是左值,且形参name也是左值,它以拷贝的方式加入全局变量names

第二种情况创建了临时对象传递给name,形参name是左值,它以拷贝的方式加入全局变量names。但这种情况下,我们实际上是可以以移动的方式加入names的。

第三种情况隐式通过字面值字符串创建了临时对象给name,与第二种情况一样,它们也是可以以移动的方式加入names的。

所以以上第二、三种情况都可以被优化。

优化版本

当传入的参数是左值时,使用拷贝。当传入的参数是右值时,使用移动。那么基于通用引用的std::forward转换便是一个比较好的方式:

1
2
3
4
5
6
template<typename T>
void logAndAdd(T&& name) {
auto now = std::chrono::system_clock::now();
log(now, "logAndAdd");
names.emplace(std::forward<T>(name));
}

使用上面这个函数模板后,第一种情况依然以拷贝方式加入全局变量names。而第二、三种情况则是以移动的方式加入names

加入重载

下面假设,用户可以查询索引的方式传入字符串,那么logAndAdd就需要一个重载版本:

1
2
3
4
5
6
7
std::string nameFromIdx(int idx);      // return name corresponding to idx new overload

void logAndAdd(int idx) {
auto now = std::chrono::system_clock::now();
log(now, "logAndAdd");
names.emplace(nameFromIdx(idx));
}

那么要调用该函数的方式如下:

1
logAndAdd(22);                         // calls int overload

但如果传入一个变量,便会出错:

1
2
3
short nameIdx;
// give nameIdx a value
logAndAdd(nameIdx); // error!

这是因为,当传入的参数是short时,函数模板就将T推导成了short,虽然int版本的重载函数也可以接受short类型,但是函数模板的推导结果更为准确。最终short无法构造出std::string而导致报错。