目录 标签 归档 RSS
- 目录 -
C++11新特性--右值引用与移动语义
C++11新特性--右值引用与移动语义

简介

C++11 引入的 右值引用(rvalue reference)移动语义(move semantics) 是现代 C++ 的核心特性之一,它们的主要目的是 提升程序性能,避免不必要的拷贝开销,尤其是在涉及大对象或资源管理时。


一、左值与右值基础

在讲右值引用之前,先来区分左值(lvalue)和右值(rvalue):

类型描述
左值(lvalue)有名字的、可取地址的对象,生命周期超过表达式。比如变量 int a = 10; 中的 a
右值(rvalue)临时对象、字面量等,不可取地址,生命周期较短。比如 10a + bstd::string("temp")

示例:

1int a = 10;
2int b = a;         // a 是左值
3int c = a + 1;     // a + 1 是右值

二、右值引用(rvalue reference)

语法:

1T&& var;

这里 && 是右值引用。它只能绑定到右值

示例:

1int a = 10;
2int&& r = 20;   // OK,20 是右值
3int&& s = a;    // 错误,a 是左值,不能绑定到右值引用

特点:

  • 可以“窃取”右值的资源(而不是深拷贝),从而提高性能。

  • 引出移动构造函数和移动赋值运算符。


三、移动语义(Move Semantics)

问题背景:

在 C++03 中,如果你返回一个大型对象,往往涉及昂贵的拷贝开销:

1std::vector<int> makeVec() {
2    std::vector<int> v(1000, 42); // 初始化 1000 个元素
3    return v;                     // C++03 会调用拷贝构造函数
4}

C++11 中引入 移动构造函数移动赋值运算符 来解决这个问题。


四、移动构造函数 & 移动赋值运算符

定义:

1class MyClass {
2public:
3    MyClass(MyClass&& other);             // 移动构造函数
4    MyClass& operator=(MyClass&& other);  // 移动赋值运算符
5};

特点:

  • 参数类型是 T&&

  • 实现中窃取资源,并让源对象处于一个“可析构”的空状态。

示例:

 1class MyString {
 2private:
 3    char* data;
 4public:
 5    MyString(const char* str) {
 6        data = new char[strlen(str)+1];
 7        strcpy(data, str);
 8    }
 9
10    // 移动构造函数
11    MyString(MyString&& other) noexcept {
12        data = other.data;
13        other.data = nullptr;
14    }
15
16    ~MyString() {
17        delete[] data;
18    }
19};

这样,在返回临时 MyString 时,就不会再调用拷贝构造,而是直接移动其内部指针。


五、std::move 与移动语义激活

什么是 std::move

它并不移动,而是一个 类型转换工具,将左值强制转换为右值引用,从而激活移动语义。

示例:

1MyString a = "Hello";
2MyString b = std::move(a); // a 变为右值,调用移动构造

此时 b 接管了 a 的内部资源,而 a 成为空状态。


六、标准容器中的应用

C++11 STL 容器(如 std::vectorstd::map)都支持移动语义。

示例:

1std::vector<std::string> v;
2std::string s = "hello";
3v.push_back(std::move(s));  // 避免拷贝,提高效率

七、Rule of Five(五法则)

在 C++11 中,如果你的类涉及资源管理(如裸指针),你可能需要实现以下五个函数:

  • 析构函数

  • 拷贝构造函数

  • 拷贝赋值运算符

  • 移动构造函数

  • 移动赋值运算符

否则资源转移或释放可能会出错。


八、完美转发与泛型代码

在模板中,右值引用还可以配合 std::forward 实现 完美转发,但这里涉及引用折叠和万能引用(T&&)的内容,这是右值引用的进阶应用。


九、总结

概念描述
右值引用 T&&一种只能绑定到右值的引用,用于移动语义
移动构造函数从临时对象“窃取资源”的构造函数,避免拷贝
std::move将左值转换为右值引用,触发移动语义
性能提升点尤其适用于大对象、动态分配资源类、容器操作等场景

