C++面向对象(OOP)编程-C++11新特性详解

        C++11作为一个重要的版本,引入了很多新的特性,解决了C++语言本身很多遗留的内存泄露问题,并且提供了很多比较灵活的用法。引入的auto,智能指针、线程机制都使得C++语言的灵活性、安全性、并发性有了很大的提升。

        本文会比较详细的介绍C++11引入的新特性,以及各自引入的原因和基本的用法。

🎬个人简介:一个全栈工程师的升级之路!
📋个人专栏:C/C++精进之路
🎀CSDN主页 发狂的小花
🌄人生秘诀:学习的本质就是极致重复!

目录

1 C++11 新特性分类

2 C++11新特性详解

2.1 并发编程支持

2.1.1 内存模型

2.1.2 线程和锁

2.1.3 期值

2.2 泛型编程支持

2.2.1 lambda

2.2.2 变参模板

2.2.3 别名

2.2.4 tuple

2.3 简化使用

2.3.1 auto和decltype

2.3.2 范围for

2.3.3 智能指针

2.3.4 统一初始化

2.3.5 nullptr

2.3.6 constexpr

2.3.7 explicit

2.3.8 final和override

2.3.9 右值引用

2.3.10 移动语义

2.3.11 完美转发


1 C++11 新特性分类

        C++11引入的新特性基本上可以分为并发编程支持、泛型编程支持和简化使用三部分。

如下图是详细的分类:

2 C++11新特性详解

2.1 并发编程支持

2.1.1 内存模型

C++11内存模型是一种抽象,它定义了程序中各种变量(如指针、数组、对象等)的访问方式,以及在多线程环境下的并发操作行为。内存模型解决了由于编译器优化、指令重排等原因导致的可见性问题,从而使得程序员可以更好地控制程序的执行顺序,避免出现竞态条件等问题。

内存类型:

C++11内存模型的主要特性包括:

  1. 原子操作:C++11标准对原子操作进行了定义,并引入了一些新的原子类型和函数,以支持无锁编程。原子操作就是对一个内存上变量(或者叫左值)的读取-变更-存储(load-add-store)作为一个整体一次完成。

  2. 同步原语:C++11提供了一些同步原语,如std::mutex、std::condition_variable等,用于实现线程间的同步和互斥。

  3. 内存模型:C++11从各种不同的平台上抽象出了一个软件的内存模型,并以内存顺序进行描述,以使得想进一步挖掘并行系统性能的程序员有足够简单的手段来完成以往只能通过底层编程技术实现的功能。

  4. 禁止重排序:C++11内存模型规定了某些特定的内存操作不能被重排序。

  5. 内存屏障:C++11内存模型引入了内存屏障(memory barrier)的概念,用于控制内存访问的顺序,以避免编译器对代码的重排导致的问题。

  6. 内存序:C++11标准中提供了6种memory order,来描述内存模型。

例子:

#include <iostream>
#include <thread>
#include <mutex>
#include <atomic>
#include <vector>std::mutex mtx; // 定义互斥量
std::atomic<int> x(0); // 定义原子变量
std::vector<int> data; // 定义数据容器void reader() {mtx.lock(); // 获取锁data.push_back(x.load(std::memory_order_relaxed)); // 读取原子变量的值mtx.unlock(); // 释放锁
}void writer() {mtx.lock(); // 获取锁x.store(10, std::memory_order_release); // 修改原子变量的值mtx.unlock(); // 释放锁
}int main() {std::thread t1(reader); // 创建读线程t1std::thread t2(writer); // 创建写线程t2t1.join(); // 等待读线程结束t2.join(); // 等待写线程结束return 0;
}

2.1.2 线程和锁

        C++11还提供了一些同步机制,例如互斥锁、条件变量、自旋锁、读写锁等。其中,条件变量是一种同步原语,用于阻塞一个或多个线程,直到接收到另一个线程的通知信号,或暂停信号,或伪唤醒信号。自旋锁是一种忙等待锁,它在等待锁的过程中不会使线程进入睡眠状态。读写锁允许多个线程同时读取共享资源,但在写入时只允许一个线程访问。


• mutex——系统的互斥锁,支持 lock()、unlock() 和保证 unlock() 的 RAII 方式
• condition_variable——系统中线程间进行事件通信的条件变量
• thread_local——线程本地存储

线程和锁模型需要使用某种形式的同步来避免竞争条件。C++11 为此提供了标准 的 mutex(互斥锁)。

