考虑如下代码:
std::multiset<std::string> names; // 全局数据结构
void logAndAdd(const std::string &name) {
auto now = std::chrono::system_clock::now();
log(now, "logAndAdd");
names.emplace(name); // 將名字添加到全局数据结构中
}
std::string petName("Darla");
logAndAdd(petName); // 传递左值std::string
logAndAdd(std::string("Persephone")); // 传递右值std::string
logAndAdd("Patty Dog"); // 传递右值std::string
在第一个调用中,logAndAdd
的形参绑定到了变量petName
。在logAndAdd
内部,name
被传递给了names.emplace
,由于name
是个左值,所以是被复制进names
的。
第2个和第3个调用name
绑定到一个右值,由于name
是个左值,所以是被复制进names
的;我们可以改写logAndAdd
让它接受一个万能引用:
template<typename T>
void logAndAdd(T &&name) {
auto now = std::chrono::system_clock::now();
log(now, "logAndAdd");
names.emplace(std::forward<T>(name));
}
std::string petName("Darla");
logAndAdd(petName); // 传递左值std::string,將左值复制进multiset
logAndAdd(std::string("Persephone")); // 对右值进行移动
logAndAdd("Patty Dog"); // 在multiset中直接构造一个std::string对象,而非复制一个st::string类型的临时对象
}
在考虑如下情景,我们增加一个logAndAdd
的重载版本:
typename<typename T>
void logAndAdd(T &&name) {
auto now = std::chrono::system_clock::now();
log(now, "logAndAdd");
names.emplace(std::forward<T>(name));
}
std::string nameFromIdx(int dx);
void logAndAdd(int dx) {
auto now = std::chrono::system_clock::now();
log(now, "logAndAdd");
names.emplace(nameFromIdx(idx)); // 將名字添加到全局数据结构中
}
std::string petName("Darla");
logAndAdd(petName); // 下面三个掉用都调用了形参为T&&的重载版本
logAndAdd(std::string("Persephone"));
logAndAdd("Patty Dog");
logAndAdd(22); // 调用了形参类型为int的版本
但是如果客户产生了如下调用,会无法编译
short nameIndx;
logAndAdd(nameIdx); // !错误
logAndAdd
有个两个重载版本,,形参为T&&
的版本將T
推导为short
,从而产生一个精确匹配。而形参为int
的版本却只能在类型提升后才能匹配到short
形参,但是names
无法添加short
类型的元素
填上这个坑一个办法是撰写一个带有完美转发的构造函数。
class Person{
public:
template<typename T>
explicit Person(T &&n) :name(std::forward<T>(n)){}
explicit Person(int idx) : name(nameFromIdx(idx)){}
Person(const Person&& rhs); // 编译器生成拷贝构造函数
Person(Person &&rhs); // 编译器生成移动构造函数
private:
std::string name;
};
C++会默认生成拷贝构造和移动构造函数,如果用户产生如下调用:
Person p("Nancy");
auto cloneOfP(p);
上面代码没有调用拷贝构造函数,而是调用了完美转发构造函数。该函数在尝试从一个Person
类型的对象p出发来初始化另外一个Person
类型的对象的std::string
类型的数据成员。所以编译失败。
完美转发构造函数如果扯上集成后会变得更加复杂:
class SpecialPerson : public Person
{
public:
SpecialPerson(const SpecialPerson &rhs) : Person(rhs) {} //拷贝构造函数,调用的是基类的完美构造函数
SpecialPerson(SpecialPerson &&rhs) : Person(std::move(rhs)){} //移动构造函数,调用的是基类的完美构造函数
};
最终代码没有通过编译,因为std::string
没有接受SpecialPerson
类型的形参