C++ 代码是否可以同时符合 C++03 标准和 C++11 标准,但根据编译的标准做不同的事情?
auto
可能会导致这样的情况
>>
。您可以想出一种情况,它可以针对这两种标准进行编译。我确信很容易找到更改的另一个是初始化。
auto
会导致这种情况。按照旧的含义,auto
声明需要类型名称;有了新的含义,类型名称是不允许的。
答案是肯定的。从好的方面来说,有:
以前隐式复制对象的代码现在将在可能的情况下隐式移动它们。
不利的一面是,标准附录 C 中列出了几个示例。尽管负面的比正面的多得多,但它们中的每一个都不太可能发生。
字符串文字
#define u8 "abc"
const char* s = u8"def"; // Previously "abcdef", now "def"
和
#define _x "there"
"hello "_x // Previously "hello there", now a user defined string literal
类型转换为 0
在 C++11 中,只有字面量是整数空指针常量:
void f(void *); // #1
void f(...); // #2
template<int N> void g() {
f(0*N); // Calls #2; used to call #1
}
整数除法和取模后的舍入结果
在 C++03 中,允许编译器向 0 或向负无穷大舍入。在 C++11 中,必须向 0 舍入
int i = (-1) / 2; // Might have been -1 in C++03, is now ensured to be 0
嵌套模板右括号之间的空格 >> vs >>
在特化或实例化中,>>
可能被解释为 C++03 中的右移。不过,这更有可能破坏现有代码:(来自 http://gustedt.wordpress.com/2013/12/15/a-disimprovement-observed-from-the-outside-right-angle-brackets/)
template< unsigned len > unsigned int fun(unsigned int x);
typedef unsigned int (*fun_t)(unsigned int);
template< fun_t f > unsigned int fon(unsigned int x);
void total(void) {
// fon<fun<9> >(1) >> 2 in both standards
unsigned int A = fon< fun< 9 > >(1) >>(2);
// fon<fun<4> >(2) in C++03
// Compile time error in C++11
unsigned int B = fon< fun< 9 >>(1) > >(2);
}
运算符 new
现在可能会引发除 std::bad_alloc
之外的其他异常
struct foo { void *operator new(size_t x){ throw std::exception(); } }
try {
foo *f = new foo();
} catch (std::bad_alloc &) {
// c++03 code
} catch (std::exception &) {
// c++11 code
}
用户声明的析构函数具有隐式异常规范来自 What breaking changes are introduced in C++11? 的示例
struct A {
~A() { throw "foo"; } // Calls std::terminate in C++11
};
//...
try {
A a;
} catch(...) {
// C++03 will catch the exception
}
现在需要 size()
个容器在 O(1) 中运行
std::list<double> list;
// ...
size_t s = list.size(); // Might be an O(n) operation in C++03
std::ios_base::failure
不再直接派生自 std::exception
虽然直接基类是新的,但 std::runtime_error
不是。因此:
try {
std::cin >> variable; // exceptions enabled, and error here
} catch(std::runtime_error &) {
std::cerr << "C++11\n";
} catch(std::ios_base::failure &) {
std::cerr << "Pre-C++11\n";
}
我指出 this article 和 the follow-up,它有一个很好的例子,说明 >>
如何将含义从 C++03 更改为 C++11,同时仍然在两者中进行编译。
bool const one = true;
int const two = 2;
int const three = 3;
template<int> struct fun {
typedef int two;
};
template<class T> struct fon {
static int const three = ::three;
static bool const one = ::one;
};
int main(void) {
fon< fun< 1 >>::three >::two >::one; // valid for both
}
关键部分是 main
中的行,它是一个表达式。
在 C++03 中:
1 >> ::three = 0
=> fon< fun< 0 >::two >::one;
fun< 0 >::two = int
=> fon< int >::one
fon< int >::one = true
=> true
在 C++11 中
fun< 1 > is a type argument to fon
fon< fun<1> >::three = 3
=> 3 > ::two > ::one
::two is 2 and ::one is 1
=> 3 > 2 > 1
=> (3 > 2) > 1
=> true > 1
=> 1 > 1
=> false
恭喜,同一个表达式有两个不同的结果。当然,当我测试 C++03 时,它确实提出了 Clang 警告表。
::two
的 typename
true
或 false
。也许我们可以将其用作功能测试 </joke>
warning: comparisons like ‘X<=Y<=Z’ do not have their mathematical meaning [-Wparentheses]
),但仍然是模棱两可的 ::
运算符如何更改含义的一个很好的例子(引用全局范围或取消引用直接位于它之前的那个)
是的,有许多更改会导致相同的代码在 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 中提供了许多函数,如 < em>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.2
C++ 和 ISO C++ 2003 介绍了这个和更多实例。它还包括:
新类型的字符串文字 [...] 具体来说,名为 R、u8、u8R、u、uR、U、UR 或 LR 的宏在与字符串文字相邻时不会扩展,但会被解释为字符串文字的一部分.例如 #define u8 "abc" const char *s = u8"def"; // 以前是“abcdef”,现在是“def”
用户定义的文字字符串支持 [...]以前,#1 将由两个单独的预处理标记组成,宏 _x 将被扩展。在本国际标准中,#1 由单个预处理标记组成,因此宏未展开。 #define _x “那里” “你好”_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::system_error 又派生自std::runtime_error。假定 std::ios_base::failure 直接派生自 std::exception 的有效 C++ 2003 代码在本国际标准中可能以不同方式执行。
一个潜在危险的向后不兼容更改是在序列容器(例如 std::vector
)的构造函数中,特别是在指定初始大小的重载中。在 C++03 中,他们复制了一个默认构造的元素,而在 C++11 中,他们默认构造了每个元素。
考虑这个例子(使用 boost::shared_ptr
使其成为有效的 C++03):
#include <deque>
#include <iostream>
#include "boost/shared_ptr.hpp"
struct Widget
{
boost::shared_ptr<int> p;
Widget() : p(new int(42)) {}
};
int main()
{
std::deque<Widget> d(10);
for (size_t i = 0; i < d.size(); ++i)
std::cout << "d[" << i << "] : " << d[i].p.use_count() << '\n';
}
原因是 C++03 为“指定大小和原型元素”和“仅指定大小”指定了一个重载,如下所示(为简洁起见,省略了分配器参数):
container(size_type size, const value_type &prototype = value_type());
这将始终将 prototype
复制到容器 size
次。因此,当仅使用一个参数调用时,它将创建默认构造元素的 size
个副本。
在 C++11 中,此构造函数签名已被删除并替换为以下两个重载:
container(size_type size);
container(size_type size, const value_type &prototype);
第二个像以前一样工作,创建 prototype
元素的 size
个副本。但是,第一个(现在只处理指定了 size 参数的调用)默认单独构造每个元素。
我对这种变化的猜测是 C++03 重载不能用于只移动元素类型。但这仍然是一个突破性的变化,而且很少记录在案。
deque
包含十个单独的小部件,而不是十个共享相同资源的小部件。
从 std::istream
读取失败的结果已更改。 CppReference 很好地总结了它:
如果提取失败(例如,如果在需要数字的地方输入了一个字母),则值保持不变并设置失败位。 (直到 C++11)如果提取失败,则将零写入 value 并设置 failbit。如果提取导致值太大或太小而无法放入值,则写入 std::numeric_limits
如果您习惯了新的语义然后必须使用 C++03 编写,这主要是一个问题。以下不是特别好的做法,但在 C++11 中定义良好:
int x, y;
std::cin >> x >> y;
std::cout << x + y;
但是,在 C++03 中,上述代码使用了未初始化的变量,因此具有未定义的行为。
int x = 1, y = 1; cin >> x >> y; cout << x*y;
。使用 C++03,当没有 y
可以读取时,这将正确生成 x
。
此线程 What differences, if any, between C++03 and C++0x can be detected at run-time 有示例(从该线程复制)来确定语言差异,例如通过利用 C++11 引用折叠:
template <class T> bool f(T&) {return true; }
template <class T> bool f(...){return false;}
bool isCpp11()
{
int v = 1;
return f<int&>(v);
}
和 c++11 允许本地类型作为模板参数:
template <class T> bool cpp11(T) {return true;} //T cannot be a local type in C++03
bool cpp11(...){return false;}
bool isCpp0x()
{
struct local {} var; //variable with local type
return cpp11(var);
}
这是另一个例子:
#include <iostream>
template<class T>
struct has {
typedef char yes;
typedef yes (&no)[2];
template<int> struct foo;
template<class U> static yes test(foo<U::bar>*);
template<class U> static no test(...);
static bool const value = sizeof(test<T>(0)) == sizeof(yes);
};
enum foo { bar };
int main()
{
std::cout << (has<foo>::value ? "yes" : "no") << std::endl;
}
印刷:
Using c++03: no
Using c++11: yes
noexecpt(true)
,因此析构函数中的throw
现在将调用std::terminate
。但我希望任何编写此类代码的人都会对此感到高兴!catch (std::exception &)
仍然捕获std::ios_base::failure
。operator new
是准确的(它现在可以抛出std::bad_array_new_length
),但您的示例根本没有显示这一点。您显示的代码在 C++03 和 C++11 AFAIK 中是相同的。