2.1.3 期值

        在C++11中,期值(future)是一种用于异步计算的对象,它代表了一个尚未完成但将来会得到的值。期值的主要用途是实现并发编程中的异步操作,例如在一个线程中执行某个耗时的操作,然后在另一个线程中等待该操作的结果。

C++11主要提供的API

future——一个句柄,通过它你可以从一个共享的单对象缓冲区中 get() 一个值,可能需要等待某个 promise 将该值放入缓冲区。

• promise——一个句柄,通过它你可以将一个值 put() 到一个共享的单对象缓冲区,可能会唤醒某个等待 future 的 thread。

• packaged_task——一个类,它使得设置一个函数在线程上异步执行变得容易,由 future 来接受 promise 返回的结果。

• async()——一个函数,可以启动一个任务并在另一个 thread 上执行。

可以通过std::future来获取期值,其基本用法如下:

#include <iostream>
#include <thread>
#include <future>int main() {// 创建一个异步任务std::future<int> fut = std::async(std::launch::async, [](){std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟耗时操作return 42; // 返回结果});// 在另一个线程中等待结果std::cout << "Result: " << fut.get() << std::endl; // 输出结果return 0;
}

一个例子:

#include <iostream>
#include <future>int main() {std::future<int> result;// 启动一个异步任务来计算两个数之和result = std::async(std::launch::async, [](int a, int b) {return a + b;}, 3, 4);// 在主线程中等待异步任务完成并获取结果int sum = result.get();std::cout << "两数之和为:" << sum << std::endl;return 0;
}

2.2 泛型编程支持

2.2.1 lambda

C++11引入了lambda表达式,它是一种匿名函数,可以在代码中直接定义和使用。lambda表达式的语法如下:

[capture](parameters) -> return_type { function_body }

其中:

  • capture:捕获列表,用于捕获外部变量。可以有值捕获(按值传递)和引用捕获(按引用传递)。
  • parameters:参数列表,与普通函数的参数列表类似。
  • return_type:返回类型,可以省略,编译器会自动推导。
  • function_body:函数体,包含实现的代码。

下面是一个简单的lambda表达式示例:

#include <iostream>int main() {auto add = [](int a, int b) -> int { return a + b; };int sum = add(3, 4);std::cout << "Sum: " << sum << std::endl;return 0;
}

捕获列表的几种形式:

1.不捕获任何外部变量:

[]{ /* code */ }

2.按值捕获所有外部变量:

[=]{ /* code */ }

3.按引用捕获所有外部变量:

[&]{ /* code */ }

4.按值捕获特定变量:

[x]{ /* code */ }

5.按引用捕获特定变量:

[&x]{ /* code */ }

6.混合捕获模式:

[x, &y]{ /* code */ }

7.隐式捕获某些变量,显式捕获其他变量:

[=, &x]{ /* code */ }
[&x, y]{ /* code */ }

例子:
1.不捕获任何外部变量:

[]{ return 23; }

一个简单的 lambda,不使用任何外部变量,返回 23。

2.按值捕获外部变量:

int x = 2;
auto l = [=]{ return x + 1; };

这个 lambda 按值捕获 x,返回 x + 1 的结果。

3.按引用捕获外部变量并修改:

int x = 1;
auto l = [&x]{ ++x; };

按引用捕获 x 并在 lambda 内部对其进行修改。

4.具有参数的 lambda:

auto l = [](int a, int b){ return a + b; };

一个接受两个整数参数并返回它们的和的 lambda。

5.可变 lambda(修改按值捕获的变量):

int x = 1;
auto l = [x]() mutable { return ++x; };

mutable 关键字允许 lambda 修改按值捕获的变量。

6.带有显式返回类型的 lambda:

auto l = [](int a, int b) -> double { return (a + b) / 2.0; };

一个计算平均值的 lambda,显式指定返回类型为 double。
 

2.2.2 变参模板

C++11中的变参模板允许函数或类模板接受可变数量的参数。在函数模板中,可以使用省略号(...)表示可变数量的参数;在类模板中,可以使用参数包(parameter pack)表示可变数量的类型参数。

下面分别举一个变参函数模板和一个变参类模板的例子:

  • 变参函数模板
template<typename T, typename... Args>
void foo(T t, Args... args) {// ...
}

在上面的例子中,foo函数模板可以接受任意数量的参数,其中第一个参数的类型为T,后面的参数类型为Args。这些参数可以是任何类型,包括基本类型、类类型和其它模板类型。

