CppGuide社区 CppGuide社区
首页
  • 最新谷歌C++风格指南(含C++17/20)
  • C++17详解
  • C++20完全指南
  • C++23快速入门
  • C++语言面试问题集锦
  • 🔥C/C++后端开发常见面试题解析 (opens new window)
  • 网络编程面试题 (opens new window)
  • 网络编程面试题 答案详解 (opens new window)
  • 聊聊WebServer作面试项目那些事儿 (opens new window)
  • 字节跳动面试官现身说 (opens new window)
  • 技术简历指南 (opens new window)
  • 🔥交易系统开发岗位求职与面试指南 (opens new window)
  • 第1章 高频C++11重难点知识解析
  • 第2章 Linux GDB高级调试指南
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 高性能网络通信协议设计精要
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 后端服务重要模块设计探索
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 源码分析系列

    • leveldb源码分析
    • libevent源码分析
    • Memcached源码分析
    • TeamTalk源码分析
    • 优质源码分享 (opens new window)
    • 🔥远程控制软件gh0st源码分析
  • 从零手写C++项目系列

    • C++游戏编程入门(零基础学C++)
    • 🔥使用C++17从零开发一个调试器 (opens new window)
    • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
    • 🔥使用C++从零写一个C语言编译器 (opens new window)
    • 从零用C语言写一个Redis
  • Windows 10系统编程
  • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
  • TCP源码实现超详细注释版.pdf (opens new window)
  • Go语言特性

    • Go开发实用指南
    • Go系统接口编程
    • 高效Go并发编程
    • Go性能调优
    • Go项目架构设计
  • Go项目实战

    • 使用Go从零开发一个数据库
    • 🔥使用Go从零开发一个编译器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥用Go从零写一个编排器(类Kubernetes) (opens new window)
  • Rust编程

    • Rust编程指南
  • 数据库

    • SQL零基础指南
    • MySQL开发与调试指南
GitHub (opens new window)
首页
  • 最新谷歌C++风格指南(含C++17/20)
  • C++17详解
  • C++20完全指南
  • C++23快速入门
  • C++语言面试问题集锦
  • 🔥C/C++后端开发常见面试题解析 (opens new window)
  • 网络编程面试题 (opens new window)
  • 网络编程面试题 答案详解 (opens new window)
  • 聊聊WebServer作面试项目那些事儿 (opens new window)
  • 字节跳动面试官现身说 (opens new window)
  • 技术简历指南 (opens new window)
  • 🔥交易系统开发岗位求职与面试指南 (opens new window)
  • 第1章 高频C++11重难点知识解析
  • 第2章 Linux GDB高级调试指南
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 高性能网络通信协议设计精要
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 后端服务重要模块设计探索
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 源码分析系列

    • leveldb源码分析
    • libevent源码分析
    • Memcached源码分析
    • TeamTalk源码分析
    • 优质源码分享 (opens new window)
    • 🔥远程控制软件gh0st源码分析
  • 从零手写C++项目系列

    • C++游戏编程入门(零基础学C++)
    • 🔥使用C++17从零开发一个调试器 (opens new window)
    • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
    • 🔥使用C++从零写一个C语言编译器 (opens new window)
    • 从零用C语言写一个Redis
  • Windows 10系统编程
  • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
  • TCP源码实现超详细注释版.pdf (opens new window)
  • Go语言特性

    • Go开发实用指南
    • Go系统接口编程
    • 高效Go并发编程
    • Go性能调优
    • Go项目架构设计
  • Go项目实战

    • 使用Go从零开发一个数据库
    • 🔥使用Go从零开发一个编译器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥用Go从零写一个编排器(类Kubernetes) (opens new window)
  • Rust编程

    • Rust编程指南
  • 数据库

    • SQL零基础指南
    • MySQL开发与调试指南