例程

接下来我们手写一个类 MyString,它内部维护一个动态分配的 C 字符串,我们将:

  1. ✅ 实现构造函数、析构函数

  2. ✅ 实现拷贝构造、拷贝赋值(传统语义)

  3. ✅ 实现移动构造、移动赋值(现代语义)

  4. ✅ 编写测试用例对比两者性能和行为


🔧 第一步:MyString 类的基础结构(构造 / 析构)

 1#include <iostream>
 2#include <cstring>
 3
 4class MyString {
 5private:
 6    char* data;
 7
 8public:
 9    // 默认构造
10    MyString() : data(nullptr) {}
11
12    // 构造函数(从 const char* 初始化)
13    MyString(const char* str) {
14        std::cout << "[Ctor] Constructing from C-string\n";
15        data = new char[strlen(str) + 1];
16        strcpy(data, str);
17    }
18
19    // 析构函数
20    ~MyString() {
21        std::cout << "[Dtor] Destructing\n";
22        delete[] data;
23    }
24
25    void print() const {
26        std::cout << "Content: " << (data ? data : "null") << "\n";
27    }
28};

📋 第二步:添加拷贝构造 & 拷贝赋值

 1    // 拷贝构造函数
 2    MyString(const MyString& other) {
 3        std::cout << "[CopyCtor] Deep copy\n";
 4        if (other.data) {
 5            data = new char[strlen(other.data) + 1];
 6            strcpy(data, other.data);
 7        } else {
 8            data = nullptr;
 9        }
10    }
11
12    // 拷贝赋值运算符
13    MyString& operator=(const MyString& other) {
14        std::cout << "[CopyAssign] Deep copy assign\n";
15        if (this != &other) {
16            delete[] data;
17            if (other.data) {
18                data = new char[strlen(other.data) + 1];
19                strcpy(data, other.data);
20            } else {
21                data = nullptr;
22            }
23        }
24        return *this;
25    }

🚀 第三步:添加移动构造 & 移动赋值

 1    // 移动构造函数
 2    MyString(MyString&& other) noexcept {
 3        std::cout << "[MoveCtor] Moving\n";
 4        data = other.data;
 5        other.data = nullptr;
 6    }
 7
 8    // 移动赋值运算符
 9    MyString& operator=(MyString&& other) noexcept {
10        std::cout << "[MoveAssign] Move assign\n";
11        if (this != &other) {
12            delete[] data;
13            data = other.data;
14            other.data = nullptr;
15        }
16        return *this;
17    }

✅ 完整测试代码

 1int main() {
 2    std::cout << "=== Creating a ===\n";
 3    MyString a("Hello");
 4
 5    std::cout << "\n=== Copying to b ===\n";
 6    MyString b = a;  // Copy ctor
 7
 8    std::cout << "\n=== Moving to c ===\n";
 9    MyString c = std::move(a);  // Move ctor
10
11    std::cout << "\n=== Assign copy to d ===\n";
12    MyString d;
13    d = b;  // Copy assignment
14
15    std::cout << "\n=== Assign move to e ===\n";
16    MyString e;
17    e = std::move(c);  // Move assignment
18
19    std::cout << "\n=== Final states ===\n";
20    std::cout << "a: "; a.print();  // Should be null
21    std::cout << "b: "; b.print();
22    std::cout << "c: "; c.print();  // Should be null
23    std::cout << "d: "; d.print();
24    std::cout << "e: "; e.print();
25}

🧠 观察输出(关键点)

=== Creating a ===
[Ctor] Constructing from C-string

=== Copying to b ===
[CopyCtor] Deep copy

=== Moving to c ===
[MoveCtor] Moving

=== Assign copy to d ===
[CopyAssign] Deep copy assign

=== Assign move to e ===
[MoveAssign] Move assign
  • 拷贝构造/赋值是 深拷贝:申请新内存 + 拷贝内容。

  • 移动构造/赋值是 浅转移:直接转移指针,不复制内容。