在函数体内,可以使用递归展开(recursive expansion)来访问这些参数。例如:

template<typename T, typename... Args>
void bar(T t, Args... args) {foo(t); // 处理第一个参数bar(args...); // 递归调用自身,处理剩余参数
}

在上面的例子中,bar函数首先调用foo函数处理第一个参数,然后使用递归展开的方式调用自身来处理剩余的参数。这样,我们就可以实现类似于printf函数的功能,接受任意数量的参数并进行处理。

  • 变参类模板
template<typename T, typename... Args>
class MyClass {
public:MyClass(T t, Args... args) {// ...}
};

在上面的例子中,MyClass类模板可以接受任意数量的类型参数。在构造函数中,可以使用参数包展开(parameter pack expansion)来初始化成员变量。例如:

template<typename T, typename... Args>
void bar(T t, Args... args) {MyClass<T, Args...> obj(t, args...); // 创建对象并初始化成员变量
}

在上面的例子中,我们使用MyClass<T, Args...>来创建一个对象,并将传入的参数传递给构造函数进行初始化。这样,我们就可以实现类似于printf函数的功能,接受任意数量的参数并进行处理。

一个简单的例子:

#include <iostream>
#include <string>
#include <sstream>template<typename T>
std::string toString(const T& value) {std::ostringstream oss;oss << value;return oss.str();
}template<typename T, typename... Args>
std::string toString(const T& value, const Args&... args) {std::ostringstream oss;oss << value;return oss.str() + " " + toString(args...);
}template<typename... Args>
void myPrintf(const char* format, const Args&... args) {std::cout << toString(format, args...) << std::endl;
}int main() {myPrintf( "Alice", 30,23.8);return 0;
}

        运行结果:

Alice 30 23.8

2.2.3 别名

在C++11中,引入了类型别名(Type Alias)的新特性,允许开发者为现有的数据类型创建一个新的名称。这个功能有助于提高代码的可读性、可维护性和可重用性。类型别名可以通过typedef关键字或using关键字来定义。

以下两种typedef和using的使用方式:

// 别名typedef int myInt;
myInt kp;using IntVector = std::vector<int>; 
IntVector ki;
ki.push_back(23);
for (auto i : ki)
{std::cout << i << std::endl;
}using MYint = int;
MYint lo = 4;
std::cout << lo << std::endl;

这两种方法在语义上是等效的。另外,值得一提的是,使用using关键字定义的模板别名相比typedef更具优势,它更简洁,并且可以完全像新的类模板一样使用。

2.2.4 tuple

C++11中的tuple是一个模板类,它可以存储任意数量和类型的数据。它的主要优点是可以方便地访问元组中的元素,并且支持元组的复制、移动和比较等操作。

使用tuple的基本语法如下:

std::tuple<type1, type2, ...> tuple_name(value1, value2, ...);

其中,type1、type2等是元组中元素的类型,value1、value2等是对应的元素值。

例如,我们可以定义一个包含两个int类型和一个string类型的tuple:

#include <iostream>
#include <tuple>
#include <string>int main() {std::tuple<int, int, std::string> myTuple(1, 2, "hello");return 0;
}

要访问tuple中的元素,可以使用std::get函数。例如,如果我们想访问上述定义的tuple中的第一个元素(一个int),可以使用std::get<0>(myTuple)来获取。需要注意的是,元组的索引是从0开始的。

此外,tuple还支持一些其他操作,例如创建元组的副本、移动元组等。例如,我们可以使用std::make_tuple函数来创建一个包含两个int类型和一个string类型的tuple:

std::tuple<int, int, std::string> anotherTuple = std::make_tuple(3, 4, "world");

我们还可以使用std::tie函数将tuple中的元素解包到多个变量中:

int a, b;
std::string c;
std::tie(a, b, c) = myTuple;

最后,我们可以使用std::tuple_cat函数将两个或多个tuple连接起来:

std::tuple<int, int, std::string> combinedTuple = std::tuple_cat(myTuple, anotherTuple);

结构化绑定:

// tuple
std::tuple<int, int, std::string> myTuple(1, 2, "hello");auto [value1, value2, value3] = myTuple;
// 结构化绑定
std::cout << value1 << " " << value2 << " " << value3 << " " << std::endl;

2.3 简化使用

2.3.1 auto和decltype

C++11中用auto关键字来支持自动类型推导。用decltype推导表达式类型。头文件:#include<typeinfo>