GitHub (opens new window)
  • C++17 详解 说明
  • 第一部分——语言特性
  • 1. 快速入门
  • 2. 移除或修正的语言特性
  • 3. 语言澄清(Language Clarification)
  • 4. 通用语言特性
  • 5. 模板(Templates)
  • 6. 代码标注
  • 第二部分 - 标准库的变化
  • 7. std::optional
  • 8. std::variant
  • 9. std::any
  • 10. std::string_view
  • 11. 字符串转换
  • 12. 搜索器与字符串匹配
  • 13. 文件系统
  • 14. 并行STL算法
  • 15. 标准库中的其他变化
  • 16. 移除和弃用的库特性
    • 16. 移除和弃用的库特性
      • 移除auto_ptr
      • 额外信息
      • 移除std::random_shuffle
      • 额外信息
      • “移除旧的函数式编程相关内容”
      • 额外信息
      • std::iterator已被弃用
      • 额外信息
      • 其他被移除或弃用的小项
      • 弃用shared_ptr::unique()
      • 弃用<codecvt>
      • 移除已弃用的输入/输出流别名
      • 弃用C库头文件
      • 弃用std::result_of
      • 暂时弃用std::memory_order_consume
      • 移除std::function对分配器的支持
      • 编译器支持
  • 第三部分 - 更多示例和用例
  • 17. 使用std::optional和std::variant进行重构
  • 18. 使用[[nodiscard]]强制执行代码契约
  • 19. 用if constexpr替换enable_if——带可变参数的工厂函数
  • 20. 如何实现CSV读取器的并行化
目录

16. 移除和弃用的库特性

# 16. 移除和弃用的库特性

在关于移除或修正的语言特性的章节中,本书仅聚焦于语言层面。但在C++17中,标准库也进行了清理。本章展示了大部分¹被移除或弃用的类型和工具列表。

在本章中,你将学到:

  • 为什么auto_ptr被移除,以及为什么应该使用智能指针
  • 为什么std::random_shuffle算法被移除,更好的替代方案是什么
  • 如何实现自定义迭代器,而不继承已弃用的std::iterator类型
  • 其他被弃用或移除的较小元素有哪些

如果你想查找所有被弃用元素的列表,可以查看标准的 “附录D兼容性特性”,例如在这个链接下:https://timsong-cpp.github.io/cppwp/n4659/depr

# 移除auto_ptr

这可能是所有消息中最好的一条!

C++98引入了auto_ptr,作为对原始指针支持基本资源获取即初始化(RAII,Resource Acquisition Is Initialization)特性的一种方式。然而,由于当时语言中缺乏移动语义(move semantics),这个智能指针很容易被误用,从而导致运行时错误。

下面是一个auto_ptr可能导致程序崩溃的示例:

Chapter Removed Lib Features/auto_ptr_crash.cpp
void doSomething(std::auto_ptr<int> myPtr) { 
    *myPtr = 11;
}

void AutoPtrTest() {
    std::auto_ptr<int> myTest(new int(10));
    doSomething(myTest);
    *myTest = 12; // 哎呀!
}
1
2
3
4
5
6
7
8
9
10

doSomething()按值接受auto_ptr,但由于它不是共享指针,它获得了所管理对象的唯一所有权。之后,当函数执行完毕,指针的副本超出作用域,对象被删除。

在AutoPtrTest()中,当doSomething()执行结束后,指针已经被清理,此时调用*myTest = 12会产生未定义行为。

在C++11中,我们有了智能指针:unique_ptr、shared_ptr和weak_ptr。有了移动语义,语言终于能够支持正确的唯一资源转移。而且,新的智能指针可以存储在标准容器中,这是auto_ptr无法做到的。你应该用unique_ptr替换auto_ptr,因为它是与auto_ptr最直接、最合适的等价物。

我们可以重写这个示例,使用unique_ptr:

Using unique_ptr
void doSomething(std::unique_ptr<int> myPtr) { 
    *myPtr = 11;
}

void AutoPtrTest() {
    auto myTest = std::make_unique<int>(10);
    doSomething(myTest); // 无法编译!
    *myTest = 12; 		 // 移动后使用??
}
1
2
3
4
5
6
7
8
9
10

现在,这段代码将无法编译,因为你需要将unique_ptr移动到doSomething()中。由于移动操作是显式的,这可能需要重新思考解决方案。例如,在这种情况下,也许doSomething()并不需要指针的所有权?或许传递一个没有所有权的原始指针会更好?

或者,你可以使用shared_ptr,这样在doSomething()执行结束后,指针不会被删除,因为shared_ptr使用引用计数。

新的智能指针比auto_ptr功能更强大、更安全,所以自C++11起auto_ptr就被弃用了。编译器应该会报告一个警告:

warning: 'template<class> class std::auto_ptr' is deprecated
1

现在,当你使用符合C++17标准的编译器进行编译时,会得到一个错误。这是使用/std:c++latest编译选项时,MSVC 2017给出的错误:

error C2039: 'auto_ptr': is not a member of 'std'
1

