1. T&&
的两种不同含义
T&&
有两种不同的含义:
- 一种含义是右值引用;
- 另一种含义则表示既可以是右值引用,也可以是左值引用。
void f(Widget&& param); // 不涉及类型推导,param是个右值引用
Widget&& var1 = Widget(); // 不涉及类型推导,右值引用
auto&& var2 = var1; // 非右值引用,实际上var2是万能引用
template<typename T>
void f(std::vector<T>&& param); // 右值引用
template<typename T>
void f(T&& param); // 万能引用
Widget w;
f(w); // 左值被传递给f,param的类型是Wiget&,即左值引用
f(std::move(w)); // 右值被传递给f,param的类型是Wiget&&,即右值引用
2. “类型推导+T&&
的形式”才是万能引用
若要使一个引用成为万能引用,其涉及类型推导是必要条件,但还不是充分条件。引用声明的形式也必须正确无误,并且该形式被限定的很死:必须得正好形如“T&&
”。所以万能引用的条件是在调用函数的时候有类型推导 + 形式是T&&
。
template<class T, class Allocator = allocator<T>> // still from
class vector { // C++
public: // Standards
template <class... Args>
void emplace_back(Args&&... args); // Args跟vector的类型T无关,
// 每次调用emplace_back都需要类型推导因此args是万能引用
};
并不是模板内出现T&&
就是universal reference。如下例,在调用push_back()
的时候没有type deduction类型推导。
template<class T, class Allocator = allocator<T>> // from C++
class vector // Standards
{
public:
void push_back(T&& x); // 因为在调用此函数的时候vector已经实例化了,
// 所以T的类型已经明确了,在调用函数的时候就不涉及类型推导,
// 因此此处只是右值引用,不是万能引用
};
即使是一个const修饰的存在也会剥夺一个引用成为万能引用的资格。
template<typename T> void f(const T&& param); // param是个右值引用,不是万能引用
非完美转发示例:
#include <iostream>
namespace test_imperfect_forward
{
void process(int &i)
{
std::cout << "process(int&), " << i << std::endl;
}
void process(int &&i)
{
std::cout << "process(int&&), " << i << std::endl;
}
void forwarding(int &&i)
{
std::cout << "forwarding(int&&), " << i << ", ";
process(i);
}
void forwarding2(int &&i)
{
std::cout << "forwarding2(int&&), " << i << ", ";
process(std::forward<int>(i));
}
auto main() -> int
{
std::cout << "Testing imperfect_forward......" << std::endl;
int a = 0;
process(a); // process(int&), 0
process(2); // process(int&&), 2
process(std::move(a)); // process(int&&), 0
forwarding(3); // forwarding(int&&), 3, process(int&), 3
forwarding(std::move(a)); // forwarding(int&&), 0, process(int&), 0
forwarding2(4); // forwarding2(int&&), 4, process(int&&), 4
return 0;
}
}
完美转发(万能引用)示例:
namespace test_perfect_forward
{
void process(int &i)
{
std::cout << "process(int&), " << i << std::endl;
}
void process(int &&i)
{
std::cout << "process(int&&), " << i << std::endl;
}
// univesal reference(perfect forward)
template<typename T>
void forwarding(T&& i)
{
std::cout << "forwarding(T&&i), " << i << ", ";
process(std::forward<T>(i));
}
auto main() -> int
{
std::cout << "Testing perfect_forward......" << std::endl;
int a = 0;
process(a); // process(int&), 0
process(2); // process(int&&), 2
process(std::move(a)); // process(int&&), 0
forwarding(3); // forwarding(T&&i), 3, process(int&&), 3
forwarding(std::move(a)); // forwarding(T&&i), 0, process(int&&), 0
forwarding(a); // forwarding(T&&i), 0, process(int&), 0
return 0;
}
}
当转发右值给其它函数时,应当使用std::move
,而当转发万能引用时,应当使用std::forward
。
class Widget
{
public:
template<typename T>
void setName(T&& newName) // newName is
{ name = std::forward<T>(newName); } // universal reference
};
3. 右值引用使用std::move
,万能引用使用std::forward
#include <iostream>
#include <string>
#include <memory>
namespace test_move
{
class Wiget0
{
public:
Wiget0() = default;
// 右值引用
Wiget0(Wiget0&& rhs):name_(std::move(rhs.name_)), p_(std::move(rhs.p_))
{std::cout << "copy-ctor called." << std::endl;}
private:
std::string name_;
std::shared_ptr<int> p_;
};
class Wiget1
{
public:
// 万能引用
template<typename T>
void setName(T&& newname){
name_ = std::forward<T>(newname);
}
void print(){
std::cout << name_ << std::endl;
}
private:
std::string name_;
std::shared_ptr<int> p_;
};
class Wiget2
{
public:
// 本应是万能引用的形式,却使用了move,!!!典型的错误用法!!!
template<typename T>
void setName(T&& newname){
name_ = std::move(newname);
}
private:
std::string name_;
std::shared_ptr<int> p_;
};
auto main() -> int
{
std::cout << "Testing move......" << std::endl;
Wiget0 w00;
Wiget0 w01(std::move(w00));
Wiget1 w10;
w10.setName("567");
w10.print();
return 0;
}
}
Reference
Scott Meyers, Effective Modern C++,