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
1

在Clang(Clang 7.0)中则会报错:

error: ISO C++17 does not allow'register' storage class specifier
1
# 额外信息

该变更在P0001R1 (opens new window)中被提出。

# 移除已弃用的operator++(bool)

针对bool类型的递增运算符operator++已经被弃用很长时间了!委员会早在1998年(C++98)就不建议使用它,但直到现在才最终决定将其从语言中移除。请注意,bool类型从未支持过operator--

如果你尝试编写以下代码:

bool b; 
b++;
1
2

你应该会从GCC(GCC 8.1)得到类似这样的错误:

error: use of an operand of type 'bool' in 'operator++' is forbidden in C++17
1
# 额外信息

该变更在P0002R1 (opens new window)中被提出。

# 移除已弃用的异常规范

在C++17中,异常规范将成为类型系统的一部分(在下一章“语言澄清”中会讨论)。然而,标准中包含的旧的、已弃用的异常规范既不实用又很少被使用。

例如:

void fooThrowsInt(int a) throw (int ) {
    printf_s("can throw ints\n");
    if (a == 0)
        throw 1; 
}
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
1
# 额外信息

该变更在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()};     // 直接初始化
1
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
1
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起无需消息
1
2

在许多情况下,你检查的条件本身就足够明确,无需在消息字符串中提及。

# 额外信息

该变更在N3928 (opens new window)中被提出。

# 基于范围的for循环中beginend类型不同

C++11添加了基于范围的for循环:

for (for-range-declaration : for-range-initializer)
	statement;
1
2

根据C++14标准,该循环等效于以下代码:

auto && __range = for-range-initializer;
for ( auto __begin = begin-expr, __end = end-expr;
		__begin != __end;
		++__begin ) {
	for-range-declaration = *__begin;
	statement;
}
1
2
3
4
5
6
7

如你所见,beginend具有相同的类型。这在一定程度上工作得很好,但可扩展性不足。例如,你可能希望迭代到某个哨兵值(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;
}
1
2
3
4
5
6
7

beginend的类型可能不同,仅要求存在比较运算符。该变更对现有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循环中beginend类型不同 6.0 3.6 VS 2017
上次更新: 2025/04/01, 13:21:34

社区交流

讨论与留言

前往 GitHub Issues →

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

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