如果你在将auto_ptr转换为unique_ptr时需要帮助,可以查看Clang Tidy,它提供自动转换功能:Clang Tidy:modernize-replace-auto-ptr (opens new window) 。

# 额外信息

这一更改是在N4190 (opens new window)中提出的。

# 移除std::random_shuffle

random_shuffle(first, last)和random_shuffle(first, last, rng)函数在C++14中就已被标记为弃用。原因是在大多数情况下,它使用rand()函数,该函数被认为效率低下,甚至容易出错(因为它使用全局状态)。另外,你可以提供rng函数参数,但在实际使用中它似乎没什么用。如果你需要相同的功能,可以使用std::shuffle:

template < class RandomIt , class URBG >
void shuffle( RandomIt first, RandomIt last, URBG&& g );
1
2

std::shuffle将随机数生成器作为第三个模板参数,它更安全、更易用,也更具扩展性。

下面来看一个如何从random_shuffle转换为shuffle的示例:

Chapter Removed Lib Features/shuffle.cpp
std::vector<int> vec = { 0, 1, 2, 3, 4, 5 };

// C++17之前版本:
std::random_shuffle(begin(vec), end(vec));
for (auto& elem : vec)
    std::cout << elem << ", ";

// C++17版本:
std::random_device randDev;
std::mt19937 gen(randDev());
std::shuffle(begin(vec), end(vec), gen);
for (auto& elem : vec)
    std::cout << elem << ", ";
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 额外信息

更多信息请参见N4190 (opens new window) 。

# “移除旧的函数式编程相关内容”

像bind1st()、bind2nd()、mem_fun()等函数是在C++98时代引入的,现在已经不再需要,因为你可以使用lambda表达式。而且,这些函数没有更新以处理完美转发(perfect forwarding)、decltype以及C++11中的其他技术。因此,在现代代码中最好不要使用它们。

被移除的函数:

  • unary_function() / pointer_to_unary_function()
  • binary_function() / pointer_to_binary_function()
  • bind1st() / binder1st
  • bind2nd() / binder2nd
  • ptr_fun()
  • mem_fun()
  • mem_fun_ref()

例如,要替换bind1st/bind2nd,你可以使用lambda表达式、自C++11起可用的std::bind,或者自C++20起应该可用的std::bind_front。

Chapter Removed Lib Features/bind.cpp
auto onePlus = std::bind1st(std::plus<int>(), 1);
auto minusOne = std::bind2nd(std::minus<int>(), 1);
std::cout << onePlus(10) << ", " << minusOne(10) << '\n ';

// 使用硬编码的lambda表达式:
auto lamOnePlus1 = [](int b) { return 1 + b; };
auto lamMinusOne1 = [](int b) { return b - 1; };
std::cout << lamOnePlus1(10) << ", " << lamMinusOne1(10) << '\n ';

// 带有初始化捕获的lambda表达式
auto lamOnePlus = [a=1](int b) { return a + b; };
auto lamMinusOne = [a=1](int b) { return b - a; };
std::cout << lamOnePlus(10) << ", " << lamMinusOne(10) << '\n ';
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 额外信息

更多信息请参见N4190 (opens new window) 。

# std::iterator已被弃用

标准库API要求每个迭代器类型都必须公开五个类型定义:

  • iterator_category:迭代器的类型
  • value_type:迭代器中存储的类型
  • difference_type:两个迭代器相减的结果类型
  • pointer:存储类型的指针类型
  • reference:存储类型的引用类型

iterator_category必须是input_iterator_tag、forward_iterator_tag、bidirectional_iterator_tag或random_access_iterator_tag中的一种。

在C++17之前,如果想要定义一个自定义迭代器,可以使用std::iterator作为基类。在C++14中,它的定义如下:

template <
    class Category, class T,
    class Distance = std::ptrdiff_t, class Pointer = T*,
    class Reference = T& > 
struct iterator;
1
2
3
4
5

这个辅助类可以简化类型定义的编写:

class ColumnIterator
: public std::iterator<std::random_access_iterator_tag, Column>
{
    // ...
};
1
2
3
4
5

在C++17中,不能再从std::iterator派生,而需要显式地编写特征类型定义:

class ColumnIterator    {
public:
    using iterator_category = std::random_iterator_tag;
    using value_type = Column;
    using difference_type = std::ptrdiff_t;
    using pointer = Column*;
    using reference = Column&;
    // ...
};
1
2
3
4
5
6
7
8
9

