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_ptrshared_ptrweak_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_tagforward_iterator_tagbidirectional_iterator_tagrandom_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_utf8codecvt_utf16codecvt_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_ptrrandom_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

社区交流

讨论与留言

前往 GitHub Issues →

本留言区仅对应当前文章,欢迎补充观点、提出问题或帮助修正文中疏漏。 留言由 GitHub/Gitalk 提供,需要使用 GitHub 登录。

最近更新
第 1 课:专栏导论与安全边界
07-01
第 3 课:安装编译与调试环境
07-01
第 4 课:第三方库下载、编译与依赖管理
07-01
更多文章>