C++ std::forward<T> 的使用
C++ std::forward 的使用
C++真实一门细节比较多的语言,稍不注意就会出现奇怪请琢磨不透的bug,这时候就说明你的C++基础不扎实。
C++ lvalue rvalue
std::string hello = "Hello World";
顾名思义左值就是等号左边的hello, 右值就是等号右边的字符串Hello World;
观察一下左值指向了右值,左值指向的右值是可以替换的。左值代表一个具体的存储位置,右值代表一个临时的值.
左值右值的重载函数
class Person {
private:
std::string name;
public:
// 构造函数
Person(const std::string& name) : name(name) {
std::cout << "Person constructed: " << name << std::endl;
}
// 析构函数
~Person() {
std::cout << "Person destructed: " << name << std::endl;
}
// 拷贝构造函数
Person(const Person& other) : name(other.name) {
std::cout << "Person copy-constructed: " << name << std::endl;
}
// 移动构造函数
Person(Person&& other) noexcept : name(std::move(other.name)) {
std::cout << "Person move-constructed: " << name << std::endl;
}
// 赋值运算符
Person& operator=(const Person& other) {
if (this != &other) {
name = other.name;
std::cout << "Person copy-assigned: " << name << std::endl;
}
return *this;
}
// 移动赋值运算符
Person& operator=(Person&& other) noexcept {
if (this != &other) {
name = std::move(other.name);
std::cout << "Person move-assigned: " << name << std::endl;
}
return *this;
}
const std::string& getName() const { return name; }
void setName(const std::string& newName) { name = newName; }
friend std::ostream& operator<<(std::ostream& os, const Person& person) {
os << "Person(Name: " << person.name << ")";
return os;
}
};
void consume(Person& p){
std::cout << "consume(Person&): " << p << " (lvalue overload)\n";
}
void consume(const Person& p){
std::cout << "consume(const Person&): " << p << " (lvalue overload)\n";
}
void consume(Person&& p) {
std::cout << "consume(Person&&): " << p << " (rvalue overload)\n";
}
void consume(const Person&& p) {
std::cout << "consume(const Person&&): " << p << " (rvalue overload)\n";
}
存在普通重载函数是不能存在左值、右值重载函数的。即:
void consume(Person p){
std::cout << "consume(Person): " << p << " (lvalue overload)\n";
}
触发重载函数:
Person alice("Alice");
consume(alice);
// consume(std::string("Alice")); // 1
代码1处使用到了隐式转换, 如果需要禁止使用explicit修饰构造函数. 当然隐式转换只能一次。比如const char*-> std::string -> Person就不支持。
可以尝试删除上面四个重载函数,什么时候继续会执行,什么时候会编译报错。
函数模板
最简单的函数模板
template<typename T>
T add(T a, T b){
return a + b;
}
使用C++20 Concepts
加一些限制
template<typename T>
requires requires(T a, T b) { { a + b } -> std::convertible_to<T>; }
T add(T a, T b){
return a + b;
}
struct Point {
int x;
int y;
friend std::ostream& operator<<(std::ostream& os, const Point& point){
os << "Point(" << point.x << ", " << point.y << ")";
return os;
}
Point operator+(const Point& other) const {
return Point{x + other.x, y + other.y};
}
};
void learn001() {
int a = 1;
int b = 2;
Point p1{1, 2};
Point p2{3, 4};
std::cout << add(a, b) << "\n";
std::cout << add(p1, p2) << "\n";
}
当然也可以使用C++11/C++14 的 SFINAE,当然其较为复杂。
TODO: 出一篇简单解释
C++20 Concepts和C++11/C++14 的 SFINAE的博客
requires requires(T a, T b) {
{ a + b } -> std::convertible_to<T>;
}
{ a + b } -> std::convertible_to<T>;是一个lambda.
a + b: 检查是否可以进行相加操作;
std::convertible_to<T>: 表示相加的结果是否可以隐式转换为T
std::forward<T>的作用
std::forward<T>的源码:
template <typename T>
T&& forward(typename std::remove_reference<T>::type& t) noexcept {
return static_cast<T&&>(t);
}
class Person {
private:
std::string name;
public:
// 构造函数
Person(const std::string& name) : name(name) {
std::cout << "Person constructed: " << name << std::endl;
}
// 析构函数
~Person() {
std::cout << "Person destructed: " << name << std::endl;
}
// 拷贝构造函数
Person(const Person& other) : name(other.name) {
std::cout << "Person copy-constructed: " << name << std::endl;
}
// 移动构造函数
Person(Person&& other) noexcept : name(std::move(other.name)) {
std::cout << "Person move-constructed: " << name << std::endl;
}
// 赋值运算符
Person& operator=(const Person& other) {
if (this != &other) {
name = other.name;
std::cout << "Person copy-assigned: " << name << std::endl;
}
return *this;
}
// 移动赋值运算符
Person& operator=(Person&& other) noexcept {
if (this != &other) {
name = std::move(other.name);
std::cout << "Person move-assigned: " << name << std::endl;
}
return *this;
}
const std::string& getName() const { return name; }
void setName(const std::string& newName) { name = newName; }
friend std::ostream& operator<<(std::ostream& os, const Person& person) {
os << "Person(Name: " << person.name << ")";
return os;
}
};
void consume(Person& p){
std::cout << "consume(Person&): " << p << " (lvalue overload)\n";
}
void consume(Person&& p) {
std::cout << "consume(Person&&): " << p << " (rvalue overload)\n";
}
template<typename T>
void wrapper_forward(T&& arg) { // arg is a universal reference (万能引用) 虽然类型是右值类型,但是arg是一个左值
std::cout << "wrapper_forward(T&&): called\n";
// consume(std::forward<T>(arg)); // 1
// consume(std::forward<T&>(arg)); // error
// consume(std::forward<T&&>(arg)); // error
consume(arg);
}
template<typename T>
void wrapper_forward(T& arg) {
std::cout << "wrapper_forward(T&): called\n";
// consume(arg);
consume(std::forward<T>(arg)); // 2
// consume(std::forward<T&>(arg)); // error
// consume(std::forward<T&&>(arg)); // error
}
void learn001() {
Person alice("alice");
wrapper_forward(alice); // 3
}
试图触发wrapper_forward重载函数, 并试着注释或者打开代码1、2,观察结果。
我解释一下执行过程:
传入左值alice,调用重载函数wrapper_forward(T&), T被推断为Person,所以代码2处
consume(std::forward<T>(arg));为consume(std::forward<Person>(arg));
而arg类型是Person&。
带入std::forward<T>源码如下:
Person&& forward(Person& t) noexcept {
return static_cast<Person&&>(t);
}
折叠规律(万能引用):
| 折叠 | 结果 |
|---|---|
| T& && | T& |
举个例子:
void consume(Person& p){
std::cout << "consume(Person&): " << p << " (lvalue overload)\n";
}
void consume(Person&& p) {
std::cout << "consume(Person&&): " << p << " (rvalue overload)\n";
}
template<typename T>
void wrapper_forward(T&& arg) { // arg is a universal reference (万能引用) 虽然类型是右值类型,但是arg是一个左值
std::cout << "wrapper_forward(T&&): called\n";
consume(std::forward<T>(arg));
// consume(std::forward<T&>(arg)); // error
// consume(std::forward<T&&>(arg)); // error
// consume(arg);
}
template <typename T>
T&& forward(typename std::remove_reference<T>::type& t) noexcept {
return static_cast<T&&>(t);
}
void learn001() {
Person alice("alice");
wrapper_forward(alice); // 1
wrapper_forward(std::move(alice)); // 2
}
T被推断为Person&,所以wrapper_forward(Person& &&),根据折叠规律结果为Person&, 之后 强转为Person&;T被推断为Person&&,所以wrapper_forward(Person&& &&),根据折叠规律结果为Person&&, 之后 强转为Person&&;
代码验证
template<typename T>
void wrapper_forward(T&& arg) {
std::cout << "wrapper_forward(T&): called\n";
std::cout << "T is Person? " << std::is_same_v<T, Person> << "\n";
std::cout << "T is Person&? " << std::is_same_v<T, Person&> << "\n";
std::cout << "T is Person&&? " << std::is_same_v<T, Person&&> << "\n";
std::cout << "arg type is Person? " << std::is_same_v<decltype(arg), Person> << "\n";
std::cout << "arg type is Person&? " << std::is_same_v<decltype(arg), Person&> << "\n";
std::cout << "arg type is Person&&? " << std::is_same_v<decltype(arg), Person&&> << "\n";
std::cout << "param type: " << (std::is_lvalue_reference_v<decltype(arg)> ? "lvalue ref" : "rvalue ref") << "\n";
consume(std::forward<T>(arg));
// consume(std::forward<T&>(arg)); // error
// consume(std::forward<T&&>(arg)); // error
// consume(arg);
}
template <typename T>
T&& forward(typename std::remove_reference<T>::type& t) noexcept {
return static_cast<T&&>(t);
}
void learn001() {
Person alice("alice");
wrapper_forward(std::move(alice));
}

C++ std::forward
浙公网安备 33010602011771号