虽然这样需要编写更多代码,但代码可读性更强,也更不容易出错。

例如,相关论文提到了标准库中的以下示例:

template <class T, class charT = char, class traits = char_traits<charT> > 
class ostream_iterator :
public iterator<output_iterator_tag, void, void, void, void>;
1
2
3

在上述代码中,定义中的四个void类型的含义并不明确。

此外,std::iterator可能会在名称查找时导致复杂的错误,尤其是当在派生迭代器中使用与基类相同的名称时。

# 额外信息

更多信息请查看论文P0174R2 - 《在C++17中弃用标准库中过时的部分 (opens new window)》(Deprecating Vestigial Library Parts in C++17) 。

# 其他被移除或弃用的小项

下面来看看标准库中一些被修改的小项。

# 弃用shared_ptr::unique()

在C++14中,shared_ptr::unique()被定义为use_count() == 1。但由于use_count()在多线程环境中只是一个近似值(因为它不涉及任何同步访问),所以unique()并不可靠。

更多信息请查看P0521 (opens new window)。

# 弃用<codecvt>

<codecvt>头文件声明了几个转换工具:codecvt_utf8、codecvt_utf16和codecvt_utf8_utf16。但这些类使用起来很困难,不安全,而且定义也不够完善。

注意:std::codesvt类没有被弃用,因为它位于另一个头文件<locale>中,所以仍然可以使用。

更多信息请查看P0618R0 (opens new window)。

# 移除已弃用的输入/输出流别名

自C++11起,以下输入/输出流类型和方法已被弃用,现在已从标准库中移除。

typedef T1 io_state;  // T1是整数
typedef T2 open_mode; // T2是整数
typedef T3 seek_dir;  // T3是整数
typedef implementation-defined streamoff; 
typedef implementation-defined streampos; 
basic_streambuf::stossc()
1
2
3
4
5
6

此外,依赖于上述类型的方法和重写也被移除了。

此前,它们都在标准附录D:[depr.ios.members] 中声明。更多信息请查看P0004R1 (opens new window)。

# 弃用C库头文件

以下头文件现在已被弃用:

  • <ccomplex>
  • <cstdalign>
  • <cstdbool>
  • <ctgmath>

这是清理依赖C99规范内容的结果。在C++17中,标准参考的是C11而非C99。

更多信息请查看P0063R3 (opens new window) 。

# 弃用std::result_of

类型特征std::result_of使用了非可变参数模板声明,这限制了它的使用。建议使用增强的类型特征,例如std::invoke_result。

更多信息请查看P0604R0 (opens new window) 。

# 暂时弃用std::memory_order_consume

memory_order_consume的内存模型难以实现,而且在标准中的定义也不够完善。该模型现在被暂时弃用,未来可能会重新出现。

更多信息请查看P0371R1 (opens new window) 。

# 移除std::function对分配器的支持

std::function使用类型擦除来处理可调用对象。为这种类型提供分配器支持非常复杂(或者难以高效实现),因此决定将其从标准中移除。

更多信息请查看https://wg21.link/P0302R1。

# 编译器支持

特性 GCC Clang MSVC
移除auto_ptr、random_shuffle、旧的<functional>相关内容 否(为保持兼容性保留) 尚未支持 VS 2015
弃用std::iterator 尚未支持 尚未支持 VS 2017 15.5
弃用shared_ptr::unique() 尚未支持 尚未支持 VS 2017 15.5
弃用<codecvt> 尚未支持 尚未支持 VS 2017 15.5
移除已弃用的输入/输出流别名 尚未支持 3.8 VS 2015.2
弃用result_of 尚未支持 尚未支持 VS 2017 15.3
移除std::function对分配器的支持 尚未支持 4.0 VS 2017 15.5
C++17应参考C11而非C99 9.1 7.0 VS 2015
移除已弃用的输入/输出流别名 尚未支持 3.8 VS 2015.2
上次更新: 2025/04/01, 13:21:34
15. 标准库中的其他变化
第三部分 - 更多示例和用例

← 15. 标准库中的其他变化 第三部分 - 更多示例和用例→

最近更新
01
第二章 关键字static及其不同用法
03-27
02
第一章 auto与类型推导
03-27
03
第四章 Lambda函数
03-27
更多文章>
Copyright © 2024-2025 沪ICP备2023015129号 张小方 版权所有
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式