C++标准库实用工具与特性详解
立即解锁
发布时间: 2025-08-22 00:43:46 阅读量: 2 订阅数: 16 


深入解析C++标准库:从入门到精通
### C++标准库实用工具与特性详解
#### 1. 异常抛出与标准异常类
在C++中,抛出标准异常是比较简单的。例如:
```cpp
throw std::out_of_range ("out_of_range (somewhere, somehow)");
throw std::system_error (std::make_error_code(std::errc::invalid_argument), "argument ... is not valid");
```
不过,不能抛出基类`exception`以及用于语言支持的异常类(如`bad_cast`、`bad_typeid`、`bad_exception`)的异常。
另外,还可以定义直接或间接从`exception`类派生的特殊异常类。要做到这一点,必须确保`what()`机制或`code()`机制能够正常工作,因为`what()`是虚函数。
#### 2. 可调用对象
C++标准库在不同地方使用了“可调用对象”这一术语,它指的是能够以某种方式调用某些功能的对象,主要包括以下几种:
- **函数**:可以传递额外参数作为参数。
- **成员函数指针**:第一个额外参数必须是对象的引用或指针,其余参数作为成员函数的参数。
- **函数对象**:通过重载`operator()`来调用。
- **lambda表达式**:严格来说,它是一种函数对象。
以下是一个示例代码:
```cpp
void func (int x, int y);
auto l = [] (int x, int y) {
// ...
};
class C {
public:
void operator () (int x, int y) const;
void memfunc (int x, int y) const;
};
int main()
{
C c;
std::shared_ptr<C> sp(new C);
// bind()使用可调用对象绑定参数
std::bind(func,77,33)();
std::bind(l,77,33)();
std::bind(C(),77,33)();
std::bind(&C::memfunc,c,77,33)();
std::bind(&C::memfunc,sp,77,33)();
// async()使用可调用对象启动(后台)任务
std::async(func,42,77);
std::async(l,42,77);
std::async(c,42,77);
std::async(&C::memfunc,&c,42,77);
std::async(&C::memfunc,sp,42,77);
}
```
可以看到,智能指针也可用于传递成员函数调用的对象。一般来说,可以使用`std::function<>`类来声明可调用对象。
#### 3. 并发与多线程
在C++11之前,C++语言和标准库对并发没有提供支持,不过实现可以自行给出一些保证。C++11改变了这一状况,核心语言和库都得到了改进,以支持并发编程。
在核心语言方面:
- 引入了内存模型,保证不同线程对两个不同对象的更新是相互独立的。在C++11之前,无法保证一个线程写入`char`不会干扰另一个线程写入另一个`char`。
- 引入了新关键字`thread_local`,用于定义线程特定的变量和对象。
在库方面:
- 提供了一些关于线程安全的保证。
- 提供了支持并发的类和函数(用于启动和同步多个线程)。
C++标准库关于并发和多线程的一般约束如下:
| 约束类型 | 具体描述 |
| ---- | ---- |
| 共享对象修改 | 多个线程共享一个库对象,且至少有一个线程修改该对象时,可能会导致未定义行为。除非该类型的对象明确指定可以无数据竞争地共享,或者用户提供了锁定机制。 |
| 对象构造与使用 | 在一个线程中构造对象时,在另一个线程中使用该对象会导致未定义行为。同样,在一个线程中销毁对象时,在另一个线程中使用该对象也会导致未定义行为,这甚至适用于用于线程同步的对象。 |
支持并发访问库对象的重要场景如下:
- **STL容器和容器适配器**:
- 支持并发只读访问,如调用`begin()`、`end()`等非常量成员函数,以及通过迭代器访问(前提是不修改容器)。
- 除了`vector<bool>`,支持对同一容器的不同元素进行并发访问。
- **标准流的格式化输入输出**:与C I/O同步的标准流(如`std::cin`、`std::cout`、`std::cerr`)支持并发访问,但可能会导致字符交错。对于字符串流、文件流或流缓冲区,并发访问会导致未定义行为。
- **特定函数调用**:`atexit()`和`at_quick_exit()`的并发调用是同步的,设置或获取新的、终止或意外处理程序的函数(如`set_new_handler()`等)也是同步的,`getenv()`也是同步的。
- **默认分配器**:除析构函数外,默认分配器的所有成员函数的并发访问是同步的。
#### 4. 分配器
C++标准库在多个地方使用特殊对象来处理内存的分配和释放,这些对象被称为分配器。它们代表一种特殊的内存模型,用于将内存使用需求抽象为对内存的原始调用。同时使用不同的分配器对象可以在程序中使用不同的内存模型。
最初,分配器作为STL的一部分引入,用于处理PC上不同指针类型(如近指针、远指针和巨指针)的问题。现在,分配器成为了使用某些内存模型(如共享内存、垃圾回收和面向对象数据库)的技术解决方案的基础,而无需更改接口。
C++标准库定义了默认分配器:
```cpp
namespace std {
template <typename T>
class allocator;
}
```
默认分配器在可以使用分配器作为参数的地方都作为默认值使用。它会调用`new`和`delete`运算符进行内存分配和释放,但具体调用时机和频率未作规定。在大多数程序中,通常使用默认分配器。其他库有时会提供适合特定需求的分配器,在这种情况下,只需将它们作为参数传递即可。只有在极少数情况下,才有必要编写分配器。
#### 5. 实用工具概述
C++标准库的实用工具包括一些小而简单的类、类型或函数,用于执行常见任务,主要有以下几类:
- `pair<>`和`tuple<>`类
- 智能指针类(如`shared_ptr<>`和`unique_ptr`)
- 数值限制
- 类型特征和类型实用工具
- 辅助函数(如`min()`、`max()`和`swap()`)
- `ratio<>`类
- 时钟和计时器
- 一些重要的C函数
这些实用工具中的大部分在C++标准的“通用实用工具”条款中描述,其余部分与库的其他主要组件一起描述,原因可能是它们主要与特定组件一起使用,或者出于历史原因。
#### 6. 对和元组
在C++98中,提供了一个简单的类来处理不同类型的值对,而无需定义特定的类。TR1引入了元组类,将值对的概念扩展到任意但有限数量的元素。C++11使用可变参数模板重新实现了元组类,现在有了适用于任意大小异构集合的标准元组类。同时,`pair`类仍然用于处理两个元素,并且可以与二元组一起使用。
##### 6.1 对(Pairs)
`pair`类将两个值视为一个单元,在C++标准库的多个地方都有使用。例如,`map`、`multimap`、`unordered_map`和`unordered_multimap`容器类使用`pair`来管理它们的元素(键值对)。
`pair`类的定义如下:
```cpp
namespace std {
template <typename T1, typename T2>
struct pair {
T1 first;
T2 second;
// ...
};
}
```
`pair`类的操作如下表所示:
| 操作 | 效果 |
| ---- | ---- |
| `pair<T1,T2> p` | 默认构造函数,创建一个`T1`和`T2`类型的值对,使用默认构造函数初始化。 |
| `pair<T1,T2> p(val1,val2)` | 创建一个`T1`和`T2`类型的值对,用`val1`和`val2`初始化。 |
| `pair<T1,T2> p(rv1,rv2)` | 创建一个`T1`和`T2`类型的值对,使用移动语义初始化。 |
| `pair<T1,T2> p(piecewise_construct, t1,t2)` | 使用元组`t1`和`t2`的元素初始化`T1`和`T2`类型的值对。 |
| `pair<T1,T2> p(p2)` | 复制构造函数,创建`p`作为`p2`的副本。 |
| `pair<T1,T2> p(rv)` | 移动构造函数,将`rv`的内容移动到`p`(可能进行隐式类型转换)。 |
| `p = p2` | 将`p2`的值赋给`p`(C++11起支持隐式类型转换)。 |
| `p = rv` | 移动赋值,将`rv`的值赋给`p`(C++11起支持,可能进行隐式类型转换)。 |
| `p.first` | 直接访问对中的第一个值。 |
| `p.second` | 直接访问对中的第二个值。 |
| `get<0>(p)` | 等同于`p.first`(C++11起)。 |
| `get<1>(p)` | 等同于`p.second`(C++11起)。 |
| `p1 == p2` | 判断`p1`是否等于`p2`(等同于`p1.first==p2.first && p1.second==p2.second`)。 |
| `p1 != p2` | 判断`p1`是否不等于`p2`(`!(p1==p2)`)。 |
| `p1
0
0
复制全文
相关推荐










