是的,有许多更改会导致相同的代码在 C++03 和 C++11 之间产生不同的行为。排序规则的差异导致一些有趣的变化,包括一些以前未定义的行为变得明确。
1.初始化列表中同一变量的多个突变
一个非常有趣的极端情况是初始化列表中同一变量的多个突变,例如:
int main()
{
int count = 0 ;
int arrInt[2] = { count++, count++ } ;
return 0 ;
}
在 C++03 和 C++11 中,这是很好的定义,但 order of evaluation in C++03 is unspecified 但在 C++11 they are evaluated in the order in which they appear 中。因此,如果我们在 C++03 模式下使用 clang 进行编译,它会提供以下警告 (see it live):
warning: multiple unsequenced modifications to 'count' [-Wunsequenced]
int arrInt[2] = { count++, count++ } ;
^ ~~
但在 C++11 中不提供警告 (see it live)。
2.新的排序规则使 i = ++ i + 1;在 C++11 中定义良好
C++03之后采用的新排序规则意味着:
int i = 0 ;
i = ++ i + 1;
在 C++11 中不再是未定义的行为,这在 defect report 637. Sequencing rules and example disagree 中有介绍
3.新的排序规则也使得 ++++i ;在 C++11 中定义良好
C++03之后采用的新排序规则意味着:
int i = 0 ;
++++i ;
在 C++11 中不再是未定义的行为。
4.稍微更明智的有符号左移
后来的 C++11 草案包括 N3485,我在下面链接 fixed the undefined behavior of shifting a 1 bit into or past the sign bit。这也包含在defect report 1457 中。 Howard Hinnant 在Is left-shifting (<<) a negative integer undefined behavior in C++11? 上评论了这一变化的重要性。
5. constexpr 函数可以被视为 C++11 中的编译时常量表达式
C++11 引入了constexpr 函数:
constexpr 说明符声明可以在编译时计算函数或变量的值。然后可以在仅允许编译时常量表达式的情况下使用此类变量和函数。
虽然 C++03 没有 constexpr 特性,但我们不必显式使用 constexpr 关键字,因为标准库在 C++ 中提供了许多函数11 作为 constexpr。例如std::numeric_limits::min。这可能会导致不同的行为,例如:
#include <limits>
int main()
{
int x[std::numeric_limits<unsigned int>::min()+2] ;
}
在 C++03 中使用 clang 这将导致 x 成为可变长度数组,即 an extension 并将生成以下警告:
warning: variable length arrays are a C99 feature [-Wvla-extension]
int x[std::numeric_limits<unsigned int>::min()+2] ;
^
而在 C++11 中,std::numeric_limits<unsigned int>::min()+2 是编译时常量表达式,不需要 VLA 扩展。
6.在 C++11 中,为您的析构函数隐式生成 noexcept 异常规范
由于在 C++11 中用户定义的析构函数具有隐含的 noexcept(true) 规范,如 noexcept destructors 中所述,这意味着以下程序:
#include <iostream>
#include <stdexcept>
struct S
{
~S() { throw std::runtime_error(""); } // bad, but acceptable
};
int main()
{
try { S s; }
catch (...) {
std::cerr << "exception occurred";
}
std::cout << "success";
}
在 C++11 中会调用 std::terminate,但会在 C++03 中成功运行。
7.在 C++03 中,模板参数不能有内部链接
Why std::sort doesn't accept Compare classes declared within a function 很好地介绍了这一点。所以下面的代码不应该在 C++03 中工作:
#include <iostream>
#include <vector>
#include <algorithm>
class Comparators
{
public:
bool operator()(int first, int second)
{
return first < second;
}
};
int main()
{
class ComparatorsInner : public Comparators{};
std::vector<int> compares ;
compares.push_back(20) ;
compares.push_back(10) ;
compares.push_back(30) ;
ComparatorsInner comparatorInner;
std::sort(compares.begin(), compares.end(), comparatorInner);
std::vector<int>::iterator it;
for(it = compares.begin(); it != compares.end(); ++it)
{
std::cout << (*it) << std::endl;
}
}
但目前clang 允许在 C++03 模式下使用此代码并发出警告,除非您使用 -pedantic-errors 标志,这有点恶心,see it live。
8. >> 关闭多个模板时不再格式错误
使用>> 关闭多个模板不再是错误的格式,但会导致代码在 C++03 和 C+11 中产生不同的结果。下面的例子取自Right angle brackets and backwards compatibility:
#include <iostream>
template<int I> struct X {
static int const c = 2;
};
template<> struct X<0> {
typedef int c;
};
template<typename T> struct Y {
static int const c = 3;
};
static int const c = 4;
int main() {
std::cout << (Y<X<1> >::c >::c>::c) << '\n';
std::cout << (Y<X< 1>>::c >::c>::c) << '\n';
}
在 C++03 中的结果是:
0
3
在 C++11 中:
0
0
9. C++11 改变了一些 std::vector 构造函数
来自this answer 的稍微修改的代码表明使用来自std::vector 的以下构造函数:
std::vector<T> test(1);
在 C++03 和 C++11 中产生不同的结果:
#include <iostream>
#include <vector>
struct T
{
bool flag;
T() : flag(false) {}
T(const T&) : flag(true) {}
};
int main()
{
std::vector<T> test(1);
bool is_cpp11 = !test[0].flag;
std::cout << is_cpp11 << std::endl ;
}
10.在聚合初始化器中缩小转换范围
在 C++11 中,聚合初始值设定项中的缩小转换格式不正确,看起来 gcc 在 C++11 和 C++03 中都允许这样做,尽管它在 C++11 中默认提供警告:
int x[] = { 2.0 };
这在草案 C++11 标准部分 8.5.4 List-initialization 段落 3 中有介绍:
类型 T 的对象或引用的列表初始化定义如下:
并包含以下项目符号(强调我的):
否则,如果 T 是类类型,则考虑构造函数。枚举适用的构造函数,并通过重载决议(13.3、13.3.1.7)选择最佳构造函数。 如果需要缩小转换(见下文)来转换任何参数,则程序格式错误
draft C++ standard 部分 annex C.2C++ 和 ISO C++ 2003 中介绍了这个和更多实例。它还包括:
-
新类型的字符串文字 [...] 具体来说,名为 R、u8、u8R、u、uR、U、UR 或 LR 的宏在与字符串文字相邻时不会扩展,但会被解释为字符串文字。例如
#define u8 "abc"
const char *s = u8"def"; // Previously "abcdef", now "def"
-
用户定义的文字字符串支持 [...]以前,#1 将由两个单独的预处理标记组成,宏 _x 将被扩展。在本国际标准中,#1 由单个预处理标记组成,因此宏
没有展开。
#define _x "there"
"hello"_x // #1
为整数 / 和 % [...] 的结果指定舍入 使用整数除法的 2003 代码将结果朝 0 或朝负无穷大舍入,而这
国际标准总是将结果四舍五入到 0。
size() 成员函数的复杂性现在保持不变 [...] 一些符合 C++ 2003 的容器实现可能不符合本国际标准中指定的 size() 要求。将 std::list 等容器调整为更严格的要求可能需要不兼容的更改。
更改 std::ios_base::failure 的基类 [...] std::ios_base::failure 不再直接从 std::exception 派生,而是从 std::system_error 派生,它又派生自 std::runtime_error。假定 std::ios_base::failure 直接派生自 std::exception 的有效 C++ 2003 代码在本国际标准中的执行方式可能不同。