前置知识: C++

完美转发与引用折叠

00:00
3 min Advanced 2026/6/14

C++完美转发与引用折叠详解:std::forward与转发引用。

概述

完美转发(Perfect Forwarding)是 C++11 引入的重要机制,允许函数模板将参数的值类别(左值或右值)原封不动地传递给另一个函数。这一机制依赖于两个核心概念:引用折叠(Reference Collapsing)和 std::forward。完美转发是现代 C++ 中工厂函数、包装器和泛型代码的基础,std::make_uniquestd::make_sharedstd::vector::emplace_back 等都依赖它实现零开销的参数传递。

基础概念

引用折叠规则

模板实例化导致引用的引用出现时,编译器通过引用折叠规则确定最终类型:

T 的类型T&& 的结果说明
U&U&左值引用与右值引用折叠为左值引用
U&&U&&两个右值引用折叠为右值引用
const U&const U&const影响折叠
const U&&const U&&const影响折叠

规则:只有两个右值引用折叠为右值引用其他所有组合折叠左值引用。

转发引用(Forwarding Reference)

转发引用模板参数T&&特殊形式,它与右值引用不同:

// 转发引用:T&& 在模板参数推导上下文中
template<typename T>
void wrapper(T&& arg) {
    // 当传入左值时,T 推导为 int&,T&& 折叠为 int&
    // 当传入右值时,T 推导为 int,T&& 保持为 int&&
}

// 右值引用:非推导上下文中的 &&
void func(int&& arg) {
    // arg 只能绑定到右值,不是转发引用
}

快速上手

std::forward 基本用法

#include <utility>
#include <iostream>

void target(int& x) { std::cout << "左值版本" << std::endl; }
void target(int&& x) { std::cout << "右值版本" << std::endl; }

template<typename T>
void wrapper(T&& arg) {
    target(std::forward<T>(arg));
}

int main() {
    int x = 42;
    wrapper(x);   // 传入左值,调用 target(int&),输出"左值版本"
    wrapper(42);  // 传入右值,调用 target(int&&),输出"右值版本"
    return 0;
}

std::forward 的工作原理

// std::forward 的简化实现
template<typename T>
T&& forward(std::remove_reference_t<T>& t) noexcept {
    return static_cast<T&&>(t);
}

// 当 T = int& 时(传入左值):
// static_cast<int& &&>(t) → 引用折叠 → int&(左值引用)

// 当 T = int 时(传入右值):
// static_cast<int&&>(t) → 右值引用

详细用法

完美转发多个参数

#include <utility>
#include <memory>
#include <string>

// 完美转发多个参数到构造函数
template<typename T, typename... Args>
std::unique_ptr<T> makeUnique(Args&&... args) {
    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

class Widget {
    std::string name_;
    int id_;
public:
    Widget(std::string name, int id) : name_(std::move(name)), id_(id) {}
};

// 使用
auto w1 = makeUnique<Widget>("hello", 1);  // 字符串字面量被完美转发
std::string name = "world";
auto w2 = makeUnique<Widget>(std::move(name), 2);  // 右值被完美转发

完美转发与类成员初始化

#include <utility>
#include <vector>

class Container {
    std::vector<int> data_;
public:
    // 完美转发构造函数参数
    template<typename... Args>
    void emplace(Args&&... args) {
        data_.emplace_back(std::forward<Args>(args)...);
    }
};

// 使用
Container c;
c.emplace(42);                    // 直接构造 int
c.emplace();                      // 默认构造 int(值为0)

完美转发返回值

#include <utility>

// 完美转发返回值,保持值类别
template<typename T>
auto wrapCall(T&& callable) {
    // 转发可调用对象及其返回值
    return std::forward<T>(callable)();
}

int getValue() { return 42; }
int& getRef() { static int x = 10; return x; }

auto r1 = wrapCall(getValue);  // 返回 int
auto r2 = wrapCall(getRef);    // 返回 int&(引用)

常见场景

通用包装器

#include <utility>
#include <functional>
#include <chrono>
#include <iostream>

// 计时包装器:完美转发参数并测量执行时间
template<typename Func, typename... Args>
auto timedCall(Func&& func, Args&&... args) {
    auto start = std::chrono::steady_clock::now();
    auto result = std::forward<Func>(func)(std::forward<Args>(args)...);
    auto end = std::chrono::steady_clock::now();
    auto ms = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
    std::cout << "耗时: " << ms.count() << "us" << std::endl;
    return result;
}

工厂函数

#include <memory>
#include <utility>

// 通用工厂函数,完美转发所有参数
template<typename Base, typename Derived, typename... Args>
std::unique_ptr<Base> create(Args&&... args) {
    return std::make_unique<Derived>(std::forward<Args>(args)...);
}

// 使用
class Shape { public: virtual ~Shape() = default; };
class Circle : public Shape {
    double radius_;
public:
    explicit Circle(double r) : radius_(r) {}
};

auto shape = create<Shape, Circle>(5.0);

注意事项

  • std::forward 必须与转发引用T&&)配合使用,普通右值引用使用 std::forward错误
  • std::forward<T> 中的 T 是从模板参数推导得到的,不是参数类型
  • 每个参数只能被 std::forward 一次,转发后原参数不应再使用
  • std::forwardstd::move 的区别:forward 类型条件转发,move条件转为右值
  • 非 forwarding reference 上下文中的 T&&右值引用,不参与引用折叠
  • auto&& 也可以是转发引用,如 auto&& x = expr;据 expr 的值类别推导

进阶用法

完美转发与 Lambda

// 在 Lambda 中使用完美转发
template<typename... Args>
auto makeCallback(Args&&... args) {
    return [... captures = std::forward<Args>(args)]() mutable {
        // C++20: 使用初始化捕获完美转发参数包
        return (captures + ... + 0);
    };
}

std::invoke 与完美转发

#include <functional>
#include <utility>

// 完美转发调用成员函数
template<typename Object, typename MemberFunc, typename... Args>
auto invokeMember(Object&& obj, MemberFunc func, Args&&... args) {
    return std::invoke(func,
        std::forward<Object>(obj),
        std::forward<Args>(args)...);
}

class Processor {
public:
    int compute(int x, int y) { return x + y; }
};

Processor p;
auto result = invokeMember(p, &Processor::compute, 10, 20);  // 30

完美转发的限制与替代

// 完美转发不能转发花括号初始化列表
// template<typename T> void makeVec(T&& arg) { std::vector<int> v(std::forward<T>(arg)); }
// makeVec({1, 2, 3});  // 错误:无法推导花括号列表

// 解决方案:使用单独的函数或 initializer_list
template<typename T>
void makeVec(std::initializer_list<T> init) {
    std::vector<T> v(init);
}

makeVec({1, 2, 3});  // 正确

知识检测

学习进度

-- 已学文档
--% 知识覆盖率

学习推荐

专注模式