【C++】C++11特性
1、列表初始化
1.1 {}初始化
C++11引入了初始化列表(Initializer lists)的特性,通过花括号{}
可以方便地初始化数组、容器和自定义类型的成员变量。这种语法可以一次性地指定多个初始值,而不需要显式地编写多个赋值语句。
下面是一些示例用法:
- 初始化数组:
int arr[] = {1, 2, 3, 4, 5};
- 初始化容器(如std::vector):
std::vector<int> vec = {10, 20, 30, 40, 50};
- 初始化自定义类型的成员变量:
class Point {
public:
int x;
int y;
};
Point p = {10, 20};
使用初始化列表的语法可以更直观地初始化多个值,并且在某些情况下可以提高代码的可读性和效率。
1.2 std::initializer_list
std::initializer_list
是C++11引入的一个模板类,用于简化和处理初始化列表。它定义在头文件<initializer_list>
中,并提供了一种方便的方式来传递和操作初始化列表。
std::initializer_list
允许以花括号{}
中的值序列作为参数传递给函数或构造函数。它的语法类似于标准容器,但不同于容器,std::initializer_list
本身并不拥有元素,而只是提供了对初始化列表中元素的访问。
下面是一个简单的示例,展示了如何使用std::initializer_list
:
#include <initializer_list>
#include <iostream>
void printValues(std::initializer_list<int> values) {
for (auto it = values.begin(); it != values.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
}
int main() {
printValues({1, 2, 3, 4, 5});
return 0;
}
在上面的例子中,printValues
函数接受一个std::initializer_list<int>
类型的参数,然后遍历并打印出其中的值。在main
函数中,我们使用花括号创建一个初始化列表,并将其作为参数传递给printValues
函数。
std::initializer_list
还可以用于自定义类型的构造函数,使得自定义类型可以方便地接受和处理初始化列表作为参数。
需要注意的是,std::initializer_list
中的元素是常量,因此不能修改其中的值。如果需要修改元素,可以将其拷贝到其他容器或数据结构中。
std::initializer_list使用场景:
std::initializer_list一般是作为构造函数的参数,C++11对STL中的不少容器就增加std::initializer_list作为参数的构造函数,这样初始化容器对象就更方便了。也可以作为operator= 的参数,这样就可以用大括号赋值。
2、声明
2.1 auto
在C++11中,auto
是一个关键字,用于进行自动类型推导。使用auto
关键字可以使编译器根据变量的初始化表达式来自动推导出变量的类型,而无需显式地指定类型。
下面是一个示例,展示了auto
的使用:
auto num = 42; // 自动推导为 int
auto name = "John"; // 自动推导为 const char*
auto result = computeResult(); // 自动推导为函数返回类型
在上述示例中,变量num
的类型被自动推导为int
,变量name
的类型被自动推导为const char*
,而变量result
的类型被自动推导为某个函数的返回类型。
auto
的使用可以简化代码,特别是当变量的类型较为复杂或难以表达时,可以避免手动编写冗长的类型声明。此外,它还可以与其他C++11特性(如范围for循环和Lambda表达式)结合使用,提供更简洁和灵活的编程方式。
需要注意的是,auto
并不是一种动态类型或者弱类型,它只是让编译器根据上下文推导出变量的类型。推导得到的类型在编译时就是确定的,不会在运行时发生类型的变化。
另外,C++14引入了decltype(auto)
的语法,结合了auto
和decltype
的特性,可以更精确地进行类型推导。这个特性允许在推导类型时保留变量的引用性质(左值或右值引用)或常量性质。
2.2 decltype
在C++中,decltype
是一个关键字,用于推导表达式的类型。它可以在编译时获取表达式的类型,并将其作为变量的声明类型或函数的返回类型。
下面是一些使用decltype
的示例:
- 推导变量的类型:
int x = 42;
decltype(x) y = x; // 推导y的类型为int
在这个示例中,使用decltype(x)
推导出变量y
的类型为int
,因为它与变量x
具有相同的类型。
- 推导表达式的类型:
int a = 10, b = 20;
decltype(a + b) result = a + b; // 推导result的类型为int
在这个示例中,decltype(a + b)
推导出表达式a + b
的类型为int
,因为它是两个int
类型相加的结果。
- 推导函数的返回类型:
int getValue() {
return 42;
}
decltype(getValue()) num = getValue(); // 推导num的类型为int
在这个示例中,decltype(getValue())
推导出函数getValue()
的返回类型为int
,因此变量num
的类型也被推导为int
。
decltype
在编写模板或需要从表达式中获取类型信息的情况下特别有用。它能够保留表达式的常量性质、引用性质和cv限定符,从而提供更准确的类型推导。
需要注意的是,decltype
并不会执行表达式,它仅仅用于获取表达式的类型。在使用时要确保表达式是有效的,并且不会产生副作用。
3、智能指针
智能指针(Smart pointers)是C++中的一种工具,用于管理动态分配的资源(如堆上的内存),以避免内存泄漏和资源泄漏等问题。智能指针提供了自动化的内存管理,使得资源的释放可以更安全、可靠和方便。
C++中提供了几种智能指针类型,其中最常用的是以下三种:
-
std::shared_ptr
:共享指针是一种引用计数智能指针,允许多个指针共享同一个对象,并在最后一个引用被释放时自动销毁对象。 -
std::unique_ptr
:独占指针是一种独占所有权的智能指针,确保只有一个指针可以访问对象。它提供了高效的移动语义,使得资源的所有权可以进行转移。 -
std::weak_ptr
:弱引用指针是一种不增加引用计数的智能指针,用于解决std::shared_ptr
可能引发的循环引用问题。它允许观察一个对象,但不会增加对象的引用计数。
使用智能指针可以避免手动释放资源的繁琐和容易出错的过程,因为它们利用了RAII(Resource Acquisition Is Initialization)的原则,在对象构造和析构的过程中自动管理资源的申请和释放。
以下是一个简单的示例,展示了智能指针的用法:
#include <memory>
#include <iostream>
int main() {
// 使用 std::unique_ptr 管理动态分配的整数
std::unique_ptr<int> ptr = std::make_unique<int>(42);
// 使用 std::shared_ptr 共享指针
std::shared_ptr<int> sharedPtr = std::make_shared<int>(10);
std::shared_ptr<int> anotherPtr = sharedPtr;
// 使用 std::weak_ptr 弱引用指针
std::weak_ptr<int> weakPtr = sharedPtr;
// 访问指针所指向的对象
std::cout << *ptr << std::endl;
std::cout << *sharedPtr << std::endl;
return 0;
}
在上述示例中,我们使用了std::unique_ptr
、std::shared_ptr
和std::weak_ptr
来管理不同类型的指针。这些智能指针会在适当的时候自动释放所管理的资源,无需手动调用delete
或delete[]
来释放内存。
智能指针是一种强大的工具,可以提高C++程序的内存安全性和可维护性。它们是现代C++中推荐使用的内存管理方式之一。
4、右值引用和移动语义
4.1 左值与右值
在C++中,左值(Lvalue)和右值(Rvalue)是表达式的两种基本分类,用于描述表达式的值类别。
-
左值(Lvalue):
- 左值是具名的对象,可以通过其标识符进行访问和修改。
- 左值可以出现在赋值语句的左侧和右侧,可以被取地址。
- 示例:变量、数组元素、对象的成员等。
-
右值(Rvalue):
- 右值是指临时的、没有持久性的值,通常是表达式的计算结果。
- 右值只能出现在赋值语句的右侧,不能被取地址。
- 示例:字面量(如整数、浮点数、字符串)、临时对象、表达式的结果等。
简单来说,左值是具名的、有持久性的对象,可以被多次引用和修改;而右值是临时的、短暂的值,一般用于计算或传递给函数等临时操作。
在C++11之后,引入了右值引用(Rvalue reference)和移动语义的概念,允许对右值进行特殊的操作和优化,如移动语义的使用、避免不必要的复制等,以提高性能和资源的有效利用。
4.2 引用举例
左值引用(Lvalue reference)和右值引用(Rvalue reference)是C++中用于引用类型的两种引用方式。
左值引用(Lvalue reference)用于绑定到左值,即可以标识一个具名的对象。左值引用使用单个&
符号表示,例如:
int x = 10; // x是一个左值
int& lvalueRef = x; // 左值引用绑定到x
在上述示例中,lvalueRef
是一个左值引用,它绑定到了变量x
,因此可以通过lvalueRef
修改x
的值。
右值引用(Rvalue reference)用于绑定到右值,即临时对象、字面量或表达式的结果。右值引用使用双个&&
符号表示,例如:
int&& rvalueRef = 42; // 右值引用绑定到临时对象
在上述示例中,rvalueRef
是一个右值引用,它绑定到了临时对象42
。右值引用通常用于支持移动语义,允许高效地转移资源的所有权或实现移动构造函数和移动赋值运算符。
右值引用还与完美转发(perfect forwarding)相关,通过使用右值引用可以保持传递给函数的实参的值类别(左值或右值)不变,从而实现参数的完美转发。
总结起来,左值引用用于绑定左值,右值引用用于绑定右值。左值引用是对对象的别名,而右值引用则提供了对临时对象和可移动对象的特殊引用方式,用于实现高效的资源管理和完美转发。
左值引用总结:
- 左值引用只能引用左值,不能引用右值。
- 但是const左值引用既可引用左值,也可引用右值。
右值引用总结:
- 右值引用只能右值,不能引用左值。
- 但是右值引用可以move以后的左值。
4.3 移动构造
移动构造(Move construction)是C++中的一个特性,用于通过使用右值引用(Rvalue reference)来高效地从一个对象“移动”资源到另一个对象。
移动构造函数通常用于在对象的构造过程中,从一个临时对象或即将销毁的对象中“窃取”资源,而不是进行昂贵的复制操作。这对于具有大量数据或动态分配内存的对象特别有用,可以避免不必要的内存复制和分配。
移动构造函数的定义遵循以下形式:
ClassName(ClassName&& other) noexcept
{
// 从other中移动资源到当前对象
// 对other进行必要的清理或标记,确保其析构不会造成资源重复释放
}
在移动构造函数中,参数类型为ClassName&&
,表示一个右值引用。通过使用右值引用,可以确保移动构造函数只会绑定到右值(如临时对象或即将销毁的对象)上,而不会绑定到左值。
移动构造函数的实现通常涉及以下步骤:
-
从源对象中窃取资源:例如,可以通过将指针或句柄的所有权从源对象转移到目标对象,而无需进行昂贵的数据复制。
-
对源对象进行适当的清理或标记:例如,将源对象置于一种无效或已被移动状态,以避免其析构过程中重复释放资源。
移动构造函数常常与右值引用和移动语义搭配使用,以提高性能和资源的有效利用。它是现代C++中优化对象构造和资源管理的重要特性之一。
4.4 移动语义
移动语义(Move semantics)是C++中的一个重要概念,用于优化对象的拷贝操作,特别是对于具有资源所有权的对象。
传统的拷贝语义会对对象进行完全的复制,包括复制底层资源,这在某些情况下可能是低效的。移动语义通过使用右值引用(Rvalue reference)和移动构造函数(Move constructor)来实现资源的转移,从而避免了不必要的资源复制和分配。
移动语义的主要思想是在移动对象时,尽可能地“窃取”(移动)资源,而不是进行昂贵的复制操作。对于可以移动的对象,移动语义可以提供显著的性能提升。
通过使用右值引用和移动构造函数,可以将资源的所有权从一个对象转移到另一个对象,而无需进行深层次的数据复制。移动构造函数利用右值引用绑定到右值上,并在构造过程中从源对象“窃取”资源,使得资源的所有权被转移至目标对象。
例如,考虑以下情况:
std::vector<int> createVector()
{
std::vector<int> vec;
// 假设在vec中添加大量数据
return vec; // 返回vec
}
在上述示例中,返回类型为std::vector<int>
的函数createVector()
创建了一个局部的std::vector
对象,并将其返回。在传统的拷贝语义中,返回操作会对整个std::vector
对象进行复制,包括其中的数据。然而,通过使用移动语义,可以避免不必要的数据复制,而是直接将资源(内存)的所有权从局部对象转移到函数的调用方:
std::vector<int> vec = createVector(); // 使用移动语义,避免不必要的复制
在这个例子中,由于返回的对象是一个临时对象(右值),移动语义允许将其资源(内存)有效地移动到变量vec
中,而无需进行数据的复制。
移动语义不仅可以应用于容器,还可以应用于其他具有资源所有权的对象,如动态分配的内存、文件句柄等。它提供了一种高效的资源管理方式,使得程序可以更好地利用资源并避免不必要的开销。
4.5 使用场景
左值引用和右值引用在不同的场景下有不同的使用方式和目的。
左值引用的主要使用场景包括:
-
传统的对象引用:左值引用常用于函数参数和返回类型,允许在函数中对对象进行修改或传递对象的引用。
-
容器和数据结构的访问:左值引用可以用于访问容器(如
std::vector
、std::map
)中的元素,以便进行修改或检索。 -
对象的拷贝和赋值:左值引用常用于对象的拷贝构造函数和赋值运算符的实现中。
右值引用的主要使用场景包括:
-
移动语义:右值引用允许将资源(如动态分配的内存)的所有权从一个对象转移到另一个对象,实现高效的资源管理,避免不必要的数据复制。
-
完美转发:右值引用在模板和泛型编程中广泛应用,用于实现完美转发(perfect forwarding),即将参数原封不动地传递给其他函数,保持其值类别不变。
-
临时对象的优化:右值引用可以用于优化临时对象的创建和销毁过程,避免不必要的拷贝开销。
总的来说,左值引用用于传统的对象引用和修改操作,而右值引用用于移动语义、完美转发和临时对象的优化。它们在C++中共同提供了更灵活、高效和安全的资源管理和函数调用方式。
5、可变参数模板
C++11引入了可变参数模板(Variadic templates)的特性,允许函数和类模板接受可变数量的参数。可变参数模板是一种在编译时处理不确定数量参数的方式,它为编程提供了更大的灵活性和通用性。
可变参数模板的基本语法如下:
template <typename... Args>
void myFunction(Args... args)
{
// 使用args进行操作,可以是展开、迭代或其他处理方式
}
在上述示例中,Args
是一个模板参数包(template parameter pack),用于接受任意数量的模板参数。args
是一个函数参数包(function parameter pack),用于接受相应数量的函数参数。
可变参数模板的使用方式包括:
-
展开参数包(Expanding the parameter pack):使用递归、展开等方式在编译时对参数包进行处理。例如,可以使用递归模板函数对每个参数进行操作,直到参数包为空。
-
递归实例化(Recursive instantiation):使用递归实例化来处理参数包中的每个参数。通过逐个实例化每个参数,可以实现递归展开的效果。
-
递归调用(Recursive invocation):在函数模板内部递归调用自身,以处理参数包中的每个参数。这允许对参数包中的每个参数执行相同的操作。
可变参数模板的使用非常灵活,它可以适应各种不同的需求。例如,可以定义接受不同类型参数的通用函数,或者定义递归的数据结构,以适应可变数量的元素。
以下是一个简单的示例,展示了可变参数模板的使用:
#include <iostream>
// 递归展开参数包的函数
template<typename T>
void printArguments(T arg)
{
std::cout << arg << std::endl;
}
template<typename T, typename... Args>
void printArguments(T arg, Args... args)
{
std::cout << arg << " ";
printArguments(args...); // 递归调用,展开参数包
}
int main()
{
printArguments(1, "hello", 3.14, 'c');
return 0;
}
在上述示例中,printArguments
函数使用可变参数模板,通过递归展开参数包,打印传递给函数的所有参数。运行该程序将输出:1 hello 3.14 c,每个参数都用空格分隔开来。
可变参数模板为C++中的泛型编程提供了更大的能力,使得编写通用的、可适应不确定参数数量的函数和类模板变得更加方便和灵活。
6、lambda表达式
随着C++语法的发展,人们开始觉得,每次为了实现一个algorithm算法, 都要重新去写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名, 这些都给编程者带来了极大的不便。因此,在C++11语法中出现了Lambda表达式
Lambda表达式是一种匿名函数形式,它允许在需要函数对象时,直接在代码中定义一个简洁的函数。
6.1 语法格式
Lambda表达式的基本语法如下:
[capture list](parameters) -> return_type {
// 函数体
}
其中:
-
capture list
(捕获列表)用于捕获外部变量,可以是空的、值捕获(使用变量的副本)或引用捕获(使用变量的引用)。 -
parameters
(参数列表)用于定义Lambda函数的参数。 -
return_type
(返回类型)是可选的,可以自动推导出来。
下面是一个简单的示例,展示了Lambda表达式的使用:
#include <iostream>
int main() {
int x = 42;
// Lambda表达式打印传入参数的两倍
auto printDouble = [](int num) {
std::cout << num * 2 << std::endl;
};
printDouble(x); // 输出:84
return 0;
}
在上述示例中,我们定义了一个Lambda表达式printDouble
,它接受一个整数参数num
,并打印出其两倍的值。Lambda表达式被赋值给了一个自动推导的变量printDouble
,然后我们调用该Lambda函数并传递参数x
。
Lambda表达式还可以与标准算法函数(如std::for_each
、std::transform
)等结合使用,提供一种简洁而灵活的方式来定义函数对象,避免显式编写函数或函数对象的定义。
Lambda表达式的捕获列表允许在函数体中使用外部作用域的变量,从而实现了闭包(Closure)的特性。它使得编写更为紧凑、内聚的代码成为可能,并且对于一些需要临时函数对象的场景非常方便。
6.2 捕获列表
Lambda捕获列表(Capture list)用于指定Lambda表达式中的外部变量的可见性和使用方式。捕获列表出现在Lambda表达式的开头,位于参数列表之前。
捕获列表允许Lambda表达式访问和使用外部作用域的变量,包括局部变量、函数参数、全局变量等。它提供了对这些外部变量的副本或引用的访问权限,使得Lambda函数可以在其函数体中使用这些变量。
捕获列表有以下两种主要形式:
-
值捕获(Value capture):
-
[var]
:按值捕获变量var
,在Lambda函数内部创建一个变量的副本。 -
[=]
:按值捕获所有外部变量,创建它们的副本。 -
[=, &var]
:按值捕获所有外部变量,并对变量var
进行引用捕获。 -
[&, var]
:按引用捕获所有外部变量,并对变量var
进行值捕获。
-
-
引用捕获(Reference capture):
-
[&var]
:按引用捕获变量var
,在Lambda函数内部直接使用外部变量。 -
[&]
:按引用捕获所有外部变量,直接使用它们。
-
示例:
int x = 42;
int y = 10;
// 值捕获
auto lambda1 = [x, y]() {
// 使用x和y的副本
// ...
};
// 引用捕获
auto lambda2 = [&x, &y]() {
// 直接使用x和y的引用
// ...
};
// 按引用捕获所有外部变量
auto lambda3 = [&]() {
// 直接使用所有外部变量的引用
// ...
};
// 按值捕获所有外部变量,并对x进行引用捕获
auto lambda4 = [=, &x]() {
// 使用所有外部变量的副本,并对x进行引用
// ...
};
需要注意的是,捕获列表中的变量在Lambda函数内部被视为只读(除非使用mutable
关键字),无法直接修改。对于值捕获而言,外部变量的副本在Lambda函数创建时被复制,而对于引用捕获,则直接引用外部变量。
Lambda捕获列表允许在Lambda函数体内访问外部变量,使得Lambda表达式具有了闭包(Closure)的特性,可以在其函数体内部操作和使用外部作用域的变量。这提供了一种便捷的方式来创建具有自包含状态的函数对象。
7、包装器
7.1 没有包装器
当没有使用函数包装器时,让我们看看一个简单的示例来比较结果。
考虑以下目标函数divide
,用于将两个整数相除并返回结果:
int divide(int a, int b) {
return a / b;
}
现在,我们想要实现一个包装器,用于在目标函数调用前后输出额外的信息。
使用函数包装器的情况下,可以这样实现:
#include <iostream>
template<typename Func>
void wrapper(Func func, int a, int b) {
std::cout << "Before function call" << std::endl;
int result = func(a, b);
std::cout << "After function call" << std::endl;
std::cout << "Result: " << result << std::endl;
}
int main() {
wrapper(divide, 10, 2);
return 0;
}
输出结果:
Before function call
After function call
Result: 5
在这个例子中,函数包装器wrapper
用于调用目标函数divide
,并在调用前后输出信息。它提供了前置和后置处理的功能。
如果没有使用函数包装器,而直接调用目标函数divide
,则没有额外的处理或输出信息,只有目标函数的结果:
int result = divide(10, 2);
std::cout << "Result: " << result << std::endl;
输出结果:
Result: 5
在这种情况下,缺少了包装器提供的前置和后置处理,导致无法在调用前后输出额外的信息。
通过比较这两种情况,可以看到函数包装器提供了一种方便的方式来添加额外的逻辑或修改函数行为,使得代码更具灵活性和可扩展性。
7.2 函数包装器
函数包装器(Function wrapper)是一种将函数进行封装或包装的技术,可以在函数调用的前后添加额外的逻辑、修改函数的行为或提供新的接口。
函数包装器通常通过以下几种方式实现:
-
函数指针包装器:使用函数指针来包装目标函数,并在调用前后执行额外的操作。可以通过定义一个接受函数指针的函数来实现包装器的调用。
-
函数对象包装器:使用函数对象(可调用对象)来包装目标函数,通过重载函数调用运算符
operator()
来实现包装器的逻辑。函数对象可以是普通函数对象、函数对象类或Lambda表达式。 -
Lambda表达式包装器:使用Lambda表达式来包装目标函数,并在Lambda表达式中添加额外的逻辑。Lambda表达式可以直接作为函数包装器使用。
函数包装器的常见用途包括:
- 前置和后置处理:在函数调用之前或之后执行额外的操作,如日志记录、性能统计、参数验证等。
- 异常处理:在函数调用过程中捕获异常并进行处理,提供异常安全的接口。
- 记录函数调用历史:跟踪函数的调用记录,用于调试或追踪程序执行流程。
- 功能扩展:在现有函数的基础上扩展功能,例如添加缓存、权限验证等。
以下是一个简单的示例,展示了使用函数对象(函数指针)作为函数包装器的方式:
#include <iostream>
// 包装器函数接受函数指针和参数,并在调用前后添加额外的逻辑
template<typename Func>
void wrapper(Func func, int arg)
{
std::cout << "Before function call" << std::endl;
// 调用目标函数
func(arg);
std::cout << "After function call" << std::endl;
}
// 目标函数
void myFunction(int arg)
{
std::cout << "Inside target function: " << arg << std::endl;
}
int main()
{
// 使用函数包装器调用目标函数
wrapper(myFunction, 42);
return 0;
}
在上述示例中,我们定义了一个函数包装器wrapper
,它接受一个函数指针和一个参数,并在调用目标函数之前和之后输出相应的消息。然后,我们定义了目标函数myFunction
,它接受一个整数参数并输出该参数的值。在main
函数中,我们使用函数包装器调用目标函数myFunction
,并传递参数42
。
函数包装器提供了一种灵活的方式来扩展函数的功能或修改函数的行为,可以根据具体的需求选择合适的包装器实现方式。
7.3 bind函数
std::bind
是C++标准库中的一个函数模板,用于创建一个函数对象(函数指针或函数对象)的部分应用(Partial Application)。它可以将一个可调用对象(如函数、函数指针、成员函数指针、函数对象或Lambda表达式)与一些参数进行绑定,生成一个新的函数对象,这个新的函数对象可以接受更少的参数或具有固定的参数值。
std::bind
函数的基本语法如下:
#include <functional>
std::bind(function, arg1, arg2, ...);
其中,function
是需要进行绑定的可调用对象,arg1, arg2, ...
是要绑定的参数。
以下是一个简单的示例,演示了如何使用std::bind
进行部分应用:
#include <iostream>
#include <functional>
void foo(int a, int b, int c) {
std::cout << "a: " << a << ", b: " << b << ", c: " << c << std::endl;
}
int main() {
auto boundFunc = std::bind(foo, 10, std::placeholders::_1, 20);
boundFunc(30); // 调用绑定后的函数对象
return 0;
}
输出结果:
a: 10, b: 30, c: 20
在这个例子中,我们定义了一个函数foo
,它接受三个整数参数。使用std::bind
,我们将其中的第一个参数绑定为10,第三个参数绑定为20,而第二个参数则使用了占位符std::placeholders::_1
,表示在调用时提供。
通过std::bind
生成的boundFunc
,可以看到参数b
已经被固定为30,然后通过调用boundFunc(30)
来执行部分应用后的函数。最终的输出结果为"a: 10, b: 30, c: 20"。
std::bind
的灵活性使得我们可以对函数进行更多形式的部分应用,包括固定多个参数、指定参数顺序、使用占位符等。这样可以方便地创建新的函数对象,适应不同的调用需求。
8、线程库
C++标准库提供了线程库(Thread Library)用于多线程编程。线程库包含在<thread>
头文件中,并提供了创建、管理和同步线程的相关功能。
- 线程是操作系统中的一个概念,线程对象可以关联一个线程,用来控制线程以及获取线程的 状态。
- 当创建一个线程对象后,没有提供线程函数,该对象实际没有对应任何线程。
- 当创建一个线程对象后,并且给线程关联线程函数,该线程就被启动,与主线程一起运行。 线程函数一般情况下可按照以下三种方式提供: 函数指针 lambda表达式 函数对象
- thread类是防拷贝的,不允许拷贝构造以及赋值,但是可以移动构造和移动赋值,即将一个 线程对象关联线程的状态转移给其他线程对象,转移期间不意向线程的执行。
- 可以通过jionable()函数判断线程是否是有效的,如果是以下任意情况,则线程无效:采用无参构造函数构造的线程对象 线程对象的状态已经转移给其他线程对象 线程已经调用jion或者detach结束
下面是线程库中常用的一些类和函数:
-
std::thread
:代表一个线程对象,可用于创建和管理线程。可以通过传递可调用对象(函数、函数指针、Lambda表达式等)作为参数来创建线程。 -
std::this_thread
:提供了一些与当前线程相关的操作,如sleep_for
(让当前线程休眠一段时间)、yield
(使当前线程放弃剩余时间片)等。 -
std::mutex
:提供了互斥锁(Mutex)的功能,用于保护共享数据的访问,确保在同一时间只有一个线程能够访问被锁定的数据。 -
std::condition_variable
:条件变量(Condition Variable)用于线程间的同步和通信。它可以阻塞一个或多个线程,直到某个条件满足,并在条件满足时通知等待的线程继续执行。 -
std::atomic
:提供了原子操作的类型,用于在多线程环境中对共享数据进行原子操作,避免竞态条件(Race Condition)。 -
std::future
和std::promise
:用于在异步编程中获取线程函数的返回值。std::future
表示一个可能还未完成的异步操作的结果,而std::promise
则用于设置异步操作的结果。文章来源:https://www.toymoban.com/news/detail-561984.html
除了上述类和函数,线程库还提供了其他的工具和辅助函数,以支持多线程编程的各个方面,如线程的创建和销毁、线程的同步与互斥、原子操作、线程局部存储等。文章来源地址https://www.toymoban.com/news/detail-561984.html
到了这里,关于【C++】C++11特性的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!