🔍 小结

操作开销使用场景
拷贝构造/赋值分配内存 + 拷贝内容保证原对象不变
移动构造/赋值只转移指针临时对象 / 不再需要原对象时

🔥 Bonus:性能对比建议

可以用如下方式模拟大量对象,比较是否启用了移动:

1std::vector<MyString> vec;
2vec.push_back(MyString("Temporary")); // 会调用移动构造

或者加上 -fno-elide-constructors 来关闭编译器优化(比如 RVO)观察真实行为。


进阶

下面我们深入讲解 C++11 引入的两个非常重要的进阶特性:


🌟 一、完美转发(Perfect Forwarding)& 万能引用(Universal Reference)

它们是右值引用的重要延伸,主要用于泛型编程中,尤其是在模板中构造或转发参数。


🧩 二、问题背景:普通函数模板中的值传递会丢失“值类别”

来看一个例子:

1void process(int& x) { std::cout << "LValue\n"; }
2void process(int&& x) { std::cout << "RValue\n"; }
3
4template<typename T>
5void wrapper(T x) {
6    process(x);  // 始终是 LValue!
7}

测试:

1int a = 10;
2wrapper(a);        // 输出 LValue ✅
3wrapper(10);       // 输出 LValue ❌(期望 RValue)

❗ 问题:模板参数 T 是值传递,x 总是一个左值,哪怕你传的是右值。


✅ 三、万能引用(Universal Reference)

1template<typename T>
2void wrapper(T&& x);   // 这个 T&& 就是 **万能引用**