auto:让编译器在编译器就推导出变量的类型,可以通过=右边的类型推导出变量的类型。(前提是定义一个变量时对其进行初始化)

auto a = 10; // 10是int型,可以自动推导出a是int

decltype:用于推导表达式类型,这里只用于编译器分析表达式的类型,表达式实际不会进行运算。(decltypde是不需要推导变量初始化的,根据的是表达式对变量的类型就可以推导。)

auto varname = value;
decltype(exp) varname = value;
decltype(10.8) x;  //x 被推导成了 double

两者区别:

1,auto用于变量的类型推导,根据初始化表达式的类型来推导变量的类型,常用于简化代码和处理复杂类型。而decltype则用于获取表达式的类型,保留修饰符,并且可以进行表达式求值。

2,auto在初始化时进行类型推导,而decltype直接查询表达式的类型,可以用于任何表达式,包括没有初始化的变量。

3,auto在编译期间确定类型,并且无法更改。而decltype在运行时才确定表达式的类型。

4,auto适用于简单的类型推导,而decltype适用于复杂的类型推导和获取表达式的结果类型。

2.3.2 范围for

范围for是C++11中引入的一种新的循环结构,用于遍历容器或数组中的元素。它的基本语法如下:

for (declaration : range) {// 执行语句
}
其中,declaration表示声明一个变量来存储当前迭代的值,range表示要遍历的范围,可以是容器、数组、字符串等。

例如,遍历一个vector中的元素可以使用以下代码:

#include <iostream>
#include <vector>int main() {std::vector<int> myVector = {1, 2, 3, 4, 5};for (int num : myVector) {std::cout << num << std::endl;}for (auto num : myVector) {std::cout << num << std::endl;}return 0;
}

在这个例子中,我们使用了一个int类型的变量num来存储当前迭代的值,然后通过std::cout输出到控制台。每次循环时,num会自动更新为myVector中的下一个元素。当myVector中的所有元素都被遍历完后,循环结束。这里也可以使用auto来推导。

2.3.3 智能指针

在C++11中,引入了新的智能指针类,用于更安全和方便地管理动态分配的资源,避免内存泄漏和悬空指针等问题。以下是C++11中的三种主要智能指针:

std::unique_ptr

1,std::unique_ptr 是一种独占式智能指针,用于管理唯一的对象,确保只有一个指针可以访问该对象。

2,使用 std::unique_ptr 可以自动释放动态分配的内存,当指针超出作用域或被重置时,它会自动删除所管理的对象。

3,通过 std::make_unique 函数可以创建 std::unique_ptr 对象,如:std::unique_ptr<int> ptr = std::make_unique<int>(42);

std: :shared_ptr:

1,std::shared_ptr 是一种共享式智能指针,多个指针可以同时共享对同一对象的拥有权。

2,std::shared_ptr 使用引用计数技术追踪所管理对象的引用数量,当引用计数变为零时,自动销毁所管理的对象。

3,通过 std::make_shared 函数可以创建 std::shared_ptr 对象,如:std::shared_ptr<int> ptr = std::make_shared<int>(42);

std::weak_ptr

1,std::weak_ptr 是一种弱引用智能指针,它可以解决 std::shared_ptr 的循环引用问题。

2,std::weak_ptr 指向 std::shared_ptr 管理的对象,但不会增加引用计数。因此,当所有 std::shared_ptr 对象超出作用域后,即使还有 std::weak_ptr 对象存在,所管理的对象也会被销毁。

3,通过 std::shared_ptr 的 std::weak_ptr 构造函数可以创建 std::weak_ptr 对象,如:std::weak_ptr<int> weakPtr = sharedPtr;

2.3.4 统一初始化

C++11引入了统一初始化(Uniform Initialization)的概念,它允许在构造函数参数列表中使用花括号{}来初始化对象。这种初始化方式可以用于创建数组、结构体、类等对象。

例如,对于数组的初始化:

int arr[] = {1, 2, 3}; // 使用花括号{}初始化数组
对于结构体的初始化:
struct MyStruct {int a;double b;char c;
};MyStruct ms = {1, 2.0, 'c'}; // 使用花括号{}初始化结构体

对于类的初始化:

class MyClass {
public:int a;double b;char c;
};MyClass mc = {1, 2.0, 'c'}; // 使用花括号{}初始化类

统一初始化还可以用于lambda表达式和捕获变量的初始化:

