C++11特性
Move semantics 移动语义
[!tip]
在 C++98 中,所有的对象拷贝都使用了 拷贝构造函数 或 拷贝赋值运算符,这通常需要深拷贝资源,例如动态分配的内存或文件句柄。这会导致性能开销,尤其是对于临时对象而言(如函数返回值)。
为了解决这个问题,C++11 引入了移动语义,通过区分“拷贝”和“移动”两种语义,大幅提高性能
左值和右值
- 左值(Lvalue): 指向内存中的某个具体位置,具有持久性
- 示例:变量名
int a = 10; a
是左值
- 示例:变量名
- 右值(Rvalue): 临时值,通常是没有持久性的
- 示例:字面值
10
或临时对象a + b
的结果
- 示例:字面值
C++11 中,右值被细化为 纯右值(prvalue) 和 亡值(xvalue),其中亡值用于表示即将失去所有权的对象(如返回值)
移动构造函数和移动赋值运算符
- 移动构造函数:允许从另一个对象中“窃取”资源,而不是复制资源。
- 移动赋值运算符:将一个对象的资源转移给另一个对象,而不需要深拷贝。
class MyClass {
int* data;
public:
// 构造函数
MyClass(int val) : data(new int(val)) {}
// 移动构造函数
MyClass(MyClass&& other) noexcept : data(other.data) {
other.data = nullptr; // 释放所有权
}
// 移动赋值运算符
MyClass& operator=(MyClass&& other) noexcept {
if (this != &other) {
delete data; // 释放原有资源
data = other.data; // 转移所有权
other.data = nullptr;
}
return *this;
}
~MyClass() { delete data; }
};
标准库函数
C++11
Rvalue references 右值引用
C++11 新增了一种类型引用:右值引用,使用 &&
表示,右值引用只绑定到右值。
int x = 0; // `x` is an lvalue of type `int`
int& xl = x; // `xl` is an lvalue of type `int&`
int&& xr = x; // compiler error -- `x` is an lvalue
int&& xr2 = 0; // `xr2` is an lvalue of type `int&&` -- binds to the rvalue temporary, `0`
void f(int& x) {}
void f(int&& x) {}
f(x); // calls f(int&)
f(xl); // calls f(int&)
f(3); // calls f(int&&)
f(std::move(x)); // calls f(int&&)
f(xr2); // calls f(int&)
f(std::move(xr2)); // calls f(int&& x)
右值引用的主要目的是:
- 接收和操作右值(如临时对象)。
- 实现 移动语义 和 完美转发。
Forwarding references 转发引用
也被称为Universal References(万能引用、通用引用),与std::forward
一起使用实现完美转发(Perfect Forwarding。转发引用是通过语法 T&&
创建的,其中 T
是模板类型参数,或使用 auto&&
主要作用是:根据传入参数的值类别(左值或右值)保持其原始类别,从而正确地传递给另一个函数(例如,左值保持为左值,临时对象作为右值转发)
转发引用允许引用根据类型绑定到左值或右值。转发引用遵循引用折叠规则:
T& &
变成T&
T& &&
变成T&
T&& &
变成T&
T&& &&
变成T&&
auto
类型推导与左值和右值:
int x = 0; // `x` is an lvalue of type `int`
auto&& al = x; // `al` is an lvalue of type `int&` -- binds to the lvalue, `x`
auto&& ar = 0; // `ar` is an lvalue of type `int&&` -- binds to the rvalue temporary, `0`
带有左值和右值的模板类型参数推导:
// Since C++14 or later:
void f(auto&& t) {
// ...
}
// Since C++11 or later:
template <typename T>
void f(T&& t) {
// ...
}
int x = 0;
f(0); // T is int, deduces as f(int &&) => f(int&&)
f(x); // T is int&, deduces as f(int& &&) => f(int&)
int& y = x;
f(y); // T is int&, deduces as f(int& &&) => f(int&)
int&& z = 0; // NOTE: `z` is an lvalue with type `int&&`.
f(z); // T is int&, deduces as f(int& &&) => f(int&)
f(std::move(z)); // T is int, deduces as f(int &&) => f(int&&)
[!note]
有
int x = 10
,为什么int&& y = x
会出错,而auto&& y = x
却没有问题?
int && y = x
中,x是一个左值,而y是一个右值引用,右值引用只能绑定右值auto&& y = x
中,auto&&
是一个万能引用,x
是左值时,其会自动推导为int&
,x
是右值时,其会自动推到为int&&
,所以该式子会自动推导为int& y = x
标准库函数
C++11
用于移动语义的特殊成员函数
复制构造函数和复制赋值运算符在进行复制时被调用,而随着 C++11 引入移动语义,现在有了用于移动的移动构造函数和移动赋值运算符。目的是提高性能,特别是当操作涉及临时对象时
移动构造函数
移动构造函数的作用是“窃取”另一个对象的资源,而不是复制资源,从而避免了不必要的深拷贝操作。
特点:
- 接受一个右值引用(
T&&
)作为参数。 - 通常会转移资源的所有权。
- 被移动的对象(源对象)会进入“有效但无用”的状态(如将指针置为
nullptr
)
#include <iostream>
#include <utility> // for std::move
class MyClass {
int* data;
public:
// 构造函数
MyClass(int val) : data(new int(val)) {
std::cout << "Constructed with value: " << val << std::endl;
}
// 移动构造函数
MyClass(MyClass&& other) noexcept : data(other.data) {
other.data = nullptr; // 释放源对象的所有权
std::cout << "Moved!" << std::endl;
}
// 析构函数
~MyClass() {
if (data) {
std::cout << "Destroyed: " << *data << std::endl;
delete data;
} else {
std::cout << "Destroyed empty object." << std::endl;
}
}
};
int main() {
MyClass obj1(10); // 正常构造
MyClass obj2(std::move(obj1)); // 使用移动构造
return 0;
}
Variadic templates 可变参数模板
...
语法创建一个参数包或展开一个参数包。它允许模板接受可变数量的参数。这是实现 泛型编程 和简化代码的重要特性
template<typename... Args>
void function(Args... args) {
// ...
}
...
(省略号):Args...
表示类型参数包,可以接收零个或多个类型。args...
表示非类型参数包,可以接收零个或多个值。
- 展开参数包:
- 参数包需要展开(使用
...
),否则无法直接操作其内容
- 参数包需要展开(使用
示例:
#include <iostream>
template<typename... Args>
void print(Args... args) {
(std::cout << ... << args) << "
"; // 参数包展开
}
int main() {
print(1, 2, 3, "Hello", 4.5); // 打印: 123Hello4.5
return 0;
}
参数包展开方式
- 递归展开(C++11/C++14)
#include <iostream> // 基础函数:零参数时的终止条件 void print() { std::cout << "End of arguments. "; } // 可变参数模板 template<typename T, typename... Args> void print(T first, Args... rest) { std::cout << first << " "; // 打印第一个参数 print(rest...); // 递归调用,展开剩余参数 } int main() { print(1, "Hello", 3.14, "World"); return 0; }
- 折叠表达式(C++17)
#include <iostream> template<typename... Args> void print(Args... args) { ((std::cout << args << " "), ...) << " "; // 左折叠 } int main() { print(1, "Hello", 3.14, "World"); return 0; }
折叠表达式的形式:
- 一元左折叠:
(... op expor)
- 一元右折叠:
(expr op ...)
- 二元左折叠:
(init op ... op expr)
- 二元右折叠:
(expr op ... op init)
应用场景
-
实现通用打印函数
#include <iostream> template<typename... Args> void log(const std::string& prefix, Args... args) { std::cout << prefix << ": "; (std::cout << ... << args) << " "; // 折叠表达式 } int main() { log("Info", "Hello", ", ", "World", "!", 123); log("Error", "An error occurred: ", 404); return 0; }
-
转发参数
在 C++11 中,可变参数模板与
std::forward
结合,可实现 完美转发:#include <iostream> #include <utility> void display(int x) { std::cout << "Int: " << x << " "; } void display(const std::string& str) { std::cout << "String: " << str << " "; } template<typename... Args> void forwarder(Args&&... args) { (display(std::forward<Args>(args)), ...); // 完美转发 } int main() { int x = 42; std::string str = "Hello"; forwarder(x, str, 100, "World"); return 0; }
-
构造函数的可变参数模板
可变参数模板常用于实现容器类的构造函数,支持任意数量的初始化参数:
#include <iostream> #include <vector> class MyContainer { std::vector<int> data; public: template<typename... Args> MyContainer(Args... args) : data{args...} {} void print() const { for (auto x : data) { std::cout << x << " "; } std::cout << " "; } }; int main() { MyContainer c(1, 2, 3, 4, 5); c.print(); // 输出: 1 2 3 4 5 return 0; }
初始化列表
通过花括号 {} 提供一组值,并且这些值可以自动转换为 std::initializer_list 对象
特性:
- 不可修改:
- std::initializer_list 提供只读访问(begin()、end())。
- 无法修改其元素。
- 轻量级:
- 它是一个轻量级对象,通常由编译器管理其生命周期。
- 适配统一的初始化语法:
- 支持统一的列表初始化(如 {})语法。
应用场景
-
构造函数的初始化
#include <iostream> #include <initializer_list> class MyClass { std::vector<int> data; public: MyClass(std::initializer_list<int> list) { std::cout << "Initialized with: "; for (auto value : list) { std::cout << value << " "; } std::cout << std::endl; } // 传统构造函数 MyClass(int n, int value) : data(n, value) { std::cout << "Initialized with size and value. "; } }; int main() { MyClass obj1{1, 2, 3, 4}; // 使用初始化列表 MyClass obj2{10, 20}; // 使用另一个初始化列表 MyClass obj2(5, 10); // 使用传统方法初始化 return 0; }
-
函数参数的初始化
#include <iostream> #include <initializer_list> void print(std::initializer_list<std::string> list) { for (const auto& item : list) { std::cout << item << " "; } std::cout << std::endl; } int main() { print({"Hello", "World", "!"}); // 使用初始化列表 return 0; }
-
容器初始化
#include <iostream> #include <vector> #include <set> int main() { std::vector<int> vec = {1, 2, 3, 4}; // 初始化 std::vector std::set<std::string> s = {"apple", "banana", "cherry"}; // 初始化 std::set for (auto v : vec) std::cout << v << " "; std::cout << std::endl; for (auto item : s) std::cout << item << " "; return 0; }
特性 | std::initializer_list |
普通数组 | 变长模板参数 |
---|---|---|---|
语法 | {} |
{} |
template<typename... Args> |
是否支持动态大小 | 固定大小 | 固定大小 | 支持 |
元素类型 | 必须相同 | 必须相同 | 可不同 |
是否提供容器接口 | 是 | 否 | 否 |
常用场景 | 容器初始化、构造函数 | 数组初始化 | 函数调用 |
静态断言
static_assert
是 C++11 引入的一种编译期断言工具,用于在编译时检查某些条件是否满足。如果条件不满足,编译器会报错并停止编译。
static_assert(constexpr_condition, message);
constexpr_condition
:
- 一个编译期可计算的布尔表达式。
- 必须是
true
,否则编译器会报错。
message
:
- (可选)当断言失败时,编译器输出的错误消息。
应用场景
-
类型属性检查
确保某些类型满足特定条件,例如大小、对齐方式、是否是整数类型等
template<typename T> void checkType() { static_assert(std::is_integral<T>::value, "T must be an integral type"); } int main() { checkType<int>(); // OK checkType<double>(); // 编译失败:T must be an integral type return 0; }
-
构造编译期约束
在编译期对模板参数进行检查,防止传入不符合条件的类型或值。
template<typename T> struct MyStruct { static_assert(sizeof(T) <= 4, "T must be at most 4 bytes"); }; MyStruct<int> obj; // OK MyStruct<double> obj; // 编译失败:T must be at most 4 bytes
-
平台相关检查
static_assert(sizeof(void*) == 8, "This code requires a 64-bit environment");
-
枚举值的合法性
enum class Color { Red, Green, Blue, Max }; static_assert(static_cast<int>(Color::Max) <= 3, "Color enum out of range");
动态断言 (assert
) vs 静态断言 (static_assert
)
特性 | assert |
static_assert |
---|---|---|
检查时间 | 运行时 | 编译时 |
适用场景 | 动态条件检查 | 编译期条件检查 |
性能开销 | 存在运行时开销 | 无运行时开销 |
表达式类型 | 任意布尔表达式 | constexpr (编译期可计算) |
使用时机 | 需要根据运行时数据进行验证 | 条件可以在编译期验证 |
示例 | assert(a > b); |
static_assert(sizeof(T) <= 4); |
带默认消息的简洁用法
如果不提供第二个参数,编译器会自动生成一条默认的错误消息:
static_assert(sizeof(int) == 4); // 默认错误消息
示例
-
检查模板实例的正确性
确保模板实例化时的类型或值满足特定约束。
template<int N> struct Factorial { static_assert(N >= 0, "Factorial is undefined for negative numbers"); static constexpr int value = N * Factorial<N - 1>::value; }; // 特化终止递归 template<> struct Factorial<0> { static constexpr int value = 1; }; int main() { constexpr int result = Factorial<5>::value; // OK constexpr int invalid = Factorial< -1>::value; // 编译失败 return 0; }
-
条件性静态断言
结合
if constexpr
,在特定情况下启用断言template<typename T> void process() { if constexpr (std::is_integral<T>::value) { static_assert(sizeof(T) <= 4, "Integral type must be at most 4 bytes"); } else { static_assert(sizeof(T) > 4, "Non-integral type must be larger than 4 bytes"); } } int main() { process<int>(); // OK process<float>(); // OK return 0; }
auto
用于让编译器根据表达式的上下文自动推导变量的类型,从而简化代码,提升代码的可读性和维护性
int x = 10;
auto y = x; // y 的类型自动推导为 int
应用场景
-
简化变量声明
std::vector<int> vec = {1, 2, 3, 4}; auto it = vec.begin(); // 自动推导迭代器类型
-
与范围
for
循环结合std::vector<int> vec = {1, 2, 3, 4}; for (auto value : vec) { std::cout << value << " "; }
如果需要修改值,可以使用
auto&
:for (auto& value : vec) { value *= 2; // 修改 vec 中的值 }
-
结合 Lambda 表达式
auto
可以用于推导 Lambda 表达式返回的类型。auto lambda = [](int a, int b) { return a + b; }; std::cout << lambda(2, 3); // 输出:5
-
函数返回类型的自动推导
auto
可以用于函数返回值的类型推导,结合decltype
或直接返回值推导。
(C++11) 使用decltype
:template<typename T1, typename T2> auto add(T1 a, T2 b) -> decltype(a + b) { return a + b; }
(C++14 起) 自动推导返回类型:
template<typename T1, typename T2> auto add(T1 a, T2 b) { return a + b; }
-
协助模板类型推导
template<typename Container> void printContainer(const Container& c) { for (auto it = c.begin(); it != c.end(); ++it) { std::cout << *it << " "; } }
注意事项
-
指针和引用类型
auto
会去掉顶层的指针或引用属性,但可以显式添加。-
普通类型
int x = 10; auto y = x; // y 的类型为 int
-
引用类型
int x = 10; auto& y = x; // y 是 int&,绑定到 x
-
指针类型:
int x = 10; int* p = &x; auto ptr = p; // ptr 的类型为 int*
-
-
常量和顶层
const
auto
会去掉顶层const
,但保留底层const
。-
顶层
const
const int x = 10; auto y = x; // y 的类型为 int(顶层 const 被去掉)
-
底层
const
:const int x = 10; const int* p = &x; auto ptr = p; // ptr 的类型为 const int*(底层 const 被保留)
-
-
带初始化列表时的行为
auto
和std::initializer_list
有特殊关系。auto x = {1, 2, 3}; // x 的类型是 std::initializer_list<int>
如果需要
x
是数组或容器类型,可以显式指定类型 -
函数声明中的
auto
-
Lambda 表达式的返回类型:
auto lambda = [](int a, int b) { return a + b; };
-
普通函数返回类型
auto add(int a, int b) { return a + b; // 返回类型由编译器推导 }
-
-
类型推导可能不符合预期
必须清楚
auto
推导的规则,避免误解。const int x = 42; auto y = x; // y 的类型是 int,不是 const int
-
可能影响代码的可维护性
当代码需要明确的类型信息时,滥用
auto
会降低可读性
auto
可以避免代码冗余
当类型复杂时,auto
可以避免重复声明类型
std::map<int, std::vector<int>> myMap;
auto it = myMap.begin(); // 避免写 std::map<int, std::vector<int>>::iterator
在模板和泛型编程中,auto
能自动适应变化的类型,提高代码的通用性。
Lambda表达式
lambda 是一个未命名的函数对象,能够捕获作用域中的变量,使用lambda可以简洁定义内联函数。
Lambda表达式的语法结构:
[捕获列表](参数列表) -> 返回类型 {
函数体
};
-
捕获列表
[ ]
:-
用于指定 lambda 捕获外部作用域的变量。
-
支持的捕获方式:
[]
:不捕获任何变量。[=]
:以值捕获所有外部作用域的变量。[&]
:以引用捕获所有外部作用域的变量。[x]
:以值捕获变量x
。[&x]
:以引用捕获变量x
。[=, &x]
:以值捕获其他变量,以引用捕获x
。[&, x]
:以引用捕获其他变量,以值捕获x
[this]
:通过引用捕获this
-
-
参数列表
( )
:- 用于定义传递给 lambda 表达式的参数,与普通函数的参数列表类似
-
返回类型
-> 返回类型
(可选):- 明确指定返回类型。如果能推断返回类型,则可以省略。
-
函数体
{ ... }
:- Lambda 表达式的实际逻辑。
示例
-
基本用法
#include <iostream> using namespace std; int main() { auto add = [](int a, int b) -> int { return a + b; }; cout << "3 + 4 = " << add(3, 4) << endl; // 输出 3 + 4 = 7 return 0; }
-
捕获变量
#include <iostream> using namespace std; int main() { int x = 10, y = 20; auto printSum = [x, y]() { cout << "Sum: " << x + y << endl; }; printSum(); // 输出 Sum: 30 auto modifyY = [&y]() { y += 10; cout << "Modified y: " << y << endl; }; modifyY(); // 输出 Modified y: 30 return 0; }
-
在 STL 算法中的使用
#include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { vector<int> numbers = {1, 2, 3, 4, 5}; // 使用 lambda 表达式来定义筛选条件 auto evenNumbers = count_if(numbers.begin(), numbers.end(), [](int n) { return n % 2 == 0; }); cout << "Even numbers count: " << evenNumbers << endl; // 输出 2 return 0; }
-
可变捕获(
mutable
)[!tip]
默认情况下,值捕获不能在 lambda 内部修改,因为编译器生成的方法被标记为
const
,mutable
关键字允许修改捕获的变量。该关键字放置在参数列表之后(即使为空也必须存在)#include <iostream> using namespace std; int main() { int value = 42; auto captureByValue = [value]() mutable { value += 10; cout << "Inside lambda: " << value << endl; // 输出 52 }; captureByValue(); cout << "Outside lambda: " << value << endl; // 输出 42 return 0; }
-
泛型 Lambda(C++14 引入)
#include <iostream> using namespace std; int main() { auto multiply = [](auto a, auto b) { return a * b; }; cout << "3 * 4 = " << multiply(3, 4) << endl; // 输出 12 cout << "2.5 * 4.2 = " << multiply(2.5, 4.2) << endl; // 输出 10.5 return 0; }
decltype
decltype
是一个运算符,主要用于推断表达式的类型,可以在编译时确定一个变量或表达式的类型。如果 cv 限定符(const
,volatile
)和引用是表达式的一部分,则会保留它们。
特性和用法
- 推断表达式的类型
decltype
直接返回表达式的类型,而不会对表达式进行计算。
- 区分变量和变量的引用
- 如果表达式是一个变量,
decltype
返回的是该变量的实际类型(可能包含引用)。
- 如果表达式是一个变量,
- 结合
auto
使用- 与
auto
不同,decltype
用于类型推导,但不进行类型修改。auto
通常用于变量初始化,而decltype
更适合于定义与已有变量或表达式相同类型的新变量。 decltype(auto)
, c++14特性
- 与
- 模板元编程中的应用
decltype
是模板元编程的重要工具,可以精确地推断函数返回值的类型。
decltype(expression) variable_name;
expression
是需要推断类型的表达式。variable_name
是声明的新变量。
示例
-
基本用法
#include <iostream> using namespace std; int main() { int a = 10; decltype(a) b = 20; // b 的类型与 a 一致,都是 int cout << "a: " << a << ", b: " << b << endl; return 0; }
-
推断表达式的类型
#include <iostream> using namespace std; int main() { int x = 5; decltype(x + 1.0) y = 3.14; // x + 1.0 的类型是 double,所以 y 的类型也是 double cout << "y: " << y << endl; return 0; }
-
推断引用类型
#include <iostream> using namespace std; int main() { int x = 10; int& ref = x; decltype(ref) y = x; // y 的类型是 int&,所以 y 是 x 的引用 y = 20; // 改变 y 的值也会改变 x 的值 cout << "x: " << x << ", y: " << y << endl; return 0; }
-
与
auto
的对比#include <iostream> using namespace std; int main() { const int x = 10; auto a = x; // auto 会去掉 const,a 是 int decltype(x) b = x; // decltype 保持 const,b 是 const int a = 20; // 合法 // b = 30; // 非法,因为 b 是 const cout << "a: " << a << ", b: " << b << endl; return 0; }
-
用于函数返回类型推导
#include <iostream> using namespace std; int add(int a, int b) { return a + b; } int main() { decltype(add(1, 2)) result = 5; // 推断 add 返回值的类型 cout << "result: " << result << endl; return 0; }
int a = 1; // `a` is declared as type `int`
decltype(a) b = a; // `decltype(a)` is `int`
const int& c = a; // `c` is declared as type `const int&`
decltype(c) d = a; // `decltype(c)` is `const int&`
decltype(123) e = 123; // `decltype(123)` is `int`
int&& f = 1; // `f` is declared as type `int&&`
decltype(f) g = 1; // `decltype(f) is `int&&`
decltype((a)) h = g; // `decltype((a))` is int&
类型别名
在C++中,有两种方式定义类型别名:
- 使用
typedef
- 使用
using
(C++11 引入,推荐方式)
typedef
方式
typedef
是一种传统方式,语法如下:
typedef existing_type new_type_name;
示例:
include <iostream>
#include <vector>
typedef unsigned int uint;
typedef std::vector<int> IntVector;
int main() {
uint a = 42; // uint 作为 unsigned int 的别名
IntVector vec = {1, 2, 3}; // IntVector 是 std::vector<int> 的别名
std::cout << "a: " << a << std::endl;
for (int val : vec) {
std::cout << val << " ";
}
return 0;
}
using
方式
using
是 C++11 引入的语法,更加简洁且功能更强。语法如下:
using new_type_name = existing_type;
示例:
#include <iostream>
#include <map>
#include <string>
using StringIntMap = std::map<std::string, int>;
int main() {
StringIntMap myMap; // StringIntMap 是 std::map<std::string, int> 的别名
myMap["apple"] = 3;
myMap["banana"] = 5;
for (const auto &pair : myMap) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
return 0;
}
using
与模板兼容
#include <iostream>
#include <vector>
// 定义模板别名
template <typename T>
using Vec = std::vector<T>;
int main() {
Vec<int> numbers = {1, 2, 3}; // Vec<int> 等价于 std::vector<int>
for (int n : numbers) {
std::cout << n << " ";
}
return 0;
}
强类型枚举
强类型枚举(Strongly-typed enums),也称为scoped enums
类型安全的枚举解决了 C 风格枚举的各种问题,包括:隐式转换、无法指定底层类型、作用域污染
强类型枚举的定义方式是使用关键字 enum class
或 enum struct
,两者功能相同,语法为:
enum class EnumName : underlying_type { ... };
传统枚举 (Unscoped Enum) 的问题
-
全局作用域污染:传统枚举的枚举成员直接暴露在外部作用域,容易和其他标识符发生冲突。
-
类型安全性差:传统枚举隐式转换为整数类型,可能导致意外的比较或运算。
-
未指定底层类型:默认底层类型是
int
,但无法直接控制。
#include <iostream>
enum Color { Red, Green, Blue }; // 枚举成员暴露在全局作用域
int main() {
Color color = Red; // 直接使用成员
int number = color; // 隐式转换为 int,类型不安全
std::cout << "Color as int: " << number << std::endl;
return 0;
}
强类型枚举的特性
- 作用域限制:枚举成员属于枚举类型的命名空间,不会污染全局作用域。
- 类型安全:强类型枚举不会隐式转换为整数类型。
- 自定义底层类型:可以显式指定底层类型(如
uint8_t
、int32_t
等),有助于节省内存或与特定数据结构兼容。
#include <iostream>
#include <cstdint> // for fixed-width integer types
enum class Color : uint8_t { Red, Green, Blue }; // 定义强类型枚举
int main() {
Color color = Color::Red; // 使用限定作用域的成员访问方式
// int number = color; // 错误:强类型枚举不能隐式转换为整数
if (color == Color::Red) {
std::cout << "The color is Red!" << std::endl;
}
return 0;
}
强类型枚举 vs. 传统枚举
特性 | 传统枚举 (Unscoped Enum) | 强类型枚举 (Strongly-typed Enum) |
---|---|---|
作用域 | 全局作用域污染 | 成员属于枚举类型作用域 |
类型安全性 | 允许隐式转换为整数类型 | 不允许隐式转换 |
底层类型控制 | 默认 int ,不可修改 |
可显式指定底层类型 |
适用性 | 适用于简单场景 | 适用于需要类型安全和作用域隔离的场景 |
[!IMPORTANT]
由于强类型枚举不能隐式转换为整数类型,以下操作需要显式转换:
- 将枚举值转换为整数:
static_cast<int>(EnumValue)
- 将整数转换为枚举值:
static_cast<EnumType>(intValue)
#include <iostream>
enum class Days { Monday, Tuesday, Wednesday };
int main() {
Days today = Days::Tuesday;
// 显式转换为整数
int dayNumber = static_cast<int>(today);
std::cout << "Day number: " << dayNumber << std::endl;
return 0;
}
属性 Attributes
Attributes 是 C++11 引入的一种语言特性,用于向编译器提供额外的元信息,以帮助优化代码、检查错误或修改编译行为。它们不会直接影响程序的逻辑,但可以增强代码的可读性、可维护性,并减少潜在的错误。
语法:
[[attribute_name]]
- 属性放置在代码的特定位置,通常是函数、变量、类型或语句。
- 可以组合多个属性,用逗号分隔,例如:
[[attr1, attr2]]
。
内置属性
-
[[nodiscard]]
指示调用者必须使用函数的返回值,避免因忽略返回值而导致潜在问题。
[[nodiscard]] int calculateSum(int a, int b) { return a + b; } int main() { calculateSum(2, 3); // 警告:返回值被忽略 int result = calculateSum(2, 3); // 正确 return 0; }
-
[[maybe_unused]]
用于抑制未使用变量或参数的警告。
void func([[maybe_unused]] int unusedParam) { // unusedParam 没有被使用 }
-
[[deprecated]]
标记为不建议使用的函数、类或变量,编译时会发出警告。
[[deprecated("Use newFunction() instead")]] void oldFunction() { // ... } void newFunction() { // ... } int main() { oldFunction(); // 警告:该函数已弃用 newFunction(); return 0; }
-
[[fallthrough]]
在
switch
语句中明确表示允许无意的穿透(fall-through)。void check(int value) { switch (value) { case 1: // Intentional fall-through [[fallthrough]]; case 2: std::cout << "Value is 1 or 2 "; break; default: std::cout << "Other value "; break; } }
-
[[likely]]
和[[unlikely]]
提示编译器某个分支更可能或不太可能被执行(C++20 引入)
void process(int value) {
if ([[likely]] value > 0) {
std::cout << "Positive value
";
} else {
std::cout << "Non-positive value
";
}
}
自定义属性命名空间
为了避免冲突,C++允许开发者使用命名空间为自定义属性命名。
namespace MyAttributes {
[[nodiscard]] struct my_attr {};
}
[[MyAttributes::my_attr]] void myFunction() {
// Function with custom attribute
}
使用场景
- 静态分析和错误检测:如
[[nodiscard]]
和[[deprecated]]
提高了代码的健壮性。 - 代码优化:如
[[likely]]
和[[unlikely]]
可帮助编译器生成更高效的分支预测代码。 - 清晰的语义表达:如
[[fallthrough]]
明确标注意图,避免误解。 - 压制警告:如
[[maybe_unused]]
在必要时避免警告。
常量表达式 constexpr
constexpr
是 C++11 引入的关键字,用于定义在编译时可以求值的表达式或函数。通过 constexpr
,可以在编译时进行常量求值,优化性能并减少运行时计算开销。
constexpr
的主要用途
- 定义常量
- 在编译时就能确定值的变量。
- 函数声明
- 用于声明在编译时执行的函数。
- 支持编译时计算
- 允许在编译时对复杂表达式进行求值。
constexpr
的规则
-
constexpr
变量-
使用
constexpr
修饰的变量必须具有常量值。 -
变量的初始化表达式必须在编译时可求值。
constexpr int compileTimeValue = 42; // 编译时确定 const int runTimeValue = 42; // 运行时常量,但不一定在编译时求值 constexpr int squared(int x) { return x * x; } int main() { constexpr int result = squared(4); // 编译时计算 return result; // 编译器直接用 16 替代 result }
-
-
constexpr
函数- 一个
constexpr
函数可以在编译时或运行时使用。 - 函数体内必须只包含可以在编译时求值的操作(如常量运算或调用其他
constexpr
函数)。
注意:
- 如果函数调用的参数是常量表达式,则在编译时求值;
- 如果参数是运行时值,则在运行时执行。
constexpr int add(int a, int b) { return a + b; } int main() { constexpr int result1 = add(2, 3); // 编译时计算 int x = 5; int result2 = add(x, 3); // 运行时计算,因为 x 是运行时变量 return result1 + result2; }
- 一个
-
constexpr
和对象constexpr
也可以用于对象的构造函数,使得类对象在编译时创建。class Point { public: constexpr Point(int x, int y) : x_(x), y_(y) {} constexpr int getX() const { return x_; } constexpr int getY() const { return y_; } private: int x_, y_; }; int main() { constexpr Point p(3, 4); // 编译时构造对象 constexpr int x = p.getX(); // 编译时获取值 return 0; }
constexpr
与 const
的区别
特性 | const |
constexpr |
---|---|---|
作用 | 定义常量 | 定义编译时常量或常量函数 |
编译时求值 | 不要求必须在编译时求值 | 要求初始化表达式在编译时求值 |
函数修饰 | 不适用于函数 | 适用于函数 |
类对象的支持 | 可定义 const 对象 |
可定义编译时类对象 |
C++14 中的增强
在 C++14 中,constexpr
进一步增强,支持更复杂的操作,包括:
- 函数体中允许使用
if
、switch
和循环。 - 变量可以在
constexpr
函数中声明为mutable
。
constexpr int factorial(int n) {
int result = 1;
for (int i = 2; i <= n; ++i) {
result *= i;
}
return result;
}
int main() {
constexpr int fact5 = factorial(5); // 编译时计算 5!
return fact5;
}
C++20 中的增强
C++20 进一步扩展了 constexpr
的能力:
- 允许在
constexpr
函数中使用动态内存分配(new
和delete
)。 - 允许在
constexpr
中使用标准库容器(如std::vector
和std::string
)。
#include <vector>
constexpr int sumElements(const std::vector<int>& vec) {
int sum = 0;
for (int val : vec) {
sum += val;
}
return sum;
}
int main() {
constexpr std::vector<int> numbers = {1, 2, 3, 4, 5};
constexpr int total = sumElements(numbers); // 编译时计算
return total;
}
委托构造函数 Delegating constructors
允许一个构造函数调用同一个类中的另一个构造函数
语法示例:
class ClassName {
public:
ClassName(arguments) : ClassName(other_arguments) {
// Additional initialization, if any
}
};
示例:
#include <iostream>
#include <string>
class Person {
public:
// 主构造函数
Person(const std::string& name, int age) : name(name), age(age) {
std::cout << "Primary constructor called
";
}
// 委托构造函数
Person(const std::string& name) : Person(name, 0) {
std::cout << "Delegating constructor called
";
}
void display() const {
std::cout << "Name: " << name << ", Age: " << age << std::endl;
}
private:
std::string name;
int age;
};
int main() {
Person p1("Alice", 30); // 调用主构造函数
p1.display();
Person p2("Bob"); // 调用委托构造函数
p2.display();
return 0;
}
注意事项
- 初始化顺序
- 被委托的构造函数会先执行。
- 委托构造函数的其余部分在被委托构造函数执行完成后执行。
- 防止递归调用 如果构造函数相互委托,会导致无限递归,进而引发编译错误或运行时错误。
- 不能与继承的构造函数一起使用
- 委托构造函数不能调用基类的构造函数。
- 委托只发生在同一个类中。
多重委托构造:
#include <iostream>
class Rectangle {
public:
// 主构造函数
Rectangle(int width, int height) : width(width), height(height) {
std::cout << "Rectangle(int, int) called
";
}
// 默认构造函数,委托给 Rectangle(int, int)
Rectangle() : Rectangle(0, 0) {
std::cout << "Rectangle() called
";
}
// 构造正方形,委托给 Rectangle(int, int)
Rectangle(int side) : Rectangle(side, side) {
std::cout << "Rectangle(int) called
";
}
void display() const {
std::cout << "Width: " << width << ", Height: " << height << std::endl;
}
private:
int width, height;
};
int main() {
Rectangle r1; // 默认构造函数
Rectangle r2(5); // 构造正方形
Rectangle r3(4, 6); // 主构造函数
r1.display();
r2.display();
r3.display();
return 0;
}
用户定义字面量 User-defined literals
允许开发者为数值、字符串等字面量定义自己的后缀,从而创建更具可读性和语义化的代码
语法
用户自定义字面量以一个 _
开头的后缀标识。可以为整数、浮点数、字符、字符串等字面量定义后缀。
Type operator "" _suffix(arguments);
suffix
是用户定义的后缀名称(必须以 _
开头)。
参数类型由字面量的类型决定:
- 整数字面量(e.g.,
123
):unsigned long long
- 浮点数字面量(e.g.,
1.23
):long double
- 字符字面量(e.g.,
"c"
):char
- 字符串字面量(e.g.,
"hello"
):const char*
或std::string
示例
-
为长度单位定义字面量
将数字字面量转换为单位化的类型:
#include <iostream> constexpr long double operator "" _cm(long double value) { return value; // 以厘米为单位 } constexpr long double operator "" _m(long double value) { return value * 100.0; // 转换为厘米 } constexpr long double operator "" _km(long double value) { return value * 100000.0; // 转换为厘米 } int main() { long double length = 1.0_km + 50.0_m + 25.0_cm; std::cout << "Length in cm: " << length << " cm" << std::endl; return 0; }
-
自定义字符串字面量
将字符串字面量转换为
std::string
:#include <iostream> #include <string> std::string operator "" _s(const char* str, size_t) { return std::string(str); } int main() { std::string message = "Hello, world!"_s; // 使用自定义字符串字面量 std::cout << message << std::endl; return 0; }
-
时间单位转换
#include <iostream> #include <chrono> // 秒 constexpr std::chrono::seconds operator "" _sec(unsigned long long s) { return std::chrono::seconds(s); } // 毫秒 constexpr std::chrono::milliseconds operator "" _ms(unsigned long long ms) { return std::chrono::milliseconds(ms); } int main() { auto duration = 10_sec + 500_ms; // 10秒500毫秒 std::cout << "Total milliseconds: " << std::chrono::duration_cast<std::chrono::milliseconds>(duration).count() << " ms" << std::endl; return 0; }
参数类型说明
字面量类型 | 参数类型 | 示例 |
---|---|---|
整数字面量 | unsigned long long |
123_ull |
浮点数字面量 | long double |
3.14_ld |
字符字面量 | char |
"c"_ch |
字符串字面量 | const char* 和 size_t |
"text"_str |
原始字符串字面量 | const char* 和 size_t |
R"text("_rawstr |
标准库中的用户自定义字面量
C++ 提供了一些内置的用户自定义字面量(C++14 开始),例如:
- 时间单位:
std::chrono
提供的_h
、_min
、_s
等字面量。 - 字符串:
std::string_literals
提供的_s
。 - 复杂数:
std::complex_literals
提供的_i
#include <iostream>
#include <chrono>
#include <string>
#include <complex>
using namespace std::chrono_literals; // 时间单位
using namespace std::string_literals; // 字符串
using namespace std::complex_literals; // 复数
int main() {
auto time = 10s; // 10 秒
auto str = "Hello!"s; // std::string
auto complex = 3.0 + 4.0i; // std::complex<double>
std::cout << "Time: " << time.count() << " seconds" << std::endl;
std::cout << "String: " << str << std::endl;
std::cout << "Complex: " << complex << std::endl;
return 0;
}
override
在派生类中显式地重写(override)基类的虚函数
#include <iostream>
class Base {
public:
virtual void showMessage() const {
std::cout << "Base message" << std::endl;
}
virtual ~Base() = default;
};
class Derived : public Base {
public:
void showMessage() const override { // 使用 override 明确覆盖基类的虚函数
std::cout << "Derived message" << std::endl;
}
};
int main() {
Base* obj = new Derived();
obj->showMessage(); // 输出: Derived message
delete obj;
return 0;
}
final
指定虚函数不能在派生类中被重写,或者类不能被继承
struct A {
virtual void foo();
};
struct B : A {
virtual void foo() final;
};
struct C : B {
virtual void foo(); // error -- declaration of "foo" overrides a "final" function
};
struct A final {};
struct B : A {}; // error -- base "A" is marked "final"
default
default
函数是用于显式声明类的默认行为的一种特性,通常与构造函数、析构函数和赋值运算符相关,使用 default
关键字可以让开发者控制哪些函数需要编译器自动生成的默认实现
默认函数:如果不显式定义,C++ 编译器会为类自动生成以下默认函数:
- 默认构造函数(如果类没有用户定义构造函数)。
- 默认析构函数。
- 复制构造函数。
- 移动构造函数(C++11 起)。
- 复制赋值运算符。
- 移动赋值运算符(C++11 起)。
使用 = default
的目的:
- 明确表明某个函数是默认生成的。
- 在类的接口中显式地声明默认行为,增加可读性。
- 强制生成某些情况下编译器不会自动生成的函数(例如某些模板类)
#include <iostream>
class Example {
public:
Example() = default; // 显式声明默认构造函数
~Example() = default; // 显式声明默认析构函数
Example(const Example&) = default; // 默认复制构造函数
Example& operator=(const Example&) = default; // 默认复制赋值运算符
Example(Example&&) = default; // 默认移动构造函数
Example& operator=(Example&&) = default; // 默认移动赋值运算符
};
int main() {
Example e1; // 默认构造函数
Example e2 = e1; // 复制构造函数
Example e3 = std::move(e1); // 移动构造函数
e3 = e2; // 复制赋值运算符
e3 = std::move(e2); // 移动赋值运算符
return 0;
}
delete
通过关键字 = delete
明确声明的函数,表示这些函数被删除,禁止调用。被标记为 = delete
的函数会让编译器在编译阶段检查并报告错误,从而防止意外使用
#include <iostream>
class NonCopyable {
public:
NonCopyable() = default; // 默认构造函数
~NonCopyable() = default; // 默认析构函数
NonCopyable(const NonCopyable&) = delete; // 禁用复制构造函数
NonCopyable& operator=(const NonCopyable&) = delete; // 禁用复制赋值运算符
};
int main() {
NonCopyable obj1;
// NonCopyable obj2 = obj1; // 错误:复制构造函数被删除
// obj1 = obj2; // 错误:复制赋值运算符被删除
return 0;
}
基于范围的for循环
用于遍历容器(如数组、向量等)或范围
语法
for (declaration : range) {
// 循环体
}
-
declaration
:循环变量,表示容器中每个元素的类型。 -
range
:要遍历的范围,可以是标准容器(如std::vector
)、数组、初始化列表或任何实现了begin()
和end()
的范围。
示例:
#include <iostream>
#include <vector>
int main() {
int arr[] = {1, 2, 3, 4, 5};
for (int num : arr) { // 遍历数组,值传递,会发生拷贝
std::cout << num << " ";
}
// 输出: 1 2 3 4 5
std::vector<std::string> fruits = {"apple", "banana", "cherry"};
for (const auto& fruit : fruits) { // 使用引用避免拷贝
std::cout << fruit << " ";
}
// 输出: apple banana cherry
return 0;
}
转换构造函数
转换构造函数(Converting constructors)是一种特殊的构造函数,可以通过单个参数的隐式或显式转换,将其他类型的值转换为类类型的对象的构造函数。c++03中要求转换构造函数只有单个参数,或其它参数具有默认值,c++11中转换构造函数则可以拥有多个参数,可以使用列表初始化{}
调用拥有多个参数的转换构造函数
示例:
#include <iostream>
using namespace std;
class A{
public:
A(int){cout << "A(int)" << endl;}
A(const char* s, int){cout << "A(const char* s, int)" << endl;}
A(int x, int y){cout << "A(int x, int y)" << endl;}
};
int main(){
A a1 = 1; //A(int)
A a2(2); //A(int)
A a3{4,5}; //A(int x, int y)
A a4 = {"ciallo",5}; //A(const char* s, int)
A a5 = (A)1; //A(int)
A a6(1,2); //A(int x, int y)
}
如果构造函数接受std::initializer_list
, 则会调用它
struct A {
A(int) {}
A(int, int) {}
A(int, int, int) {}
A(std::initializer_list<int>) {}
};
A a {0, 0}; // calls A::A(std::initializer_list<int>)
A b(0, 0); // calls A::A(int, int)
A c = {0, 0}; // calls A::A(std::initializer_list<int>)
A d {0, 0, 0}; // calls A::A(std::initializer_list<int>)
如果对构造函数使用了explicit
修饰符,则禁止对构造函数进行隐式转换
#include <iostream>
using namespace std;
class A{
public:
explicit A(int){cout << "A(int)" << endl;}
};
int main(){
A a1 = 1; //编译错误
}
显式转换函数
显式转换函数是通过在类成员函数前添加 explicit
关键字定义的特殊类型转换函数。这些函数只会在显式调用时生效,而不会在隐式上下文中自动调用。
class ClassName {
public:
explicit operator TargetType() const {
// Conversion logic here
}
};
示例
#include <iostream>
#include <string>
class Temperature {
private:
double celsius; // Temperature in Celsius
public:
explicit Temperature(double c) : celsius(c) {}
// Explicit conversion to double
explicit operator double() const {
return celsius;
}
// Explicit conversion to string
explicit operator std::string() const {
return std::to_string(celsius) + "°C";
}
// A method to print temperature
void print() const {
std::cout << "Temperature: " << celsius << "°C
";
}
};
int main() {
Temperature temp(36.6);
// Error: implicit conversion not allowed due to explicit keyword
// double value = temp;
// Explicit conversion to double
double value = static_cast<double>(temp);
std::cout << "Temperature in double: " << value << "
";
// Explicit conversion to string
std::string strValue = static_cast<std::string>(temp);
std::cout << "Temperature in string: " << strValue << "
";
return 0;
}
struct A {
operator bool() const { return true; }
};
struct B {
explicit operator bool() const { return true; }
};
A a;
if (a); // OK calls A::operator bool()
bool ba = a; // OK copy-initialization selects A::operator bool()
B b;
if (b); // OK calls B::operator bool()
bool bb = b; // error copy-initialization does not consider B::operator bool()
bool bbb = static_cast<bool> b; //OK calls B::operator bool()
内联命名空间
内联命名空间用于库的版本控制和名称管理。一个 inline 命名空间中的所有成员可以被视为声明在外围命名空间中
namespace Program {
namespace Version1 {
int getVersion() { return 1; }
bool isFirstVersion() { return true; }
}
inline namespace Version2 {
int getVersion() { return 2; }
}
}
int version {Program::getVersion()}; // Uses getVersion() from Version2
int oldVersion {Program::Version1::getVersion()}; // Uses getVersion() from Version1
bool firstVersion {Program::isFirstVersion()}; // Does not compile when Version2 is added
非静态数据成员初始化器
Non-static data member initializers
允许在声明非静态数据成员的地方对其进行初始化,这可能会简化默认初始化的构造函数
// C++11之前的默认初始化
class Human {
Human() : age{0} {}
private:
unsigned age;
};
// C++11的默认初始化
class Human {
private:
unsigned age {0};//括号初始化
int x = 10; //直接初始化
};
[!NOTE]
不能用于动态内存分配,但可以用来初始化指针为 nullptr
引用限定成员函数
引用限定成员函数(Ref-qualified member functions)允许为类的成员函数指定调用的对象类型约束,即只能通过特定类型的对象(左值、右值)调用。这是通过在函数签名后使用 &
或&&
限定符来实现的
class ClassName {
public:
void func() &; // 只能通过左值调用
void func() &&; // 只能通过右值调用
};
&
限定符:表示该成员函数只能通过左值对象调用。&&
限定符:表示该成员函数只能通过右值对象调用。
#include <utility>
struct Bar {
// ...
};
struct Foo {
Bar& getBar() & { return bar; }
const Bar& getBar() const& { return bar; }
Bar&& getBar() && { return std::move(bar); }
const Bar&& getBar() const&& { return std::move(bar); }
private:
Bar bar;
};
int main(){
Foo foo{};
Bar bar = foo.getBar(); // calls `Bar& getBar() &`
const Foo foo2{};
Bar bar2 = foo2.getBar(); // calls `Bar& Foo::getBar() const&`
Foo{}.getBar(); // calls `Bar&& Foo::getBar() &&`
std::move(foo).getBar(); // calls `Bar&& Foo::getBar() &&`
std::move(foo2).getBar(); // calls `const Bar&& Foo::getBar() const&`
}
尾随返回类型
允许在函数定义中将返回类型写在参数列表之后,而不是函数名之前。这种语法主要用于以下场景:
- 复杂返回类型的清晰表达。
- 需要依赖函数参数的类型来决定返回类型时。
auto function_name(parameters) -> return_type;
示例
#include <iostream>
#include <type_traits>
#include <vector>
// 简单返回类型
auto add(int a, int b) -> int {
return a + b;
}
// 返回类型依赖模板参数
template <typename T, typename U>
auto multiply(T a, U b) -> decltype(a * b) {
return a * b;
}
// 返回复杂类型
auto get_vector() -> std::vector<int> {
return {1, 2, 3};
}
int main() {
std::cout << add(3, 4) << "
"; // 输出: 7
std::cout << multiply(3, 4.5) << "
"; // 输出: 13.5
auto vec = get_vector();
for (auto x : vec) {
std::cout << x << " "; // 输出: 1 2 3
}
std::cout << "
";
return 0;
}
Noexcept 描述符
noexcept
指定符指定一个函数是否可能抛出异常。它是 throw()
的改进版本。
void func1() noexcept; // does not throw
void func2() noexcept(true); // does not throw
void func3() throw(); // does not throw
void func4() noexcept(false); // may throw
[!CAUTION]
声明为
noexcept
的函数中调用可能抛出异常的代码是危险的void unsafe() noexcept { throw std::runtime_error("Error!"); // 导致std::terminate被调用 }
noexcept操作符
C++提供了noexcept
操作符,用于检查一个表达式是否为noexcept
#include <iostream>
void safeFunction() noexcept {}
void unsafeFunction() {
throw std::runtime_error("Unsafe function");
}
int main() {
std::cout << std::boolalpha;
std::cout << "safeFunction is noexcept: " << noexcept(safeFunction()) << "
";
std::cout << "unsafeFunction is noexcept: " << noexcept(unsafeFunction()) << "
";
return 0;
}
原始字符串字面值
可以以原始形式输入转义字符
// msg1 and msg2 are equivalent.
const char* msg1 = "
Hello,
world!
";
const char* msg2 = R"(
Hello,
world!
)";
标准库特性
std::move
std::move
是 C++ 标准库中的一个函数模板,定义在头文件 <utility>
中。它的作用是将一个对象显式地 转换为右值引用,以触发对象的移动语义(而不是拷贝语义)
std::move
的定义:
template <typename T>
typename remove_reference<T>::type&& move(T&& arg) {
return static_cast<typename remove_reference<T>::type&&>(arg);
}
应用场景:
- 避免深拷贝,触发移动语义
当需要将对象的资源转移,而不需要深拷贝时使用std::move
。 - 临时对象的高效传递
std::move
可以使容器在插入/移动对象时避免多余的拷贝操作。 - 强制将变量转换为右值
即使是一个左值变量(有名字的变量),使用std::move
可以将其转为右值,允许资源转移
示例:
- 基本移动语义
#include <cstring>
#include <iostream>
#include <utility> // for std::move
#include <string>
class MyString {
char* data;
size_t length;
public:
// 构造函数
MyString(const char* str) : length(strlen(str)), data(new char[length + 1]) {
std::strcpy(data, str);
std::cout << "Constructed: " << data << std::endl;
}
// 移动构造函数
MyString(MyString&& other) noexcept : data(other.data), length(other.length) {
other.data = nullptr;
other.length = 0;
std::cout << "Moved!" << std::endl;
}
// 析构函数
~MyString() {
if (data) {
std::cout << "Destroyed: " << data << std::endl;
delete[] data;
}
}
void print() const {
if (data) std::cout << data << std::endl;
else std::cout << "Empty!" << std::endl;
}
};
int main() {
MyString s1("Hello, World!"); // 正常构造
MyString s2(std::move(s1)); // 使用 std::move 触发移动构造
s1.print(); // s1 已被移动,输出 "Empty!"
s2.print(); // s2 持有原来的资源,输出 "Hello, World!"
return 0;
}
- 容器中的优化
#include <iostream>
#include <vector>
#include <string>
int main() {
std::vector<std::string> vec;
std::string str = "Hello, World!";
std::cout << "Pushing back copy:" << std::endl;
vec.push_back(str); // 使用拷贝构造,str 内容未转移
std::cout << "Pushing back move:" << std::endl;
vec.push_back(std::move(str)); // 使用移动构造,str 内容被转移
std::cout << "Vector contents: " << vec[0] << ", " << vec[1] << std::endl;
std::cout << "Original string: " << str << std::endl; // str 可能为空或未定义状态
return 0;
}
- 强制转换为右值引用
#include <iostream>
#include <utility>
void printInt(int& n) {
std::cout << "Lvalue: " << n << std::endl;
}
void printInt(int&& n) {
std::cout << "Rvalue: " << n << std::endl;
}
int main() {
int x = 10;
printInt(x); // 调用左值版本
printInt(std::move(x)); // 调用右值版本
printInt(20); // 调用右值版本
return 0;
}
- 与
std::unique_str
的使用
#include <iostream>
#include <memory> // for std::unique_ptr and std::move
#include <vector>
void processUniquePtr(std::unique_ptr<int> p) {
std::cout << "Processing value: " << *p << std::endl;
}
int main() {
// 创建一个 std::unique_ptr,管理动态分配的 int
std::unique_ptr<int> ptr1 = std::make_unique<int>(42);
std::cout << "ptr1 owns: " << *ptr1 << std::endl;
// 将所有权转移到 ptr2
std::unique_ptr<int> ptr2 = std::move(ptr1);
// ptr1 现在为空,ptr2 拥有资源
if (!ptr1) {
std::cout << "ptr1 is now null." << std::endl;
}
std::cout << "ptr2 owns: " << *ptr2 << std::endl;
//传递 std::unique_ptr 到函数
std::unique_ptr<int> ptr = std::make_unique<int>(100);
// 使用 std::move 将所有权转移到函数参数
processUniquePtr(std::move(ptr));
if (!ptr) {
std::cout << "ptr is now null after move." << std::endl;
}
//将 std::unique_ptr 放入容器
std::vector<std::unique_ptr<int>> vec;
// 创建两个 unique_ptr
std::unique_ptr<int> ptr3 = std::make_unique<int>(10);
std::unique_ptr<int> ptr4 = std::make_unique<int>(20);
// 使用 std::move 将所有权转移到 vector 中
vec.push_back(std::move(ptr3));
vec.push_back(std::move(ptr4));
// ptr1 和 ptr2 现在为空
if (!ptr3 && !ptr4) {
std::cout << "ptr3 and ptr4 are now null." << std::endl;
}
// 访问 vector 中的元素
for (const auto& ptr : vec) {
std::cout << "Vector element: " << *ptr << std::endl;
}
return 0;
}
std::forward
主要作用是:根据传入参数的值类别(左值或右值)保持其原始类别,从而正确地传递给另一个函数(例如,左值保持为左值,临时对象作为右值转发)
与Forwarding references
一起使用,实现完美转发
定义:
template <typename T>
T&& forward(typename remove_reference<T>::type& arg) {
return static_cast<T&&>(arg);
}
T&&
的返回类型:
- 如果
T
是左值引用,则返回左值引用。 - 如果
T
是右值,则返回右值引用。
示例:
#include <iostream>
struct A {
A() = default;
A(const A& o) { std::cout << "copied" << std::endl; }
A(A&& o) { std::cout << "moved" << std::endl; }
};
template <typename T>
A wrapper(T&& arg) {
return A{std::forward<T>(arg)};
}
int main(){
wrapper(A{}); // moved
A a;
wrapper(a); // copied
wrapper(std::move(a)); // moved
}
- 如果传递左值,
T&&
会解析为T& &
(根据引用折叠规则,最终变为T&
)。 - 如果传递右值,
T&&
会解析为T&&
。
[!note]
当需要将参数arg
转发给另一个函数时,直接使用arg
可能导致值类别被错误地修改。例如,右值可能被误认为左值传递,导致调用拷贝构造函数而非移动构造函数
若使用return A{arg}
,即使arg作为万能引用正确传递了引用类型,右值引用仍然会被作为左值使用,将会调用拷贝构造函数,输出三次copied
注意事项
std::forward
只能用于函数模板中的转发操作,因为它依赖于模板参数T
的推导- 如果在不需要完美转发的地方使用
std::forward
可能导致意外行为 - 不要在普通函数中使用
std::forward
,普通函数的参数值类别是确定的,没有必要通过std::forward
转发
std::forward
与 std::move
的区别
特性 | std::forward |
std::move |
---|---|---|
用途 | 保留参数的值类别,用于完美转发。 | 将左值显式地转换为右值引用。 |
典型场景 | 用于模板参数和万能引用。 | 用于移动语义和资源转移。 |
是否保留值类别 | 是,参数的值类别由模板参数决定。 | 否,强制转换为右值引用。 |
返回类型 | 与模板参数 T 的值类别一致。 |
始终返回右值引用。 |
std::thread
std::thread
库提供了一种标准方式来控制线程,例如生成和终止线程
void foo(bool clause) { /* do something... */ }
std::vector<std::thread> threadsVector;
threadsVector.emplace_back([]() {
// Lambda function that will be invoked
});
threadsVector.emplace_back(foo, true); // thread will run foo(true)
for (auto& thread : threadsVector) {
thread.join(); // Wait for threads to finish
}
std::to_string
将数值参数转换为 std::string
std::to_string(1.2); // == "1.2"
std::to_string(123); // == "123"
类型特征
类型特征(Type traits)用于在编译时检查、修改或操作类型的属性和特性。
特点
- 编译时决策:
- Type Traits 提供的功能完全在编译时完成,无运行时开销。
- 判断类型特性:
- 检查一个类型是否满足某些条件(如是否为整数类型、是否为指针类型)。
- 修改类型:
- 对类型进行某些操作,比如移除指针、移除引用、添加常量修饰符等。
- 辅助 SFINAE:
- 与
std::enable_if
和其他模板机制结合,构建更强大的模板代码。
- 与
常用 Type Traits
Type Traits 通常定义在 <type_traits>
头文件中。以下是一些常用的 Type Traits。
判断类型特性
Type Trait | 功能描述 |
---|---|
std::is_integral<T> |
判断类型 T 是否为整型 |
std::is_floating_point<T> |
判断类型 T 是否为浮点型 |
std::is_pointer<T> |
判断类型 T 是否为指针类型 |
std::is_reference<T> |
判断类型 T 是否为引用类型 |
std::is_const<T> |
判断类型 T 是否为常量类型 |
std::is_same<T, U> |
判断类型 T 和 U 是否相同 |
std::is_array<T> |
判断类型 T 是否为数组类型 |
std::is_class<T> |
判断类型 T 是否为类类型 |
std::is_function<T> |
判断类型 T 是否为函数类型 |
#include <iostream>
#include <type_traits>
template <typename T>
void checkTypeTraits() {
if (std::is_integral<T>::value) {
std::cout << "T is an integral type.
";
}
if (std::is_pointer<T>::value) {
std::cout << "T is a pointer type.
";
}
if (std::is_const<T>::value) {
std::cout << "T is a const type.
";
}
}
int main() {
checkTypeTraits<int>(); // 输出: T is an integral type.
checkTypeTraits<const int>(); // 输出: T is an integral type. T is a const type.
checkTypeTraits<int*>(); // 输出: T is a pointer type.
return 0;
}
修改类型
Type Trait | 功能描述 |
---|---|
std::remove_const<T> |
移除类型 T 的 const 修饰符 |
std::remove_pointer<T> |
移除类型 T 的指针修饰符 |
std::remove_reference<T> |
移除类型 T 的引用修饰符 |
std::add_const<T> |
为类型 T 添加 const 修饰符 |
std::add_pointer<T> |
为类型 T 添加指针修饰符 |
std::add_reference<T> |
为类型 T 添加引用修饰符 |
std::decay<T> |
移除数组和函数类型中的修饰符 |
#include <iostream>
#include <type_traits>
template <typename T>
void modifyTypeTraits() {
using NonConstType = typename std::remove_const<T>::type;
using PointerType = typename std::add_pointer<T>::type;
using ReferenceType = typename std::add_lvalue_reference<T>::type;
std::cout << "Original type: " << typeid(T).name() << "
";
std::cout << "Non-const type: " << typeid(NonConstType).name() << "
";
std::cout << "Pointer type: " << typeid(PointerType).name() << "
";
std::cout << "Reference type: " << typeid(ReferenceType).name() << "
";
}
int main() {
modifyTypeTraits<const int>(); // 演示类型修改
return 0;
}
辅助模板编程
Type Trait | 功能描述 |
---|---|
std::conditional<Cond, T1, T2> |
如果 Cond 为 true ,则类型为 T1 ,否则为 T2 |
std::enable_if<Cond, T> |
如果 Cond 为 true ,启用模板 |
std::is_base_of<Base, Derived> |
判断 Base 是否为 Derived 的基类 |
std::integral_constant<T, v> |
编译时保存常量值 |
与 SFINAE 结合
#include <iostream>
#include <type_traits>
template <typename T>
typename std::enable_if<std::is_integral<T>::value>::type
print(T value) {
std::cout << "Integral value: " << value << "
";
}
template <typename T>
typename std::enable_if<std::is_floating_point<T>::value>::type
print(T value) {
std::cout << "Floating-point value: " << value << "
";
}
int main() {
print(42); // 输出: Integral value: 42
print(3.14); // 输出: Floating-point value: 3.14
// print("Hello"); // 编译错误,不符合任何模板
return 0;
}
智能指针
封装了原始指针的类,提供自动化的内存管理功能,避免了手动管理内存可能引发的内存泄漏、悬垂指针等问题。智能指针通过RAII(资源获取即初始化)模式,确保在超出作用域时,资源会被安全释放
C++标准库提供了三种主要的智能指针,定义在头文件 <memory>
中
std::unique_ptr
-
独占所有权的智能指针,一个对象只能被一个
unique_ptr
管理。 -
适合表示独占资源,不能复制
-
轻量级,适合高性能场景
-
通过
std::move
可以将所有权转移 -
自动释放资源,无需手动调用
delete
#include <iostream>
#include <memory>
class Resource {
public:
Resource() { std::cout << "Resource acquired
"; }
~Resource() { std::cout << "Resource released
"; }
};
int main() {
std::unique_ptr<Resource> ptr1 = std::make_unique<Resource>();
// std::unique_ptr<Resource> ptr2 = ptr1; // 编译错误,不能复制
std::unique_ptr<Resource> ptr2 = std::move(ptr1); // 转移所有权
if (!ptr1) {
std::cout << "ptr1 is now empty
";
}
//另一种初始化方式
std::unique_ptr<Resource> p1 { new Resource{} };
std::unique_ptr<Resource> p2 {std::move(p1)};
return 0;
}
std::shared_ptr
-
共享所有权的智能指针,多个
shared_ptr
可以管理同一个对象,使用引用计数来管理资源生命周期。 -
使用引用计数,只有当引用计数为零时才会释放资源
-
适合表示共享资源。
-
支持赋值和拷贝
-
使用
std::make_shared
创建,避免多次分配内存 -
自动释放资源
#include <iostream>
#include <memory>
class Resource {
public:
Resource() { std::cout << "Resource acquired
"; }
~Resource() { std::cout << "Resource released
"; }
};
int main() {
std::shared_ptr<Resource> ptr1 = std::make_shared<Resource>();
std::shared_ptr<Resource> ptr2 = ptr1; // 引用计数增加
std::cout << "Reference count: " << ptr1.use_count() << "
";
ptr1.reset(); // 减少一个引用
std::cout << "Reference count after reset: " << ptr2.use_count() << "
";
return 0; // ptr2超出作用域,资源自动释放
}
std::weak_ptr
-
辅助
shared_ptr
使用的弱引用,不影响共享资源的生命周期。 -
不增加引用计数,只是对
shared_ptr
的弱引用 -
解决了
shared_ptr
的循环引用问题 -
需要通过
lock
获取一个有效的shared_ptr
-
可以判断指向的资源是否还存在
#include <iostream>
#include <memory>
class Resource {
public:
Resource() { std::cout << "Resource acquired
"; }
~Resource() { std::cout << "Resource released
"; }
};
int main() {
std::shared_ptr<Resource> shared = std::make_shared<Resource>();
std::weak_ptr<Resource> weak = shared; // 创建弱引用
if (auto locked = weak.lock()) {
std::cout << "Resource is still alive
";
}
shared.reset(); // 释放资源
if (weak.expired()) {
std::cout << "Resource has been released
";
}
return 0;
}
注意事项
- 避免循环引用:
std::shared_ptr
之间的循环引用会导致资源无法释放。- 使用
std::weak_ptr
打破循环引用。
- 避免不必要的性能开销:
- 如果资源是独占的,优先使用
std::unique_ptr
。 std::shared_ptr
的引用计数管理有一定的性能开销。
- 如果资源是独占的,优先使用
- 不要混用原始指针和智能指针:
- 避免用智能指针管理已经由原始指针管理的资源,或者反过来。
std::chrono
chrono 库包含一组处理持续时间、时钟和时间点的实用函数和类型。
std::chrono::time_point<std::chrono::steady_clock> start, end;
start = std::chrono::steady_clock::now();
// Some computations...
end = std::chrono::steady_clock::now();
std::chrono::duration<double> elapsed_seconds = end - start;
double t = elapsed_seconds.count(); // t number of seconds, represented as a `double`
元组
元组(Tuples) 是 C++ 标准库提供的一种容器,定义在 <tuple>
头文件中,用于存储一组异构类型的数据。元组可以看作是扩展版的 std::pair
,支持多个元素,且每个元素的类型可以不同
主要特点
- 异构性:
- 元组可以存储多种不同类型的数据,例如整数、浮点数、字符串等。
- 固定大小:
- 元组的大小在编译时确定,不能动态更改。
- 类型安全:
- 通过类型索引(而不是名称)访问元组中的元素。
- 实用性:
- 常用于函数返回多个不同类型的值。
创建元组
#include <tuple>
#include <iostream>
#include <string>
int main() {
// 创建元组
std::tuple<int, double, std::string> myTuple(42, 3.14, "Hello");
// 通过 std::make_tuple 创建
auto anotherTuple = std::make_tuple(1, 2.718, "World");
return 0;
}
访问元组元素
可以使用 std::get<index>(tuple)
按索引访问元素。
#include <tuple>
#include <iostream>
int main() {
std::tuple<int, double, std::string> myTuple(42, 3.14, "Hello");
// 按索引访问元素
std::cout << "First element: " << std::get<0>(myTuple) << "
";
std::cout << "Second element: " << std::get<1>(myTuple) << "
";
std::cout << "Third element: " << std::get<2>(myTuple) << "
";
return 0;
}
元组解构
使用 C++17 的结构化绑定直接解构元组
#include <tuple>
#include <iostream>
int main() {
std::tuple<int, double, std::string> myTuple(42, 3.14, "Hello");
// 解构元组
auto [intValue, doubleValue, stringValue] = myTuple;
std::cout << "Integer: " << intValue << "
";
std::cout << "Double: " << doubleValue << "
";
std::cout << "String: " << stringValue << "
";
return 0;
}
修改元组元素
元组的元素可以通过 std::get<index>
修改
#include <tuple>
#include <iostream>
int main() {
std::tuple<int, double, std::string> myTuple(42, 3.14, "Hello");
// 修改元素
std::get<0>(myTuple) = 100;
std::get<2>(myTuple) = "World";
std::cout << "Updated tuple: ("
<< std::get<0>(myTuple) << ", "
<< std::get<1>(myTuple) << ", "
<< std::get<2>(myTuple) << ")
";
return 0;
}
获取元组的大小
使用 std::tuple_size
#include <tuple>
#include <iostream>
int main() {
std::tuple<int, double, std::string> myTuple(42, 3.14, "Hello");
constexpr size_t tupleSize = std::tuple_size<decltype(myTuple)>::value;
std::cout << "Size of tuple: " << tupleSize << "
";
return 0;
}
拼接元组
使用 std::tuple_cat
将多个元组拼接
#include <tuple>
#include <iostream>
int main() {
std::tuple<int, std::string> tuple1(42, "Hello");
std::tuple<double, char> tuple2(3.14, "A");
auto combinedTuple = std::tuple_cat(tuple1, tuple2);
std::cout << "Combined tuple: ("
<< std::get<0>(combinedTuple) << ", "
<< std::get<1>(combinedTuple) << ", "
<< std::get<2>(combinedTuple) << ", "
<< std::get<3>(combinedTuple) << ")
";
return 0;
}
比较元组
元组支持比较操作符(==
, !=
, <
, <=
, >
, >=
)
#include <tuple>
#include <iostream>
int main() {
std::tuple<int, std::string> tuple1(42, "Hello");
std::tuple<int, std::string> tuple2(42, "World");
if (tuple1 < tuple2) {
std::cout << "tuple1 is less than tuple2
";
} else {
std::cout << "tuple1 is not less than tuple2
";
}
return 0;
}
元组的典型用途
-
函数返回多个值
#include <tuple> #include <iostream> std::tuple<int, double, std::string> getValues() { return std::make_tuple(42, 3.14, "Hello"); } int main() { auto [intValue, doubleValue, stringValue] = getValues(); std::cout << "Integer: " << intValue << " "; std::cout << "Double: " << doubleValue << " "; std::cout << "String: " << stringValue << " "; return 0; }
std::tie
std::tie
是 C++ 标准库中的一个实用工具,定义在头文件 <tuple>
中。它主要用于将元组的元素绑定到多个变量上,从而实现“解构”操作
主要功能
-
解构元组
-
使用
std::tie
将元组中的元素绑定到多个变量。#include <iostream> #include <tuple> int main() { std::tuple<int, double, std::string> myTuple(42, 3.14, "Hello"); int intValue; double doubleValue; std::string stringValue; // 使用 std::tie 解构元组 std::tie(intValue, doubleValue, stringValue) = myTuple; std::cout << "Integer: " << intValue << " "; std::cout << "Double: " << doubleValue << " "; std::cout << "String: " << stringValue << " "; return 0; }
-
-
忽略部分元素
-
使用
std::ignore
忽略不需要的元组元素。#include <iostream> #include <tuple> int main() { std::tuple<int, double, std::string> myTuple(42, 3.14, "Hello"); int intValue; std::string stringValue; // 忽略中间的 double 元素 std::tie(intValue, std::ignore, stringValue) = myTuple; std::cout << "Integer: " << intValue << " "; std::cout << "String: " << stringValue << " "; return 0; }
-
-
与函数返回值结合
-
常用于从返回元组的函数中提取多个值,使用
std::tie
提取其中的值#include <iostream> #include <tuple> // 返回多个值的函数 std::tuple<int, double, std::string> getValues() { return std::make_tuple(42, 3.14, "Hello"); } int main() { int intValue; double doubleValue; std::string stringValue; // 使用 std::tie 解构函数返回的元组 std::tie(intValue, doubleValue, stringValue) = getValues(); std::cout << "Integer: " << intValue << " "; std::cout << "Double: " << doubleValue << " "; std::cout << "String: " << stringValue << " "; return 0; }
注意事项
-
变量的引用关系:
std::tie
实际上绑定了变量的引用。如果对绑定的变量进行修改,会影响元组中的内容(前提是元组也是引用类型)#include <iostream> #include <tuple> int main() { int a = 42, b = 100; std::tuple<int&, int&> myTuple = std::tie(a, b); std::get<0>(myTuple) = 200; // 修改元组中的值 std::cout << "a: " << a << " "; // 输出 200 return 0; }
-
不能用于非可修改的类型
如果需要解构
const
元组,std::tie
无法使用,需要考虑使用结构化绑定(C++17) -
推荐在需要引用的场景中使用
std::tie
更适合那些希望通过引用访问元组元素的情况。如果不需要引用,直接使用结构化绑定更简洁
与结构化绑定的对比
结构化绑定是一种更现代、更直观的方式,用于解构元组,避免了对
std::tie
和std::get
的依赖#include <iostream> #include <tuple> int main() { auto myTuple = std::make_tuple(42, 3.14, "Hello"); // 使用结构化绑定(推荐) auto [intValue, doubleValue, stringValue] = myTuple; std::cout << "Integer: " << intValue << " "; std::cout << "Double: " << doubleValue << " "; std::cout << "String: " << stringValue << " "; return 0; }
-
-
std::array
std::array
是一个基于 C 风格数组构建的容器,用来表示一个固定大小的数组
std::array<Type, Size> name;
Type
是数组元素的类型。Size
是数组的大小,必须是一个常量表达式。
成员函数
at(index)
:安全地访问指定位置的元素,带越界检查。operator[]
:访问元素,不做越界检查。front()
/back()
:返回第一个或最后一个元素。size()
:返回数组的大小。fill(value)
:将数组中所有元素设置为value
。data()
:返回底层数组的指针。
示例
#include <iostream>
#include <array>
int main() {
// 定义一个大小为5的std::array
std::array<int, 5> myArray = {1, 2, 3, 4, 5};
// 使用[]访问元素
std::cout << "使用[]访问元素:" << std::endl;
for (size_t i = 0; i < myArray.size(); ++i) {
std::cout << myArray[i] << " ";
}
std::cout << std::endl;
// 使用at()访问元素
std::cout << "使用at()访问元素:" << std::endl;
for (size_t i = 0; i < myArray.size(); ++i) {
std::cout << myArray.at(i) << " ";
}
std::cout << std::endl;
// 修改元素
myArray[0] = 10;
myArray.at(1) = 20;
std::cout << "修改后数组内容: ";
for (const auto& value : myArray) {
std::cout << value << " ";
}
std::cout << std::endl;
// 使用front()和back()
std::cout << "第一个元素: " << myArray.front() << std::endl;
std::cout << "最后一个元素: " << myArray.back() << std::endl;
// 使用fill()重置元素
myArray.fill(42);
std::cout << "使用fill()后的数组内容: ";
for (const auto& value : myArray) {
std::cout << value << " ";
}
std::cout << std::endl;
// 获取底层指针
int* ptr = myArray.data();
std::cout << "底层数组地址: " << ptr << std::endl;
return 0;
}
与C风格数组的对比
功能 | C风格数组 | std::array |
---|---|---|
类型安全性 | 较低 | 较高 |
越界检查 | 无 | at() 有越界检查 |
STL兼容性 | 需要手动适配 | 天生兼容 |
拷贝赋值操作 | 需手动实现 | 自动支持 |
是否动态分配内存 | 否 | 否 |
无序容器
无序容器(unordered containers),基于哈希表(hash table)实现,用于存储无序的键值对或集合。
std::unordered_map
- 存储键值对(
key-value
)。 - 键唯一,值可重复。
- 类似于
std::map
,但无序。 - 用途:快速查找键对应的值。
std::unordered_multimap
- 类似于
std::unordered_map
。 - 允许键重复(同一键可以有多个值)。
std::unordered_set
- 存储唯一的键,键不可重复。
- 类似于
std::set
,但无序。 - 用途:快速查找某个值是否存在。
std::unordered_multiset
- 类似于
std::unordered_set
。 - 允许键重复。
特点
- 底层实现:基于哈希表,平均时间复杂度为 O(1)。
- 无序存储:元素的存储顺序不保证(内部依赖哈希值)。
- 哈希函数:需要对键进行哈希运算,默认使用
std::hash
。 - 桶分布(bucket)
- 哈希表将元素分散到不同的桶中,每个桶存储多个元素。
- 提供接口查看和调整桶的数量。
示例
std::unordered_map
示例
#include <iostream>
#include <unordered_map>
#include <string>
int main() {
// 创建unordered_map
std::unordered_map<std::string, int> umap;
// 插入元素
umap["Alice"] = 25;
umap["Bob"] = 30;
umap["Charlie"] = 35;
// 遍历unordered_map
for (const auto& pair : umap) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
// 查找元素
auto it = umap.find("Alice");
if (it != umap.end()) {
std::cout << "Found Alice, age: " << it->second << std::endl;
}
return 0;
}
std::unordered_set
示例
#include <iostream>
#include <unordered_set>
int main() {
// 创建unordered_set
std::unordered_set<int> uset = {1, 2, 3, 4, 5};
// 插入元素
uset.insert(6);
// 查找元素
if (uset.find(3) != uset.end()) {
std::cout << "Found 3 in the set." << std::endl;
}
// 遍历unordered_set
for (int num : uset) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
重要接口
公共成员函数
函数 | 作用 |
---|---|
insert() |
插入元素 |
erase() |
删除元素 |
find() |
查找元素,返回迭代器 |
clear() |
清空容器 |
size() |
返回容器中的元素个数 |
empty() |
判断容器是否为空 |
哈希相关函数
函数 | 作用 |
---|---|
bucket_count() |
返回桶的数量 |
bucket_size(bucket) |
返回指定桶中元素的数量 |
load_factor() |
返回负载因子(size / bucket_count ) |
rehash(n) |
重置桶数量,至少为 n |
reserve(n) |
重新分配空间,确保能容纳至少 n 个元素 |
与有序容器的比较
特性 | 无序容器 (unordered_* ) |
有序容器 (map , set ) |
---|---|---|
存储顺序 | 无序 | 按键值顺序 |
时间复杂度(查找) | 平均 O(1) | O(log N) |
底层结构 | 哈希表 | 红黑树 |
支持排序操作 | 不支持 | 支持 |
std::make_shared
推荐使用 std::make_shared
来创建 std::shared_ptr
实例
std::ref
用于创建对对象的引用包装器(reference wrapper),提供一种安全的方式将引用存储在容器或函数对象中。它返回一个 std::reference_wrapper
类型的对象
std::ref 的主要功能是为引用类型创建一个可拷贝的包装器。因为引用本身不是对象,不能直接存储在 STL 容器中或传递给某些函数对象,而 std::reference_wrapper 提供了这种能力。
std::ref
:用于包装左值引用。std::cref
:用于包装左值的 const 引用。
语法
#include <functional>
std::reference_wrapper<T> std::ref(T& t);
std::reference_wrapper<const T> std::cref(const T& t);
-
T& t
是需要包装的左值引用。 -
返回值是一个
std::reference_wrapper<T>
对象。
常见用途
- 存储引用在 STL 容器中:
- 普通引用不能直接存储在容器中,而
std::ref
可以用来间接存储引用。
- 普通引用不能直接存储在容器中,而
- 函数调用:
- 在函数调用中需要传递引用而不是拷贝时,可以使用
std::ref
。
- 在函数调用中需要传递引用而不是拷贝时,可以使用
示例
-
在容器中存储引用
#include <iostream> #include <vector> #include <functional> // std::ref int main() { int a = 10, b = 20, c = 30; // 用std::ref将引用存入容器 std::vector<std::reference_wrapper<int>> refs = {std::ref(a), std::ref(b), std::ref(c)}; // 修改容器中的值会影响原变量 for (auto& ref : refs) { ref.get() += 1; // 使用.get()访问引用 } std::cout << "a: " << a << ", b: " << b << ", c: " << c << std::endl; return 0; }
-
结合
std::bind
使用std::ref
可以用来绑定引用以避免拷贝#include <iostream> #include <functional> // std::bind, std::ref void print_and_increment(int& x) { std::cout << "x: " << x << std::endl; x++; } int main() { int value = 42; // 使用std::bind绑定引用 auto bound_func = std::bind(print_and_increment, std::ref(value)); bound_func(); // 调用时不会拷贝value std::cout << "Value after increment: " << value << std::endl; return 0; }
-
传递引用给线程
在多线程编程中,
std::ref
常用于向线程传递引用。#include <iostream> #include <thread> #include <functional> void increment(int& x) { x++; } int main() { int value = 0; // 使用std::ref传递引用给线程 std::thread t(increment, std::ref(value)); t.join(); std::cout << "Value after thread increment: " << value << std::endl; return 0; }
std::ref
的返回值是 std::reference_wrapper
类型。std::reference_wrapper
提供了一些关键功能:
get()
:访问包装的引用。- 隐式转换为引用类型,方便使用。
- 支持拷贝、赋值操作。
#include <iostream>
#include <functional>
int main() {
int x = 100;
std::reference_wrapper<int> ref = std::ref(x);
// 修改原变量
ref.get() = 200;
std::cout << "x: " << x << std::endl; // 输出: x: 200
return 0;
}
注意事项
-
std::ref
仅支持左值引用:如果传递的是临时对象(右值),编译会失败。
int x = 10; std::ref(x); // 正确 std::ref(10); // 错误,10 是右值
-
需要调用
get()
明确访问引用:在某些场景下(如容器操作)需要使用
.get()
来显式访问包装的引用。 -
避免悬空引用:
如果引用的原始对象已经销毁,
std::ref
将指向无效内存,导致未定义行为。
内存模型
内存模型(Memory Model) 定义了程序在多线程环境下如何共享和访问内存。它决定了不同线程间对变量的操作是否能够安全且一致地被观察到,并提供了相应的规则和工具来保证线程安全
内存中的共享状态
- 共享状态指的是多个线程可以同时访问的内存(例如全局变量或堆上的动态分配内存)。
- 在多线程环境中,多个线程可能同时读取或修改同一共享变量,导致竞争条件(Race Condition)。
数据竞争(Data Race)
- 如果两个线程在没有同步机制的情况下同时访问同一共享变量,且至少有一个线程对该变量进行了写操作,则会发生数据竞争。
- 数据竞争会导致未定义行为。
解决方案:
- 使用同步原语(例如
std::mutex
)。 - 使用
std::atomic
类型或合适的内存序(Memory Order)。
内存序(Memory Ordering)
C++ 内存模型定义了 内存序(Memory Order),它描述了线程之间对内存操作的顺序约束。主要包括:
内存序的分类
std::memory_order_relaxed
- 不保证操作的顺序,仅保证操作是原子的。
- 用于性能要求极高但对操作顺序无严格需求的场景。
- 不提供任何跨线程的同步
memory_order_consume
- 保证对当前操作依赖的数据的访问顺序
- 主要用于读取依赖数据时,确保相关数据的正确性
- 本线程中,所有后续的有关本原子类型的操作,必须在本条原子操作完成之后执行
std::memory_order_acquire
- 当前线程在执行该操作后,能保证所有之前的操作对当前线程可见。
- 通常用于获取锁的场景。
- 本线程中,所有后续的读操作必须在本条原子操作完成后执行
std::memory_order_release
- 当前线程在执行该操作前,能保证所有之前的操作对其他线程可见。
- 通常用于释放锁的场景。
- 本线程中,所有之前的写操作完成后才能执行本条原子操作
std::memory_order_acq_rel
- 同时具备
acquire
和release
的语义。 - 用于读写操作。
- 同时具备
std::memory_order_seq_cst
(默认)- 顺序一致性模型,保证全局的操作顺序性。
- 简单且安全,但可能会有性能开销。
原子操作(Atomic Operation)
- C++ 提供了
std::atomic
类型来确保对共享变量的操作是线程安全的。 - 原子操作不会被打断或中断,确保操作的完整性。
#include <iostream>
#include <atomic>
#include <thread>
std::atomic<int> counter(0);
void increment() {
for (int i = 0; i < 1000; ++i) {
counter.fetch_add(1, std::memory_order_relaxed);
}
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << "Final counter value: " << counter << std::endl;
return 0;
}
同步与屏障
C++ 提供了多种工具来实现线程同步:
-
std::mutex
:用于保护临界区。 -
std::condition_variable
:实现线程间的等待和通知。 -
内存屏障(Memory Fence)
使用
std::atomic_thread_fence
提供显式的内存屏障。
#include <atomic>
#include <iostream>
#include <thread>
std::atomic<bool> ready(false);
int data = 0;
void producer() {
data = 42; // 写操作
std::atomic_thread_fence(std::memory_order_release);
ready.store(true, std::memory_order_relaxed);
}
void consumer() {
while (!ready.load(std::memory_order_relaxed)); // 自旋等待
std::atomic_thread_fence(std::memory_order_acquire);
std::cout << "Data: " << data << std::endl; // 读取操作
}
int main() {
std::thread t1(producer);
std::thread t2(consumer);
t1.join();
t2.join();
return 0;
}
指针与内存模型
在多线程环境中,直接使用裸指针进行共享数据访问可能导致数据竞争。可以通过以下方法改善:
- 使用智能指针(如
std::shared_ptr
)。 - 对指针的访问和修改采用原子操作(
std::atomic<T*>
)。
std::async
用于启动异步任务并返回一个 std::future
对象,这个对象可以用于获取任务的结果。std::async
使得任务能够在后台并发执行,而主线程或其他线程可以继续执行其他工作
示例
#include <iostream>
#include <future>
#include <thread>
int find_square(int x) {
std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时操作
return x * x;
}
int main() {
// 启动异步任务,计算 10 的平方
std::future<int> result = std::async(std::launch::async, find_square, 10);
// 可以在这里做其他工作
std::cout << "Doing other work while waiting for the result...
";
// 获取异步任务的结果
std::cout << "The square of 10 is: " << result.get() << std::endl;
return 0;
}
延迟执行
#include <iostream>
#include <future>
#include <thread>
int find_square(int x) {
std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时操作
return x * x;
}
int main() {
// 启动延迟执行的任务
std::future<int> result = std::async(std::launch::deferred, find_square, 10);
// 可以在这里做其他工作
std::cout << "Doing other work while waiting for the result...
";
// 获取异步任务的结果,只有此时任务才会执行
std::cout << "The square of 10 is: " << result.get() << std::endl;
return 0;
}
- 使用
std::launch::deferred
时,异步任务并不会立即启动,直到你调用result.get()
时,任务才会在当前线程中执行 std::async
在这种模式下不会启动新的线程,而是将任务推迟到get()
调用时执行
std::function
#include <functional>
std::function<返回类型(参数类型...)> func;
- 类型安全
std::function
封装了可调用对象,确保在调用时参数和返回值符合定义的签名。
- 支持任意可调用对象
- 包括普通函数、Lambda 表达式、函数对象、成员函数指针等。
- 动态分配
std::function
内部使用了动态分配,可能引入额外的性能开销。
- 默认构造
- 默认构造的
std::function
对象是空的,调用时会抛出std::bad_function_call
异常。
- 默认构造的
示例
-
存储普通函数
#include <iostream> #include <functional> void printMessage(const std::string& message) { std::cout << message << std::endl; } int main() { std::function<void(const std::string&)> func = printMessage; func("Hello, std::function!"); // 调用存储的普通函数 return 0; }
-
存储Lambdai表达式
#include <iostream> #include <functional> int main() { std::function<int(int, int)> add = [](int a, int b) { return a + b; }; std::cout << "Sum: " << add(3, 5) << std::endl; // 输出:Sum: 8 return 0; }
-
存储函数对象
#include <iostream> #include <functional> struct Multiplier { int factor; Multiplier(int f) : factor(f) {} int operator()(int value) const { return value * factor; } }; int main() { std::function<int(int)> multiplyBy2 = Multiplier(2); std::cout << multiplyBy2(10) << std::endl; // 输出:20 return 0; }
-
存储成员函数
#include <iostream> #include <functional> class MyClass { public: void printMessage(const std::string& msg) const { std::cout << "MyClass: " << msg << std::endl; } }; int main() { MyClass obj; std::function<void(const MyClass&, const std::string&)> func = &MyClass::printMessage; func(obj, "Hello from member function!"); return 0; }
-
动态切换可调用对象
#include <iostream> #include <functional> void func1() { std::cout << "Function 1" << std::endl; } void func2() { std::cout << "Function 2" << std::endl; } int main() { std::function<void()> func; func = func1; func(); // 输出:Function 1 func = func2; func(); // 输出:Function 2 return 0; }
std::bind
和std::placeholder
std::bind
用于创建函数对象。它可以将一个函数(或可调用对象)的参数绑定为特定值或占位符,以生成一个新的可调用对象。
功能
- 部分应用:固定函数的一部分参数,生成一个新的可调用对象。
- 参数重排:改变参数的顺序。
- 参数占位:用占位符指定调用时需要提供的参数。
#include <functional>
std::bind(Function, arg1, arg2, ..., argN)
-
Function
:目标函数或可调用对象。 -
arg1, arg2, ..., argN
:- 具体值:绑定为固定的参数值。
- 占位符:通过
std::placeholders::_1
等形式表示,调用时动态传入。
占位符格式为:std::placeholders::_N
,N
从1开始。
示例
-
绑定普通函数的参数
#include <iostream> #include <functional> // std::bind using namespace std; void greet(const string& name, int age) { cout << "Hello, " << name << "! You are " << age << " years old." << endl; } int main() { // 绑定 name 参数为 "Alice" auto greetAlice = std::bind(greet, "Alice", std::placeholders::_1); // 调用时只需提供 age 参数 greetAlice(25); // 输出:Hello, Alice! You are 25 years old. return 0; }
-
改变参数顺序
#include <iostream> #include <functional> using namespace std; void subtract(int a, int b) { cout << "Result: " << a - b << endl; } int main() { // 交换参数的顺序 auto reversedSubtract = std::bind(subtract, std::placeholders::_2, std::placeholders::_1); reversedSubtract(10, 20); // 输出:Result: 10 return 0; }
-
绑定成员函数
#include <iostream> #include <functional> using namespace std; class Calculator { public: void add(int a, int b) const { cout << "Sum: " << a + b << endl; } }; int main() { Calculator calc; // 绑定成员函数,传入对象实例 auto addFunc = std::bind(&Calculator::add, &calc, std::placeholders::_1, std::placeholders::_2); addFunc(10, 20); // 输出:Sum: 30 return 0; }
-
结合
std::bind
和std::function
#include <iostream> #include <functional> using namespace std; int multiply(int a, int b) { return a * b; } int main() { // 使用 std::bind auto doubleValue = std::bind(multiply, std::placeholders::_1, 2); // 存储到 std::function std::function<int(int)> func = doubleValue; cout << "5 * 2 = " << func(5) << endl; // 输出:5 * 2 = 10 return 0; }
[!note]
Lambda可以替代std::bind
auto greetAlice = [](int age) { greet("Alice", age); };
原文地址:https://www.cnblogs.com/midraos/p/18832424