C ++代码可以在C ++ 03和C ++ 11中都有效,但可以做不同的事情吗?

C ++代码是否可以符合C ++ 03标准和C ++ 11标准,但根据编译的标准做不同的事情?

答案是肯定的。 好的一面是:

  • 以前隐式复制对象的代码现在将在可能时隐式移动它们。

不利的一面是,标准附录C列举了几个例子。 即使有更多的积极消极的东西,他们每个人都不太可能发生。

string文字

 #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 

types转换为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 

嵌套的模板closures大括号之间的空格>> vs>>

在专业化或实例化的内部, >>可能会被解释为C ++ 03中的右移。 尽pipe如此,这更有可能破坏现有的代码:(从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); } 

现在operator new可能会抛出比std::bad_alloc更多的exception

 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 } 

用户声明的析构函数有一个隐含的exception说明示例从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::exception派生的代码可能会有不同的performance。

我向你指出这篇文章和后续 ,它有一个很好的例子,如何可以改变从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 ,这是一个expression式。

在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 

恭喜,同样的expression方式有两个不同的结果。 当然,C ++ 03在我testing的时候提出了一个警告表单Clang。

是的,有很多变化会导致相同的代码在C ++ 03和C ++ 11之间导致不同的行为。 sorting规则的差异使得一些有趣的变化,包括一些以前未定义的行为变得明确。

1.初始化列表中的相同variables的多个突变

一个非常有趣的angular落案例会在初始化程序列表中出现多个相同variables的突变,例如:

 int main() { int count = 0 ; int arrInt[2] = { count++, count++ } ; return 0 ; } 

在C ++ 03和C ++ 11中,这个定义很好,但是在C ++ 03中的评估顺序是不确定的,但是在C ++ 11中,它们按它们出现的顺序进行评估 。 所以,如果我们在C ++ 03模式下使用clang进行编译,它会提供以下警告( 请参阅实况 ):

 warning: multiple unsequenced modifications to 'count' [-Wunsequenced] int arrInt[2] = { count++, count++ } ; ^ ~~ 

但在C ++ 11中没有提供警告( 请参阅实况 )。

2.新的sorting规则使得i = ++ i + 1; 在C ++ 11中有很好的定义

C ++ 03之后采用的新sorting规则意味着:

 int i = 0 ; i = ++ i + 1; 

在C ++ 11中不再是未定义的行为,这在缺陷报告637中进行了介绍。sorting规则和示例不一致

3.新的sorting规则也使++++我; 在C ++ 11中有很好的定义

C ++ 03之后采用的新sorting规则意味着:

 int i = 0 ; ++++i ; 

在C ++ 11中不再是未定义的行为。

4.稍微更明智的签名左转

后来的C ++ 11草案包括我在下面链接的N3485 , 修正了将1位移入或移过符号位的不确定行为 。 这也在缺陷报告1457中进行了介绍 。 Howard Hinnant评论了在C ++ 11中左移(<<)负整数未定义行为的线程中这种变化的意义。 。

5.在C ++ 11中,constexpr函数可以被视为编译时常量expression式

C ++ 11引入了constexpr函数,其中:

constexpr说明符声明可以在编译时评估函数或variables的值。 这样的variables和函数可以用在只允许编译时间常量expression式的地方。

而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是一个可变长度的数组,这是一个扩展,并会产生以下警告:

 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是一个编译时常量expression式,不需要VLA扩展。

6.在C ++ 11中,除非你的析构函数隐式生成了exception规范

由于在C ++ 11中,用户定义的析构函数具有隐含的noexcept(true)规范,正如noexcept析构函数中所解释的,这意味着下列程序:

 #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中,模板参数不能有内部链接

为什么std :: sort不接受函数中声明的比较类 。 所以下面的代码不应该在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标志,这实在是太麻烦了。

8.closures多个模板时,>>不再不合格

使用>>closures多个模板不再是格式不正确的,但是可能导致C ++ 03和C + 11中的代码具有不同的结果。 下面的例子取自右括号和向后兼容性 :

 #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构造函数

从这个答案稍微修改的代码显示,使用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中都允许这样做,尽pipe它在C ++ 11中默认提供了一个警告:

 int x[] = { 2.0 }; 

这包括在C ++ 11标准草案部分8.5.4 列表初始化段落3

Ttypes的对象或引用的列表初始化定义如下:

并包含以下项目符号( 重点介绍 ):

否则,如果T是类types,则考虑构造函数。 列举适用的构造函数,并通过重载parsing(13.3,13.3.1.7)select最好的构造函数。 如果需要缩小转换(见下文)来转换任何参数,则该程序是不合格的

C ++标准部分annex C.2 C ++和ISO C ++ 2003中涵盖了这个以及更多的实例。 它还包括:

  • 新types的string[…]具体来说,名为R,u8,u8R,u,uR,U,UR或LR的macros在与string相邻时不会被展开,但会被解释为string的一部分。 例如

     #define u8 "abc" const char *s = u8"def"; // Previously "abcdef", now "def" 
  • 用户定义的文字string支持[…]以前,#1将由两个单独的预处理标记组成,macros_x将被扩展。 在这个国际标准中,#1由单个预处理标记组成,所以macros不扩展。

     #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 :: 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 Live示例

C ++ 11 Live示例

原因在于C ++ 03为“指定大小和原型元素”和“仅指定大小”指定了一个重载,像这样(为简洁起见省略了分配器参数):

 container(size_type size, const value_type &prototype = value_type()); 

这将始终将prototype复制到容器size时间。 当仅用一个参数调用时,它将创build一个默认构造的元素的size副本。

在C ++ 11中,这个构造函数签名被删除,并被这两个重载代替:

 container(size_type size); container(size_type size, const value_type &prototype); 

第二个像以前一样工作,创buildprototype元素的size副本。 但是,第一个(现在只处理调用大小参数的调用)default-分别构造每个元素。

我猜这个改变的原因是C ++ 03重载不能用于只移动元素types。 但是这是一个突破性的变化,而且很less有logging。

std::istream读取失败的结果已经改变。 CppReference很好地总结了这一点:

如果提取失败(例如,如果input的字母是数字所在的位置), value将保持failbit并设置失败位。 (直到C ++ 11)

如果提取失败,写入零value并设置失败failbit 。 如果提取的结果值太大或太小,不符合valuestd::numeric_limits<T>::max()std::numeric_limits<T>::min()被写入, failbit标志被设置。 (自C ++ 11以来)

如果你习惯了新的语义,然后必须使用C ++ 03编写,这主要是一个问题。 以下是不是特别好的做法,但在C ++ 11中定义明确:

 int x, y; std::cin >> x >> y; std::cout << x + y; 

但是,在C ++ 03中,上面的代码使用未初始化的variables,因此具有未定义的行为。

这个线程在运行时可以检测到C ++ 03和C ++ 0x之间有什么区别,例如通过利用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); } 

和允许本地types作为模板参数的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 

在Coliru上查看结果