auto func = [](int x, int y) -> int { return x + y; }; // 使用花括号{}初始化lambda表达式
int a = 10;
int b = 20;
[a, b]() mutable { // 使用花括号{}初始化捕获变量std::cout << "a: " << a << ", b: " << b << std::endl;
}();

2.3.5 nullptr

C++11引入了一个新的关键字nullptr,用于表示空指针。它与NULL和0等传统空指针表示方法不同,是一个类型安全的空指针常量。

nullptr的类型是std::nullptr_t,它是一个特殊的整数类型,用于表示空指针。在C++11中,可以使用nullptr来初始化或赋值给任何指针类型,包括原生指针、智能指针和成员指针等。

int* ptr = nullptr; // 使用nullptr初始化原生指针
std::shared_ptr<int> sp = nullptr; // 使用nullptr初始化智能指针
MyClass obj;
obj.setPointer(nullptr); // 使用nullptr赋值给成员指针

需要注意的是,在使用nullptr时,应该尽量避免将其与NULL或0混淆。因为NULL和0在某些情况下可能会被编译器识别为整型字面量,从而导致意外的结果。

2.3.6 constexpr

constexpr 关键字的功能是使指定的常量表达式获得在程序编译阶段计算出结果的能力,而不必等到程序运行阶段。C++ 11 标准中,constexpr 可用于修饰普通变量、函数(包括模板函数)以及类的构造函数。

常量表达式(const experssion)是指:

(1)值不会改变

(2)在编译过程就能得到计算结果的表达式。

constexpr和const的区别:

两者都代表可读,

const只表示read only的语义,只保证了运行时不可以被修改,但它修饰的仍然有可能是个动态变量,

而constexpr修饰的才是真正的常量,它会在编译期间就会被计算出来,整个运行过程中都不可以被改变,constexpr可以用于修饰函数,这个函数的返回值会尽可能在编译期间被计算出来当作一个常量,但是如果编译期间此函数不能被计算出来,那它就会当作一个普通函数被处理。

#include<iostream>
using namespace std;constexpr int func(int i) {return i + 1;
}int main() {int i = 2;func(i);// 普通函数func(2);// 编译期间就会被计算出来
}

2.3.7 explicit

在C++11中,explicit关键字用于修饰类的构造函数,表示该构造函数是显式的,不能进行隐式类型转换。

class MyClass {
public:explicit MyClass(int a) {} // 显式构造函数
};// MyClass obj = 10; // 错误,不能进行隐式类型转换
MyClass obj2(10); // 正确,需要显示调用构造函数

使用explicit关键字可以防止因编译器自动进行类型转换而导致的错误或不必要的代码生成。同时,它也可以强制程序员显式地指定参数类型,提高代码的可读性和安全性。

2.3.8 final和override

在C++11中,final和override是两个关键字,用于修饰类的成员函数。

final关键字表示该成员函数不能被继承或覆盖。当一个基类的虚函数被声明为final时,派生类不能再重写该函数。这可以防止派生类无意中修改基类的行为。

override关键字用于显式地指示派生类中的虚函数是覆盖基类中的虚函数。如果没有使用override关键字,编译器可能会因为函数签名不匹配而报错。使用override关键字可以避免这种情况的发生,提高代码的可读性和安全性。

class Base {
public:virtual void func() final {} // 基类中的虚函数被声明为final,不能被覆盖
};class Derived : public Base {
public:void func() override {} // 派生类中的虚函数覆盖了基类中的虚函数,并使用了override关键字
};

2.3.9 右值引用

C++ 中的右值引用(Rvalue reference)是一种引用类型,它用于绑定到临时对象或将要被移动的对象(右值)。通过右值引用,我们可以对右值进行有效的操作,如移动语义和完美转发。

右值引用的语法是在类型后面加上 &&,例如 int&& 表示一个右值引用到 int 类型的对象。右值引用只能绑定到右值,不能绑定到左值。

右值引用主要有两个重要的应用场景:移动语义和完美转发。

【1】移动语义: 右值引用使得我们可以实现高效的资源管理,尤其是在处理动态分配的内存或大型对象时。通过移动语义,我们可以将资源从一个对象转移到另一个对象,避免了不必要的拷贝开销。
通过定义移动构造函数和移动赋值运算符,并使用右值引用参数,可以实现对资源的高效转移。移动构造函数用于在构造对象时从临时或将要被销毁的对象中“窃取”资源,移动赋值运算符用于在对象已存在时将资源从右值赋值给对象。这样,在资源转移完成后,原始对象就不再拥有该资源,而新对象拥有该资源,避免了多余的内存分配和拷贝操作。