🔥 特点:

  • 它既能接受 左值,也能接受 右值

  • 万能引用 ≠ 普通右值引用。

  • 是否是万能引用,取决于:

    • 形式是 T&&(必须是模板参数+&&

    • T 是模板类型参数(推导发生)

示例:

1template<typename T>
2void wrapper(T&& x) {
3    process(std::forward<T>(x));  // 完美转发
4}

💡 四、std::forward 实现完美转发

在模板中,T&& 是万能引用,要保留原始“值类别”(左值或右值),就需要 std::forward<T>(x)

转发规则:

1std::forward<T>(x)

等价于:

  • 如果 T 是左值引用类型(T=int&),结果是 int&(左值)

  • 如果 T 是非引用(T=int),结果是 int&&(右值)


🎯 五、完美转发定义与作用

完美转发(Perfect Forwarding)

是指在模板中将参数原封不动地“转发”给另一个函数,包括:

  • 是否为左/右值

  • 是否为 const

  • 引用修饰

典型使用场景:

  • 构造工厂(如 make_shared

  • 容器内部 emplace_back

  • 任意包装器函数(如日志、计时器等)


🛠 六、例子:完美构造工厂

1class Widget {
2public:
3    Widget(int, double) { std::cout << "Construct Widget\n"; }
4};
5
6template<typename T, typename... Args>
7T* factory(Args&&... args) {
8    return new T(std::forward<Args>(args)...);  // 保留参数原本形态
9}

使用:

1auto w = factory<Widget>(42, 3.14);  // 完美转发参数到 Widget 构造函数

🧠 七、万能引用 vs 普通右值引用

语法是否万能引用接受左值接受右值
T&& in 模板✅ 是✅ 是✅ 是
int&&❌ 否❌ 否✅ 是
const T&&❌ 否❌ 否✅ 是

🧪 八、验证万能引用行为的小测试

 1template<typename T>
 2void test(T&& x) {
 3    if constexpr (std::is_lvalue_reference<T>::value)
 4        std::cout << "T is lvalue ref\n";
 5    else
 6        std::cout << "T is not lvalue ref\n";
 7}
 8
 9int a = 10;
10test(a);    // T = int& → T is lvalue ref
11test(10);   // T = int → T is not lvalue ref

📌 九、总结对比

概念用途特点
右值引用 T&&类中移动构造、移动赋值只能绑定右值
万能引用 T&&模板参数转发能接左/右值
std::forward保留原值类别实现完美转发必须搭配万能引用使用

🎁 Bonus:std::move vs std::forward

工具作用使用场景
std::move将表达式转为右值引用激活移动语义
std::forward根据模板参数 T 判断值类别实现完美转发

进阶例程

下面我们将分三步来深入理解并实践:


🔍 一、完美转发在 STL 中的实际应用

🎯 1. std::make_shared<T>(...) 如何用完美转发构造对象?

目标:

  • 避免重复写 new

  • 传什么参数都能构造 T,保持值类别(左值 / 右值)。

简化实现:

1template<typename T, typename... Args>
2std::shared_ptr<T> my_make_shared(Args&&... args) {
3    return std::shared_ptr<T>(new T(std::forward<Args>(args)...));
4}
  • Args&&... 是万能引用包。

  • std::forward<Args>(args)... 完美转发参数到 T 的构造函数。


🎯 2. emplace_back 如何避免多次构造?

1std::vector<std::string> vec;
2vec.emplace_back("hello");   // 构造一次 ✅
3vec.push_back("hello");      // 构造 + 拷贝或移动 ❌

emplace_back 内部:

1template<typename... Args>
2void emplace_back(Args&&... args) {
3    // 用转发构造元素到末尾
4    new (&storage[last_index]) T(std::forward<Args>(args)...);
5}
  • 避免临时对象,直接构造在容器内存中

  • 完美转发使它接受各种形式的构造参数(左/右值、引用、常量等)。


✨ 二、手写 make_unique 风格模板,支持完美转发

✅ 实现目标:

1template<typename T, typename... Args>
2std::unique_ptr<T> my_make_unique(Args&&... args);

✅ 实现代码:

1#include <memory>
2#include <utility>
3
4template<typename T, typename... Args>
5std::unique_ptr<T> my_make_unique(Args&&... args) {
6    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
7}

✅ 测试类和测试代码:

 1#include <iostream>
 2
 3class MyClass {
 4public:
 5    MyClass(int x, const std::string& s) {
 6        std::cout << "Constructing MyClass with x=" << x << ", s=" << s << "\n";
 7    }
 8};
 9
10int main() {
11    auto ptr = my_make_unique<MyClass>(42, std::string("hello"));
12    return 0;
13}

输出:

Constructing MyClass with x=42, s=hello

💡 三、你应该记住的完美转发套路

1template<typename T, typename... Args>
2T* factory(Args&&... args) {
3    return new T(std::forward<Args>(args)...);  // 保留原始形态
4}

模板 + 万能引用 + std::forward 是完美转发的三件套。


🔄 四、std::move vs std::forward:区别总结

工具适用参数值类别用途
std::move(x)任意表达式强制右值激活移动语义
std::forward<T>(x)模板参数 x保留原值类别完美转发(模板中)

🧪 五、挑战题(练习建议)

你可以自己尝试:

  • 实现一个 my_emplace_back 函数(模拟 vector::emplace_back

  • 实现一个通用工厂函数 construct_with_logging<T>(Args&&...),转发构造并记录日志


面试题

当然可以!下面是一些涵盖 右值引用、移动语义、完美转发与万能引用 的 C++11 面试题,按照从易到难的顺序整理,并配有标准答案与讲解,适合用来检验理解或准备面试。


🌱 一、基础题:右值引用 & 移动语义


✅ 题目 1:以下哪些可以绑定到 int&&

1int a = 10;
2int& lref = a;
3int&& rref = 20;

问:int&& r = a;int&& r2 = lref; 是否合法?


✅ 答案:

  • int&& r = a; ❌ 不合法,a 是左值,不能绑定到右值引用。

  • int&& r2 = lref; ❌ 同样不合法,lref 是左值引用。


✅ 讲解:

右值引用 (T&&) 只能绑定到右值(匿名变量、临时对象、字面值),不能绑定到左值或左值引用。


✅ 题目 2:以下语句中,是否发生了移动语义?

1std::string a = "hello";
2std::string b = std::move(a);

✅ 答案:

✅ 是,发生了移动构造。


✅ 讲解:

  • std::move(a)a 转换为右值引用(即 std::string&&

  • 然后调用 std::string 的移动构造函数。


🌿 二、进阶题:完美转发 & 万能引用


✅ 题目 3:下面哪个是万能引用?

1template<typename T>
2void func(T&& arg);
3
4void func2(int&& arg);

✅ 答案:

  • func(T&& arg) ✅ 是万能引用。

  • func2(int&& arg) ❌ 不是,是纯右值引用。


✅ 讲解:

万能引用的语法必须是 T&&,并且 T 是模板类型参数(推导发生)。
int&& 是固定类型,不具备万能性。


✅ 题目 4:下面的 call() 是否完美转发了参数?

1void target(int& x) { std::cout << "lvalue\n"; }
2void target(int&& x) { std::cout << "rvalue\n"; }
3
4template<typename T>
5void call(T&& arg) {
6    target(std::forward<T>(arg));
7}

调用:

1int a = 5;
2call(a);     // ?
3call(10);    // ?

✅ 答案:

  • call(a); 打印 lvalue

  • call(10); 打印 rvalue


✅ 讲解:

  • T= int& 时,forward<T>(arg) → 左值

  • T= int 时,forward<T>(arg) → 右值

完美转发使得 arg 的值类别得以保持。


🌳 三、高阶题:手写构造与转发逻辑


✅ 题目 5:解释 std::forward<T>(arg) 的实现原理。


✅ 答案(简化版):

1template<typename T>
2T&& forward(typename std::remove_reference<T>::type& arg) {
3    return static_cast<T&&>(arg);
4}

✅ 讲解:

  • remove_reference<T>::type 取得基础类型(去掉 & 和 &&)

  • 使用 static_cast<T&&> 保留 T 的值类别(左值或右值)


✅ 题目 6:用完美转发实现一个 make_unique<T> 函数模板。


✅ 答案:

1template<typename T, typename... Args>
2std::unique_ptr<T> my_make_unique(Args&&... args) {
3    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
4}

✅ 讲解:

  • Args&&... 是万能引用包

  • forward<Args>(args)... 保留所有参数的值类别

  • 支持构造任何参数组合的对象


🌲 四、实践与陷阱题


✅ 题目 7:下面这个 wrapper 有什么 bug?

1template<typename T>
2void wrapper(T&& arg) {
3    process(arg);
4}

✅ 答案:

❌ 错误:这里会将 arg 作为左值传递,即使是右值也会变成左值。


✅ 正确写法:

1process(std::forward<T>(arg));

✅ 讲解:

  • 所有变量都有名字 → 是左值

  • 要保留右值性质,必须 forward


🎯 总结:面试官常问的核心点

问题类型考察点
右值引用能否绑定左值?区分左值 / 右值引用
std::move 和 std::forward 区别用途不同:强制右值 vs 保留值类别
万能引用和右值引用如何区分?模板中 T&& 是万能引用
如何实现 make_shared / unique完美转发 + 模板
emplace_back vs push_back是否构造临时对象 + 完美转发

STL相关面试题

下面是一些STL 实践层面的完美转发题目,涵盖了 std::vector::emplace_back、容器构造、std::function 转发、std::bind 陷阱等高级用法,非常贴近实战和面试常考点。


🌟 STL 级完美转发实践题目(含答案)


✅ 题目 1:emplace_backpush_back 的区别及适用场景?

代码:

1std::vector<std::string> v;
2std::string s = "hello";
3
4v.push_back(s);            // ①
5v.push_back(std::move(s)); // ②
6v.emplace_back("world");   // ③

问题:

  1. 分析每行代码是否涉及复制或移动构造?

  2. 哪一行使用了完美转发?


✅ 答案:

    • ①:复制构造(传的是左值)

    • ②:移动构造(右值引用)

    • ③:构造就地(emplace_back),避免临时对象,调用构造函数直接构造 "world" 字符串

  1. 第 ③ 行使用了完美转发:emplace_back("world") 会调用 std::string 的构造函数,传入转发参数。


✅ 题目 2:以下 emplace_backpush_back 哪个更高效?为什么?

1std::vector<std::pair<int, std::string>> v;
2v.push_back(std::make_pair(1, "hello"));
3v.emplace_back(1, "hello");

✅ 答案:

  • emplace_back(1, "hello") 更高效。

  • 原因:避免了中间临时 pair<int, string> 对象的创建和移动/复制构造。

  • 它直接使用完美转发调用 pair 的构造函数。


✅ 题目 3:实现一个 log_and_forward 函数,它接收任意参数并调用目标函数,同时打印参数个数。

1template<typename Func, typename... Args>
2void log_and_forward(Func&& func, Args&&... args) {
3    // 实现:打印参数个数,然后调用 func(args...)
4}

✅ 答案:

1#include <iostream>
2#include <utility>
3
4template<typename Func, typename... Args>
5void log_and_forward(Func&& func, Args&&... args) {
6    std::cout << "Calling with " << sizeof...(Args) << " arguments\n";
7    std::forward<Func>(func)(std::forward<Args>(args)...);
8}

✅ 测试用例:

1void print(int x, const std::string& s) {
2    std::cout << "x=" << x << ", s=" << s << "\n";
3}
4
5int main() {
6    log_and_forward(print, 42, "hello");
7}

✅ 题目 4:如何实现一个类模板 Wrapper<T>,它可以完美转发构造参数到 T 的构造函数?

目标:

1Wrapper<MyClass> w(args...);  // 直接构造 MyClass,支持任意构造参数

✅ 答案:

 1template<typename T>
 2class Wrapper {
 3public:
 4    template<typename... Args>
 5    Wrapper(Args&&... args)
 6        : instance_(std::forward<Args>(args)...) {}
 7
 8    T& get() { return instance_; }
 9
10private:
11    T instance_;
12};

✅ 题目 5:使用 std::function 时,是否能实现完美转发?试解释为何以下写法不具备转发能力:

1std::function<void(int&)> f = [](int& x) { std::cout << x << "\n"; };
2int a = 5;
3f(a);   // OK
4f(5);   // ❌ 编译错误

✅ 答案:

  • std::function<void(int&)> 只能接受左值引用

  • f(5) 是右值,不能绑定到 int&,因此出错。

  • std::function类型擦除机制,丢失了泛型与完美转发能力。

  • 若想完美转发,应该使用模板函数或 std::invoke


✅ 题目 6(高难):实现一个 defer 函数(类似 Go),延迟执行传入的 lambda,在作用域结束时自动触发。

1auto d = defer([]() { std::cout << "deferred\n"; });

✅ 答案:

 1template<typename F>
 2class Defer {
 3    F func_;
 4public:
 5    explicit Defer(F&& f) : func_(std::forward<F>(f)) {}
 6    ~Defer() { func_(); }
 7};
 8
 9template<typename F>
10Defer<F> defer(F&& f) {
11    return Defer<F>(std::forward<F>(f));
12}

✅ 应用示例:

1int main() {
2    auto _ = defer([]() { std::cout << "defer\n"; });
3    std::cout << "in scope\n";
4}
5// 输出:
6// in scope
7// defer

📘 Bonus:面试官可能问的深入问题

问题涉及点
为什么 emplace_backpush_back 快?少一次构造/移动
std::forwardstd::move 区别?保留 vs 强制右值
为何 std::function 无法完美转发?类型擦除,不保留模板推导
万能引用的条件?T&& 且模板参数发生推导

深入问题

下面是这四个面试高频问题的标准答案和详细讲解,每个都展开深入解析,帮助你从机制层面真正理解这些 STL 与语法特性的原理。


问题 1:为什么 emplace_backpush_back 快?

✅ 答案简述:

emplace_back 避免了临时对象构造或复制/移动,因此通常比 push_back 更高效。

🔍 详细解释:

  • push_back(obj) 接受一个已经构造好的对象,它会进行一次拷贝或移动构造

    1v.push_back(std::string("hello")); // 构造 -> 移动
    
  • emplace_back(args...) 接受任意构造参数,在容器内就地构造对象,避免了临时对象的创建。

    1v.emplace_back("hello"); // 直接构造,无移动或复制
    

🧠 内部机制:

1template<class... Args>
2void emplace_back(Args&&... args) {
3    // 完美转发到对象构造函数
4    construct_at(end(), std::forward<Args>(args)...);
5}

问题 2:std::forwardstd::move 的区别?

✅ 答案简述:

  • std::move: 强制将对象转为右值。

  • std::forward: 根据模板类型 T 保留原始值类别(左值/右值)。

🔍 详细解释:

特性std::movestd::forward<T>
目的强制转为右值保留原始值类别
类型推导不依赖模板依赖模板类型 T
使用场景实现移动语义实现完美转发
会破坏原对象?可能取决于 T 推导结果

✅ 示例对比:

1template<typename T>
2void func(T&& arg) {
3    std::move(arg);        // 始终右值(破坏性)
4    std::forward<T>(arg);  // 如果 arg 是左值,仍保持左值
5}

问题 3:为何 std::function 无法完美转发?

✅ 答案简述:

std::function 是类型擦除容器,不能保留参数类型和引用类别信息,因此无法实现完美转发。

🔍 详细解释:

  • std::function 是用于存储任意可调用对象的通用容器,类型签名固定,例如:

    1std::function<void(int&)> f;
    
  • 这里参数类型一旦设定为 int&,调用时只能传左值,无法接受右值(如字面值)。

🧠 原因核心:

  • 完美转发依赖模板推导、参数类别推导(T&&),std::function 丢失了这些信息。

  • 它内部的调用语义不区分参数的左值/右值引用类别。

✅ 示例说明:

1auto lambda = [](auto&& arg) {
2    std::cout << typeid(arg).name() << "\n";
3};
4
5std::function<void(int&&)> f = lambda;  // ❌ 编译失败:类型不匹配

问题 4:万能引用的条件?

✅ 答案简述:

  • 万能引用是 T&& 的形式,并且 T 是模板参数,必须发生类型推导

🔍 判定万能引用的两个关键条件:

  1. 函数参数必须是 T&& 的形式(模板类型 + &&

  2. 必须通过 类型推导 得到 T(非显式指定)

✅ 正例(是万能引用):

1template<typename T>
2void func(T&& arg);   // ✅ 满足万能引用条件
  • func(5) 时,T 为 intint&&

  • int a = 10; func(a); → T 为 int&,参数实际类型为 int& && → 折叠为 int&

❌ 反例(不是万能引用):

1void f(int&&);              // 右值引用,非模板
2template<typename T>
3void f2(std::vector<T>&&);  // 固定类型右值引用,非万能引用

🧠 推导 + 引用折叠:

TT&&结果
intint&&int&&
int&int& &&int&

引用折叠是万能引用的核心基础之一。


✅ 总结表:

问题关键点
为什么 emplace_back 快?避免中间对象构造,直接就地构造
std::forward vs std::move保留原值类别 vs 强制右值
std::function 不支持转发?类型擦除,丢失推导与引用类别信息
万能引用条件?T&&T 被推导,支持引用折叠机制

STL 层级完美转发相关的 C++11 编程练习题

下面我为你详细解答这 5 道 STL 层级完美转发相关的 C++11 编程练习题,帮助你从实际代码中掌握完美转发、万能引用、右值引用和类型推导的精髓。


✅ 题 1:log_and_forward — 记录参数个数并完美转发

✏️ 要求:

  • 接收任意可调用对象 f 和任意参数 args...

  • 打印参数个数

  • 调用 f(args...),保持值类别(完美转发)

✅ 实现:

1#include <iostream>
2#include <utility>
3
4template<typename F, typename... Args>
5decltype(auto) log_and_forward(F&& f, Args&&... args) {
6    std::cout << "Argument count: " << sizeof...(Args) << "\n";
7    return std::forward<F>(f)(std::forward<Args>(args)...);
8}

🧪 示例:

1void print(int x, const std::string& msg) {
2    std::cout << msg << ": " << x << "\n";
3}
4
5int main() {
6    log_and_forward(print, 42, "value");  // 输出 Argument count: 2,value: 42
7}

✅ 题 2:Wrapper<T> 类模板,构造时完美转发参数

✏️ 要求:

  • 类模板 Wrapper<T>

  • 内部持有一个 T t_

  • 构造函数使用完美转发构造 T

✅ 实现:

 1#include <utility>
 2
 3template<typename T>
 4class Wrapper {
 5public:
 6    template<typename... Args>
 7    Wrapper(Args&&... args)
 8        : t_(std::forward<Args>(args)...) {}
 9
10    T& get() { return t_; }
11
12private:
13    T t_;
14};

🧪 示例:

1#include <string>
2#include <iostream>
3
4int main() {
5    Wrapper<std::string> w("hello");
6    std::cout << w.get() << "\n"; // 输出 hello
7}

✅ 题 3:模拟 Go 的 defer(作用域自动调用)

✏️ 要求:

  • 类似 Go 的 defer

  • 接收一个 lambda,作用域结束自动调用它

✅ 实现(RAII 方案):

 1#include <functional>
 2
 3class Defer {
 4public:
 5    Defer(std::function<void()> fn) : fn_(std::move(fn)) {}
 6    ~Defer() { fn_(); }
 7
 8private:
 9    std::function<void()> fn_;
10};
11
12#define CONCAT_IMPL(x, y) x##y
13#define CONCAT(x, y) CONCAT_IMPL(x, y)
14#define defer(code) Defer CONCAT(_defer_, __LINE__)([&](){ code; })

🧪 示例:

1#include <iostream>
2
3int main() {
4    defer(std::cout << "Exiting scope!\n";);
5    std::cout << "Inside scope\n";
6    // Output:
7    // Inside scope
8    // Exiting scope!
9}

✅ 题 4:is_universal_reference 判断是否为万能引用

✏️ 思路:

  • 万能引用只有在模板中 T&& 并发生类型推导才成立

  • std::is_rvalue_reference<T&&>std::is_lvalue_reference<T> 配合判断

✅ 实现:

 1#include <iostream>
 2#include <type_traits>
 3
 4template<typename T>
 5void is_universal_reference(T&& arg) {
 6    if constexpr (std::is_lvalue_reference<T>::value)
 7        std::cout << "arg is bound to LVALUE: T = " << typeid(T).name() << "\n";
 8    else
 9        std::cout << "arg is bound to RVALUE: T = " << typeid(T).name() << "\n";
10}

🧪 示例:

1int main() {
2    int x = 5;
3    is_universal_reference(x);  // LVALUE
4    is_universal_reference(10); // RVALUE
5}

✅ 题 5:分析以下声明是否为万能引用

1template<typename T> void f1(T&&);                     // ✅ 是万能引用
2void f2(int&&);                                        // ❌ 不是万能引用,只能绑定右值
3template<typename T> void f3(std::vector<T>&&);       // ❌ 不是万能引用,是右值引用模板

🧠 理由:

函数声明是否万能引用原因说明
template<typename T> void f1(T&&)✅ 是T 是模板参数,发生类型推导,引发引用折叠
void f2(int&&)❌ 否不是模板,无类型推导,是普通右值引用
template<typename T> void f3(std::vector<T>&&)❌ 否std::vector<T>&& 是具体类型,不能引用折叠

✅ 最后提示:如何判断是不是万能引用?

只要写法是 T&& 并且 T 是模板参数,并且 T 是被推导出来的,就可以判断为万能引用。



最后修改于 2025-05-15 14:45

- 目录 -