std::forward完美转发

完美转发概念

参考:《深入理解C++11:新特性解析与应用》
所谓完美转发,指的是在函数模板中,完全依照模板的参数类型,将参数传递给函数模板中调用的另外一个函数。比如:

template <typename T>
void IamForwording(T t) 
{ 
    IrunCodeActually(t); 
}

上面的 IamForwording 是一个转发函数模板,而函数 IrunCodeActually 则是真正执行代码的目标函数。
从函数 IrunCodeActually 的角度而言,总是希望转发函数将参数按照传入 IamForwording 时的类型传递(即传入 IamForwording 是左值对象, IrunCodeActually 就能获得左值对象,右值对象也是如此),而不产生额外的开销,就好像转发者不存在一样。
看起来似乎很容易,但是实际却不简单。上面例子中,IamForwording 的参数中使用了最基本类型进行转发,该方法会导致参数在传给 IrunCodeActually 之前就产生了一次额外的临时对象拷贝。所以这样的转发只能说是正确的转发,但谈不上完美。
通常程序员需要的是一个引用类型,引用类型不会有拷贝的开销,其次,则需要考虑转发函数对类型的接受能力。因为目标函数可能需要能够即接受左值引用,又接受右值引用。那么如果转发函数只能接受其中的一部分,我们也无法做到完美转发。
我们很容易想到“万能”的常量左值类型。不过以常量左值为参数的转发函数却会遇到一些尴尬,比如:

void IrunCodeActually(int t) {}
template <typename T>
void IamForwording(const T& t)
{
    IrunCodeActually(t);
}

由于目标函数的参数类型是非常量左值引用类型,因此无法接受常量左值引用作为参数。虽然转发函数的接受能力很高,但在目标函数的接受上却出了问题。
我们可能需要通过一些常量和非常量的重载来解决目标函数的接受问题。但这在函数参数比较多的情况下,就会造成代码的冗余。如果我们的目标函数的参数是个右值引用,则同样无法接受任何左值类型作为参数,间接的,也就导致无法使用移动语义。

C++11 中的完美转发

在 C++ 中是如何解决完美转发的问题呢?实际上,C++11 通过引入一条所谓的“引用折叠”的新语言规则,并结合新的模板推倒规则来完成完美转发。
在 C++11 之前,如下面的句子:

typedef const int T;
typedef T& TR;
TR& v = 1;  //该声明在C++98中会导致编译错误

其中 TR& v = 1 这样的表达式会被编译器认为是不合法的表达式,而在 C++11 中,一旦出现了这样的表达式,就会发生引用折叠,即将复杂的未知表达式折叠为已知的简单表达式,折叠规则如下:

TR 的类型定义声明 v 的类型v 的实际类型
T&TRA&
T&TR&A&
T&TR&&A&
T&&TRA&&
T&&TR&A&
T&&TR&&A&&

可以看出一个规则:一旦定义中出现了左值引用,引用折叠总是优先将其折叠为左值引用。
而模板对类型的推倒规则就比较简单,当转发函数的实参是类型 X 的一个左值引用,那么模板参数被推倒为 X& 类型;而转发函数的实参是类型 X 的一个右值引用的话,那么模板的参数将被推导为 X&& 类型,结合上面的引用折叠规则,就能确定出参数的实际类型。进一步,就可以把转发函数写成如下形式:

void IamForwording(X& && T)
{
    IrunCodeActually(static_case<X& &&>(t));
}

应用之前的引用折叠规则,就是:

void IamForwording(X& t)
{
    IrunCodeActually(static_case<X&>(t))
}

这样一来,我们的左值传递就毫无问题了。
实际使用的时候,IrunCodeActually 如果接受左值引用的话,就可以直接调用转发函数。不过你可能发现调用前的 static_cast 没有什么作用,实际上,这里的 static_cast是留给传递右值用的。
如果调用转发函数时传入了一个 X 类型的右值引用,转发函数将被实例化为:

void IamForwording(X&& && t)
{
    IrunCodeActually(static_cast<X&& &&>(t))
}

应用上面的引用折叠规则,就是:

void IamForwording(X&& t)
{
    IrunCodeActually(static_cast<X&&>(t))
}

我们看到了 static_cast 的重要性。对于一个右值而言,当它使用右值引用表达式引用的时候,该右值引用却是个不折不扣的左值,那么想在函数调用中继续传递右值,就需要使用 std::move 来进行右值的转换。而 std::move 通常就是一个 static_cast 。
不过在 C++11 中,用于完美转发的函数却不再叫 move,而是另外一个名字:forward。所以就可以把转发函数写成这样:

template <typename T>
void IamForwording(T&& t)
{
    IrunCodeActually(std::forward(t));
}

std::move 和 std::forward 在实际上的差别并不大。不过标准库这么设计,也许是为了让每个名字对应不同的用途,以应对未来可能的拓展(虽然现在使用 move 可能也能通过完美转发函数的编译,但并不推荐)。
接下来看一个 std::forward 完美转发的例子:

#include <iostream>

void RunCode(int &&m) { std::cout << "RValue Ref" << std::endl; }
void RunCode(int &m) { std::cout << "LValue Ref" << std::endl; }
void RunCode(const int &&m) { std::cout << "Const RValue Ref" << std::endl; }
void RunCode(const int &m) { std::cout << "Const LValue Ref" << std::endl; }

template <typename T>
void PerfectForward(T &&t)
{
    RunCode(std::forward<T>(t));
}

