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; // 哎呀!
}
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; // 移动后使用??
}
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
现在,当你使用符合C++17标准的编译器进行编译时,会得到一个错误。这是使用/std:c++latest
编译选项时,MSVC 2017给出的错误:
error C2039: 'auto_ptr': is not a member of 'std'
如果你在将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 );
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 << ", ";
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 ';
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;
2
3
4
5
这个辅助类可以简化类型定义的编写:
class ColumnIterator
: public std::iterator<std::random_access_iterator_tag, Column>
{
// ...
};
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&;
// ...
};
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>;
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()
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 |