【2】完美转发: 完美转发是指在函数模板中保持参数的值类别(左值或右值)并将其转发到其他函数,以实现泛型编程中的通用参数传递。通过使用右值引用和模板参数推导,可以实现参数类型的自动推导和类型保持。
在函数模板中使用右值引用参数可以接收右值和左值,并保持参数的原始类型。结合 std::forward 可以实现完美转发,将参数以原始类型转发到其他函数。这样,在调用模板函数时,参数的值类别被保留,从而选择正确的函数进行处理。

右值引用在 C++11 中引入,它的出现在很大程度上优化了资源管理和提升了代码的性能。它为移动语义和完美转发提供了重要的基础,并在现代 C++ 开发中广泛应用。

2.3.10 移动语义

C++11 引入了移动语义(Move Semantics)的概念,旨在提高对象的性能和效率。移动语义通过转移资源所有权,避免不必要的拷贝操作,从而更高效地管理对象。

在传统的拷贝语义中,当我们将一个对象赋值给另一个对象或者作为函数参数传递时,会进行对象的拷贝操作,这包括复制所有成员变量的值、分配内存等。在某些情况下,这种拷贝操作是非常昂贵的,特别是对于大型对象或者资源密集型的操作。

移动语义通过引入右值引用(Rvalue Reference)来解决这个问题。右值引用使用 && 语法进行声明,表示一个临时对象或者即将销毁的对象。在移动语义中,我们可以将资源的所有权从一个对象转移到另一个对象,而不需要进行昂贵的拷贝操作。

在使用移动语义时,可以借助 std::move 函数将左值转换为右值引用,以便进行移动操作。下面是一个简单的示例:

#include <iostream>
#include <vector>class Person {
public:Person() {std::cout << "默认构造函数" << std::endl;// 假设需要分配大量内存或进行其他资源密集型操作}Person(const Person& other) {std::cout << "拷贝构造函数" << std::endl;// 实现对象的拷贝操作}Person(Person&& other) {std::cout << "移动语义构造函数" << std::endl;// 实现对象的移动操作}
};int main() {Person person1;  // 调用默认构造函数Person person2(person1);  // 调用拷贝构造函数,拷贝 person1 的值到 person2Person person3(std::move(person1));  // 调用移动构造函数,将 person1 的值转移到 person3return 0;
}

通过移动语义,我们可以避免不必要的拷贝操作,提高代码的性能和效率。特别是对于容器类(如 std::vectorstd::string)或动态分配的资源,利用移动语义可以显著降低内存分配和复制的开销。

需要注意的是,移动构造函数的实现通常是将源对象指针设置为 nullptr,以确保在析构时不会释放已经被转移的资源。此外,移动构造函数和拷贝构造函数应该遵循特定的语义规范,以确保正确、可预期的行为。

2.3.11 完美转发

完美转发(perfect forwarding)是 C++ 中用于保持传递参数类型和转发函数调用的机制。它通常与模板和右值引用一起使用,以实现泛型编程中的参数传递。

在传统的函数调用中,如果我们想要将一个函数的参数传递给另一个函数,通常可以直接通过值传递或引用传递来实现。但是问题在于,当我们希望将参数以原始类型(值类型或引用类型)传递给另一个函数时,需要显式指定参数的类型,而无法自动推导。

完美转发解决了这个问题,它允许我们在一层函数中接收参数,并将其转发到另一层函数,同时保持参数的原始类型。这样就可以实现泛型编程中的通用参数传递,不再需要手动指定参数类型。

完美转发的核心是使用了两种类型:通用引用和 std::forward

  1. 通用引用(Universal Reference)是指使用了 auto&& 或模板参数推导结合引用折叠规则的引用类型。通用引用可以绑定到左值或右值,并保持参数的原始类型。
  2. std::forward 是一个条件转发的工具函数,用于根据参数的原始类型,选择性地将参数转发为左值引用或右值引用。它的使用场景通常是在模板函数或模板类中,用于将参数转发到另一个函数。

一个例子:

#include <iostream>
#include "common.h"using namespace std;template<typename F ,typename T, typename U>
void testFun(F f, T && t1, U && t2)
{f(std::forward<T>(t1),std::forward<U>(t2));
}void gu_y(int && t1, int & t2) // 接收左值和右值
{cout << t1+t2 << endl;
}int main(int argc, char *argv[])
{{__LOG__("完美转发");int per1 = 23;int per2 = 34;cout << "左右: ";testFun(gu_y,23,per2);// 传入右值和左值}return 0;
}

        运行结果:

对于T && t1 ,t1始终是变量是左值,因此在传入函数gu_y()时,需要将一个左值转换成右值,又由于这是一个模板函数,因此不可以使用移动语义将其直接固定为右值,所以提出了完美转发。

🌈我的分享也就到此结束啦🌈
如果我的分享也能对你有帮助,那就太好了!
若有不足,还请大家多多指正,我们一起学习交流!
📢未来的富豪们:点赞👍→收藏⭐→关注🔍
感谢大家的观看和支持!最后,☺祝愿大家每天有钱赚!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/583141.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Kubernetes 学习总结(41)—— 云原生容器网络详解

背景 随着网络技术的发展&#xff0c;网络的虚拟化程度越来越高&#xff0c;特别是云原生网络&#xff0c;叠加了物理网络、虚机网络和容器网络&#xff0c;数据包在网络 OSI 七层网络模型、TCP/IP 五层网络模型的不同网络层进行封包、转发和解包。网络数据包跨主机网络、容器…

IntelliJ IDE 插件开发 | (四)开发一个时间管理大师插件

系列文章 IntelliJ IDE 插件开发 |&#xff08;一&#xff09;快速入门IntelliJ IDE 插件开发 |&#xff08;二&#xff09;UI 界面与数据持久化IntelliJ IDE 插件开发 |&#xff08;三&#xff09;消息通知与事件监听IntelliJ IDE 插件开发 |&#xff08;四&#xff09;开发一…

未来编程语言什么样?编译解释兼方为王

○、编程语言的未来&#xff1f; 随着科技的飞速发展&#xff0c;编程语言在计算机领域中扮演着至关重要的角色。它们是软件开发的核心&#xff0c;为程序员提供了与机器沟通的桥梁。那么&#xff0c;在技术不断进步的未来&#xff0c;编程语言的走向又将如何呢&#xff1f; …

【银行测试】金融银行-理财项目面试/分析总结(二)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 银行理财相关的项…

CSS 纵向扩展动画

上干货 <template><!-- mouseenter"startAnimation" 表示在鼠标进入元素时触发 startAnimation 方法。mouseleave"stopAnimation" 表示在鼠标离开元素时触发 stopAnimation 方法。 --><!-- 容器元素 --><div class"container&q…

Spark编程范例:Word Count示例解析

Apache Spark是一个强大的分布式计算框架&#xff0c;用于处理大规模数据。Word Count示例是Spark入门教程中的经典示例&#xff0c;旨在展示如何使用Spark来进行简单的文本处理和数据分析。本文将深入解析Word Count示例&#xff0c;以帮助大家更好地理解Spark的基本概念和编程…

关于Echarts的重要属性总结

概要 1.设置图例字体颜色&#xff1a; legend: {//添加位置如下textStyle: {color: #fff // 设置图例文字颜色为白色}} 2.设置序列颜色&#xff1a; series: [{ // 添加位置如下itemStyle: { color: #FFA500 // 设置序列Series颜色}] 3.设置坐标轴单位&#xff1a; xAx…

【Shell编程练习】通过位置变量创建 Linux 系统账户及密码

系列文章目录 输出Hello World 系列文章目录位置变量代码实现运行结果 位置变量 位置变量将以数字方式对变量进行命名&#xff0c;可将命令行参数的值存储到脚本中。要从命令行、函数或脚本执行等处传递参数时&#xff0c;就需要在 Shell 脚本中使用位置参数变量。下表为常用…

LVM逻辑卷与扩容

目录 一.LVM&#xff1a; 1.什么是LVM&#xff1a; 2.LVM的基本核心组件&#xff1a; 3.LVM的基本命令&#xff1a; 二.逻辑卷的创建&#xff1a; 第一步&#xff0c;我们先要为虚拟机添加硬盘 然后我们要添加依赖包 然后我们要进行磁盘分区 再添加好分区后&#xff0…

测试C#使用OpenCvSharp从摄像头获取图片

OpenCvSharp也支持获取摄像头数据&#xff0c;不同于之前测试AForge时使用AForge控件显示摄像头数据流并从中截图图片&#xff0c;OpenCvSharp中显示摄像头数据流需要周期性地从摄像头中截取图片并显示在指定控件中。本文学习C#使用OpenCvSharp从摄像头获取图片的基本方式。  …

MybatisX逆向工程方法

官方文档链接&#xff1a;MybatisX快速开发插件 | MyBatis-Plus (baomidou.com) 使用MybatisX可以快速生成mapper文件&#xff0c;实体类和service及实现 效果 方法&#xff1a;首先下载mybatisX插件 然后创建数据库信息 然后选中表&#xff0c;右键&#xff0c;点击Mybatis…

知识积累 电源模块的介绍

1. 电源模块是什么 电源模块是一种设备或组件&#xff0c;用于提供电能供应给其他电子设备或系统。它通常包括电源转换、稳压、过流保护等功能&#xff0c;以确保输出的电能在一定电压和电流范围内稳定可靠。电源模块可以是线性电源或开关电源&#xff0c;具体类型取决于其工作…

Linux中的gcc\g++使用

文章目录 gcc\g的使用预处理编译汇编链接函数库gcc选项 gcc\g的使用 这里我们需要知道gcc和g实际上是对应的c语言和c编译器&#xff0c;而其他的Java&#xff08;半解释型&#xff09;&#xff0c;PHP&#xff0c;Python等语言实际上是解释型语言&#xff0c;因此我们经常能听…

2013年第二届数学建模国际赛小美赛B题寄居蟹进化出人类的就业模式解题全过程文档及程序

2013年第二届数学建模国际赛小美赛 B题 寄居蟹进化出人类的就业模式 原题再现&#xff1a; 寄居蟹是美国最受欢迎的宠物品种&#xff0c;依靠其他动物的壳来保护。剥去寄居蟹的壳&#xff0c;你会看到它柔软、粉红色的腹部卷曲在头状的蕨类叶子后面。大多数寄居蟹喜欢蜗牛壳&…

【iOS安全】越狱iOS安装Frida | 安装指定版本Frida

越狱iPhone安装Frida 本文的方法适用于已越狱的iPhone手机 打开Cydia&#xff0c;软件源&#xff0c;编辑&#xff08;右上角&#xff09;&#xff0c;添加&#xff08;左上角&#xff09;&#xff1a;https://build.frida.re 然后搜索Frida&#xff0c;点击安装 参考&#x…

mysql原理--MySQL基于规则的优化

设计 MySQL 的大叔依据一些规则&#xff0c;竭尽全力的把一些很糟糕的语句转换成某种可以比较高效执行的形式&#xff0c;这个过程也可以被称作 查询重写 &#xff08;就是人家觉得你写的语句不好&#xff0c;自己再重写一遍&#xff09;。 1.条件化简 我们编写的查询语句的搜…

Centos7:Jenkins+gitlab+node项目启动(2)

Centos7&#xff1a;Jenkinsgitlabnode项目启动(1) Centos7&#xff1a;Jenkinsgitlabnode项目启动(1)-CSDN博客 Centos7&#xff1a;Jenkinsgitlabnode项目启动(2) Centos7&#xff1a;Jenkinsgitlabnode项目启动(2)-CSDN博客 Centos7&#xff1a;Jenkinsgitlabnode项目启…

第十四章 Sentinel实现熔断与限流

Sentinel实现熔断与限流 gitee&#xff1a;springcloud_study: springcloud&#xff1a;服务集群、注册中心、配置中心&#xff08;热更新&#xff09;、服务网关&#xff08;校验、路由、负载均衡&#xff09;、分布式缓存、分布式搜索、消息队列&#xff08;异步通信&#x…

EXPLORING DIFFUSION MODELS FOR UNSUPERVISED VIDEO ANOMALY DETECTION 论文阅读

EXPLORING DIFFUSION MODELS FOR UNSUPERVISED VIDEO ANOMALY DETECTION 论文阅读 ABSTRACT1. INTRODUCTION2. RELATEDWORK3. METHOD4. EXPERIMENTAL ANALYSIS AND RESULTS4.1. Comparisons with State-Of-The-Art (SOTA)4.2. Diffusion Model Analysis4.3. Qualitative Result…

C练习——银行存款

题目&#xff1a;设银行定期存款的年利率 rate为2.25%,已知存款期为n年&#xff0c;存款本金为capital 元,试编程计算并输出n年后本利之和deposit。 解析&#xff1a;利息本金*利率&#xff0c;下一年的本金又是是今年的本利之和 逻辑&#xff1a;注意浮点数&#xff0c;导入…