C++11 lambda与可变参数模版

发布于:2025-03-30 ⋅ 阅读:(24) ⋅ 点赞:(0)

1. 类中的移动构造与移动赋值

编译器默认生成条件:
  • 移动构造函数:若未显式声明移动构造、拷贝构造、拷贝赋值或析构函数,且类成员支持移动语义,编译器会自动生成默认移动构造。
  • 移动赋值运算符:生成条件与移动构造类似,且未声明移动赋值运算符。
作用:
  • 资源所有权转移:通过指针窃取而非深拷贝,减少内存复制开销(如 ModernString 类中的 data_ 指针转移)。
  • 异常安全保证:移动操作通常标记为 noexcept,适用于容器扩容等关键场景。
  • 优化临时对象处理:如 std::move(obj) 将左值转为右值引用,触发移动语义。

示例

class ModernString {
public:
    ModernString(ModernString&& other) noexcept 
        : data_(other.data_), length_(other.length_) {  // 移动构造
        other.data_ = nullptr;  // 原对象置空
    }
    ModernString& operator=(ModernString&& other) noexcept {  // 移动赋值
        if (this != &other) {
            delete[] data_;
            data_ = other.data_;
            other.data_ = nullptr;
        }
        return *this;
    }
};

注意事项

  • 若自定义了拷贝操作,编译器不会自动生成移动操作,需显式定义或使用 = default
  • 内置类型成员按值拷贝,自定义类型成员需自行实现移动语义。

2. default 与 delete 关键字

default
  • 作用:显式要求编译器生成默认实现的函数(如默认构造、析构)。
  • 适用场景:当类中存在其他构造函数但仍需保留默认构造时。
    class A {
    public:
        A() = default;  // 显式生成默认构造
        A(int x) { /* ... */ }
    };
delete
  • 作用:禁止特定函数的生成或调用(如禁用拷贝)。
  • 示例
    class NonCopyable {
    public:
        NonCopyable(const NonCopyable&) = delete;  // 禁用拷贝构造
    };

注意事项

  • delete 可应用于任何函数(包括普通成员函数),而 default 仅限特殊成员函数。
  • 替代 C++98 中通过私有化函数实现禁用拷贝的方法,更直观。

3. 可变参数模板

核心机制:
  • 参数包:通过 typename... Args 声明模板参数包,Args... args 声明函数参数包。
  • 展开方式
    • 递归展开:需定义终止条件(如空参数包处理)。
    • 折叠表达式​(C++17):简化参数包操作,如 (args + ...) 求和。

示例

// 递归终止函数
template <class T>
void ShowList(const T& t)
{
 cout << t << endl;
}
// 展开函数
template <class T, class ...Args>
void ShowList(T value, Args... args)
{
 cout << value <<" ";
 ShowList(args...);
}
int main()
{
 ShowList(1);
 ShowList(1, 'A');
 ShowList(1, 'A', std::string("sort"));
 return 0;
}

注意事项

  • 参数包不支持直接索引访问,必须通过展开操作。
  • 结合 sizeof...(args) 可获取参数包大小。
  • 除此之外还可以用逗号表达式展开参数包,这里就不做展示了。

4. STL 的 emplace 系列函数

高效原因:
  • 直接构造:在容器内部直接构造对象,避免临时对象的拷贝/移动(如 vector::emplace_back 直接调用元素的构造函数)。
  • 完美转发:通过 std::forward 保留参数类型(左值/右值),优化传递效率。

示例

std::vector<std::pair<int, std::string>> vec;
vec.emplace_back(1, "example");  // 直接构造 pair,无需创建临时对象

对比 push_back

  • push_back 需先构造临时对象,再拷贝/移动到容器。
  • 对复杂对象(如含大量资源的类),emplace 性能提升显著。

5. Lambda 表达式

语法与原理:
  • 语法[捕获列表](参数) mutable -> 返回类型 { 函数体 }
  • [capture-list] (parameters) mutable -> return-type { statement }
  • 底层实现:编译器生成匿名类,捕获变量作为成员,重载 operator()
  • lambda表达式各部分说明:
  • [capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。
  • (parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以
    连同()一起省略。
  • mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量
    性。使用该修饰符时,参数列表不可省略(即使参数为空)。
  • ->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回
    值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推
    导。
  • {statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获
    到的变量。
  • 注意:在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。

示例

int x = 10;
auto lambda = [x](int y) mutable { 
    x++;  // 需 mutable 才能修改按值捕获的变量
    return x + y;
};


int main()
{
 vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 
3 }, { "菠萝", 1.5, 4 } };
 sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){
 return g1._price < g2._price; });
 sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){
 return g1._price > g2._price; });
 sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){
 return g1._evaluate < g2._evaluate; });
 sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){
 return g1._evaluate > g2._evaluate; });
}

int main()
{
    // 最简单的lambda表达式, 该lambda表达式没有任何意义
   []{}; 
    
    // 省略参数列表和返回值类型,返回值类型由编译器推导为int
    int a = 3, b = 4;
   [=]{return a + 3; }; 
    
    // 省略了返回值类型,无返回值类型
    auto fun1 = [&](int c){b = a + c; }; 
    fun1(10)
    cout<<a<<" "<<b<<endl;
    
    // 各部分都很完善的lambda函数
    auto fun2 = [=, &b](int c)->int{return b += a+ c; }; 
    cout<<fun2(10)<<endl;
    
    // 复制捕捉x
    int x = 10;
    auto add_x = [x](int a) mutable { x *= 2; return a + x; }; 
    cout << add_x(10) << endl; 
    return 0;
}

//lambda表达式实际上可以理解为无名函数,该函数无法直接调用,如果想要直接调用,可借助auto将其赋值给一个变量。

注意事项

  • 捕获方式捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用
    • [var]:表示值传递方式捕捉变量var
      [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
      [&var]:表示引用传递捕捉变量var
      [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
      [this]:表示值传递方式捕捉当前的this指针
  • 父作用域指包含lambda函数的语句块
  • 语法上捕捉列表可由多个捕捉项组成,并以逗号分割
    比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量 [&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量
  • 捕捉列表不允许变量重复传递,否则就会导致编译错误
    比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复
  • 在块作用域以外的lambda函数捕捉列表必须为空.
  • 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者 非局部变量都会导致编译报错。
  • lambda表达式之间不能相互赋值,即使看起来类型相同