int main(int argc, char **argv)
{
    int a;
    int b;
    const int c = 1;
    const int d = 0;

    PerfectForward(a);
    PerfectForward(std::move(b));
    PerfectForward(c);
    PerfectForward(std::move(d));
    return 0;
}

运行结果:

LValue Ref
RValue Ref
Const LValue Ref
Const RValue Ref

可以看到所有的 4 种类型的值对完美转发进行测试,所有的转发都被正确的送到了目的地。
完美转发的一个作用就是做包装函数,这是一个很方便的功能。对代码中的完美转发函数稍作修改,就可以用很少的代码记录单参数函数的参数传递状况,代码如下:

#include <iostream>

template <typename T, typename U>
void PerfectForward(T &&t, U &Func)
{
    std::cout << t << "\tforwarded..." << std::endl;
    Func(std::forward<T>(t));
}

void RunCode(double &&m) {}
void RunHome(double &&h) {}
void RunComp(double &&c) {}

int main(int argc, char **argv)
{
    PerfectForward(1.5, RunComp);
    PerfectForward(8, RunCode);
    PerfectForward(1.5, RunHome);
}

运行结果:

1.5     forwarded...
8       forwarded...
1.5     forwarded...

这只是个简单的例子,可以尝试变得更复杂,以更加符合实际需求。事实上,在 C++11 标准库中,我们可以看到大量的完美转发的实际应用,一些小巧好用的函数,比如:make_pair、make_unique 等在 C++11 中都通过完美转发实现了。这样一来,就减少了一些函数版本的重复(const 和非 const 版本的重复),并能够充分利用移动语义。无论从运行性能的提高还是从代码编写的简化上,完美转发都堪称完美。

### C++ 中右值引用和完美转发 (`std::forward`) 的用法及原理 #### 一、右值引用的概念 右值引用是 C++11 引入的一种新特性,它允许绑定到即将消亡的对象(即右值)。这种设计的主要目的是为了支持移动语义,从而避免不必要的深拷贝操作。 **基本语法:** ```cpp Type&& rvalue_ref = ...; ``` - **右值引用的特点:** - 右值引用可以绑定到临时对象或显式转换为右值的左值。 - 如果一个右值引用具有名称,则它会被视为左值[^2]。 **示例:** ```cpp #include <iostream> #include <memory> class LargeObject { public: LargeObject() { std::cout << "Constructor called\n"; } ~LargeObject() { std::cout << "Destructor called\n"; } LargeObject(const LargeObject&) { std::cout << "Copy Constructor called\n"; } LargeObject(LargeObject&&) noexcept { std::cout << "Move Constructor called\n"; } }; std::unique_ptr<LargeObject> createLargeObject() { return std::make_unique<LargeObject>(); } int main() { auto obj = createLargeObject(); // 移动语义生效,调用 Move 构造函数 } ``` 在这个例子中,`createLargeObject()` 返回了一个右值引用类型的 `std::unique_ptr` 对象。由于 `std::unique_ptr` 不可复制,只有通过移动语义才能完成赋值操作[^3]。 --- #### 二、`std::move` 的作用 `std::move` 是标准库提供的一组工具之一,它的主要职责是将左值强制转化为右值,以便触发移动语义。 **工作原理:** - `std::move` 实际上是一个类型转换函数,返回的是一个右值引用。 - 它不会真正“移动”任何东西,而是告诉编译器当前对象可以被视为右值处理。 **示例:** ```cpp #include <iostream> #include <utility> #include <string> void moveExample(std::string&& str) { std::cout << "String moved: " << str << '\n'; } int main() { std::string s = "Hello"; moveExample(std::move(s)); // 显式将左值转为右值 std::cout << "After moving: '" << s << "'\n"; // 结果可能为空字符串 } ``` 在这里,`std::move(s)` 将原本的左值 `s` 转换成了右值,使得 `moveExample` 函数接收到了一个右值引用参数[^4]。 --- #### 三、完美转发的概念与实现 完美转发是指在模板编程中保持原始实参的类别不变(无论是左值还是右值),并将其实参与目标函数完全一致地传递下去的技术。 **核心组件:** - 使用 `std::forward<T>` 来保留实参的具体类型信息。 - 配合万能引用(Universal Reference)来匹配任意类型的输入。 **实现细节:** - 当模板形参被声明为 `T&&` 形式时,在某些上下文中它可以表示左值引用或者右值引用。 - 此时可以通过 `std::forward<T>(arg)` 动态调整实际转发的形式。 **示例:** ```cpp #include <iostream> #include <utility> template<typename T> void forwardExample(T&& arg) { static_cast<void>(std::forward<T>(arg)); using DecayType = typename std::decay<T>::type; if constexpr (std::is_lvalue_reference_v<T>) { std::cout << "Forwarded as lvalue reference.\n"; } else { std::cout << "Forwarded as rvalue reference.\n"; } } int main() { int x = 42; forwardExample(x); // 左值转发 forwardExample(42); // 右值转发 } ``` 上述代码展示了如何利用 `std::forward` 达成不同类型间的精确映射[^4]。 --- #### 四、总结对比 | 特性 | 描述 | |-----------------|----------------------------------------------------------------------| | **右值引用** | 绑定至即将销毁的对象,主要用于实现高效的数据转移 | | **`std::move`** | 把左值转变为右值,激活移动构造/赋值 | | **完美转发** | 确保模板内部能够无损地向下游函数传输原始参数的实际身份 | 这些技术共同构成了现代 C++ 中高效的资源管理框架。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

code_peak

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值