2. 移除或修正的语言特性
# 2. 移除或修正的语言特性
C++17标准有1600多页,相比C++14增加了200多页(例如,2017年7月的草案N4687与C++14草案N4140相比。)!幸运的是,语言规范在一些地方得到了清理,一些旧的或可能有害的特性被清除。本章简要列出了一些被移除或修正的语言元素。有关标准库中的变更列表,请参阅“移除和弃用的库特性”一章。
在本章中,你将了解到:
- 从语言中移除的内容,如
register
关键字或针对bool
类型的operator++
。 - 得到修正的内容,特别是使用大括号初始化时的
auto
类型推导。 - 其他改进,如针对
static_assert
和基于范围的for
循环的改进。
# 移除的元素
C++每次迭代背后的核心概念之一是与以前版本的兼容性。我们希望语言中有新的特性,但同时,我们的旧项目仍应能够编译。然而,有时会有机会移除那些错误的或很少使用的部分。
本节简要介绍从标准中移除的内容。
# 移除register关键字
register
关键字在2011年(C++11)已被弃用,从那时起它就不再有实际意义。它在C++17中被移除。该关键字被保留,可能会在未来的标准修订中重新赋予用途(例如,auto
关键字就被重新使用,现在是一个全新且强大的特性)。
如果你使用register
声明变量:register int a;
你可能会得到以下警告(以下是GCC8.1的警告信息):
warning: ISO C++17 does not allow'register' storage class specifier
在Clang(Clang 7.0)中则会报错:
error: ISO C++17 does not allow'register' storage class specifier
# 额外信息
该变更在P0001R1 (opens new window)中被提出。
# 移除已弃用的operator++(bool)
针对bool
类型的递增运算符operator++
已经被弃用很长时间了!委员会早在1998年(C++98)就不建议使用它,但直到现在才最终决定将其从语言中移除。请注意,bool
类型从未支持过operator--
。
如果你尝试编写以下代码:
bool b;
b++;
2
你应该会从GCC(GCC 8.1)得到类似这样的错误:
error: use of an operand of type 'bool' in 'operator++' is forbidden in C++17
# 额外信息
该变更在P0002R1 (opens new window)中被提出。
# 移除已弃用的异常规范
在C++17中,异常规范将成为类型系统的一部分(在下一章“语言澄清”中会讨论)。然而,标准中包含的旧的、已弃用的异常规范既不实用又很少被使用。
例如:
void fooThrowsInt(int a) throw (int ) {
printf_s("can throw ints\n");
if (a == 0)
throw 1;
}
2
3
4
5
特别注意throw(int)
这部分。
上述代码自C++11起就已被弃用。唯一实用的异常声明是throw()
,表示这段代码不会抛出任何异常。自C++11起,建议使用noexcept
。
在Clang 4.0中,你会得到以下错误:
error: ISO C++1z does not allow dynamic exception specifications [-Wdynamic-exception-spec] note: use 'noexcept(false)' instead
# 额外信息
该变更在P0003R5 (opens new window)中被提出。
# 移除三字符组
三字符组是一种特殊的字符序列,在系统不支持7位ASCII(如ISO 646)时可以使用。例如,??=
会生成#
,??-
会生成~
。(C++的所有基本源字符集都可以用7位ASCII表示)如今,三字符组很少被使用,在翻译阶段移除它们可以使编译过程更简单。以下是直到C++17还在使用的所有三字符组及其替换字符的表格:
三字符组 | 替换字符 |
---|---|
??= | # |
??( | [ |
??< | { |
??/ | \ |
??) | ] |
??> | } |
??’ | ˆ |
??! | \| |
??- | ~ |
# 额外信息
该变更在N4086 (opens new window)中被提出。
# 修正内容
关于什么算是语言标准中的修正,我们可能存在争议。以下有三点内容,看起来像是对之前规则中缺失或存在问题之处的修正。
# 直接列表初始化的新auto
规则
自C++11起,就出现了一个奇怪的问题:auto x { 1 };
会被推导为std::initializer_list<int>
。这种行为并不直观,因为在大多数情况下,人们会期望它的工作方式与int x { 1 };
类似。
在现代C++中,花括号初始化是首选模式,但这类异常情况削弱了该特性。
有了新的标准,我们可以修复这个问题,使其推导为int
类型。
为实现这一点,我们需要了解两种初始化方式——复制初始化和直接初始化:
// foo()是一个按值返回某种类型的函数
auto x = foo(); // 复制初始化
auto x{foo()}; // 直接初始化,初始化一个initializer_list(直到C++17)
int x = foo(); // 复制初始化
int x{foo()}; // 直接初始化
2
3
4
5
对于直接初始化,C++17引入了新规则:
- 对于只有一个元素的花括号初始化列表,
auto
推导将从该元素进行推导。 - 对于包含多个元素的花括号初始化列表,
auto
推导将是格式错误的。
例如:
auto x1 = { 1, 2 }; // decltype(x1)是std::initializer_list<int>
auto x2 = { 1, 2.0 }; // 错误:无法推导元素类型
auto x3{ 1, 2 }; // 错误:不是单个元素
auto x4 = { 3 }; // decltype(x4)是std::initializer_list<int>
auto x5{ 3 }; // decltype(x5)是int
2
3
4
5
# 额外信息
该变更在N3922 (opens new window)和N3681 (opens new window)中被提出。编译器很早就修复了这个问题,GCC 5.0(2015年中)、Clang 3.8(2016年初)和MSVC 2015(2015年中)中就已提供该改进。这比C++17获批要早得多。
# 不带消息的static_assert
该特性为static_assert
添加了一个新的重载。它允许在static_assert
中仅使用条件,而无需传递消息。
它将与其他断言(如BOOST_STATIC_ASSERT
)兼容。有使用boost
经验的程序员现在切换到C++17的static_assert
将不会有问题。
static_assert (std::is_arithmetic_v<T>, "T必须是算术类型");
static_assert (std::is_arithmetic_v<T>); // 自C++17起无需消息
2
在许多情况下,你检查的条件本身就足够明确,无需在消息字符串中提及。
# 额外信息
该变更在N3928 (opens new window)中被提出。
# 基于范围的for
循环中begin
和end
类型不同
C++11添加了基于范围的for
循环:
for (for-range-declaration : for-range-initializer)
statement;
2
根据C++14标准,该循环等效于以下代码:
auto && __range = for-range-initializer;
for ( auto __begin = begin-expr, __end = end-expr;
__begin != __end;
++__begin ) {
for-range-declaration = *__begin;
statement;
}
2
3
4
5
6
7
如你所见,begin
和end
具有相同的类型。这在一定程度上工作得很好,但可扩展性不足。例如,你可能希望迭代到某个哨兵值(sentinel value),而该值的类型与范围起始值的类型不同。
在C++17中,基于范围的for
循环被定义为等效于以下代码:
auto && __range = for-range-initializer;
auto __begin = begin-expr;
auto __end = end-expr;
for ( ; __begin != __end; ++__begin ) {
for-range-declaration = *__begin;
statement;
}
2
3
4
5
6
7
begin
和end
的类型可能不同,仅要求存在比较运算符。该变更对现有for
循环没有影响,但为库提供了更多选择。例如,这个小的变更使得范围技术规范(Range TS)(以及C++20中的范围库)能够与基于范围的for
循环协同工作。
# 额外信息
该变更在P0184R0(https://wg21.link/p0184r0)中被提出。
# 编译器支持
特性 | GCC | Clang | MSVC |
---|---|---|---|
移除register 关键字 | 7.0 | 3.8 | VS 2017 15.3 |
移除已弃用的operator++(bool) | 7.0 | 3.8 | VS 2017 15.3 |
移除已弃用的异常规范 | 7.0 | 4.0 | VS 2017 15.5 |
移除三字符组 | 5.1 | 3.5 | VS 2010 |
直接列表初始化的新auto 规则 | 5.0 | 3.8 | VS 2015 |
不带消息的static_assert | 6.0 | 2.5 | VS 2017 |
基于范围的for 循环中begin 和end 类型不同 | 6.0 | 3.6 | VS 2017 |