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

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

18968 次浏览

我向你指出这篇文章后续,它们有一个很好的例子,说明>>如何在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。

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

  • 以前隐式复制对象的代码现在将在可能的情况下隐式移动对象。

在负面方面,标准的附录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中的右移。这更有可能破坏现有的代码:(from 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
}

用户声明的析构函数具有隐式异常规范 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";
}

一个潜在的危险的向后不兼容更改是在序列容器的构造函数中,例如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 example . c++ 03 Live example

c++ 11 Live example .

原因是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重载不能用于仅移动的元素类型。但这毕竟是一个突破性的变化,而且很少有文献记载。

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

如果提取失败(例如,如果在需要数字的地方输入了一个字母),value将保持不变,并设置failbit(直到c++ 11)

如果提取失败,则将0写入value并设置failbit。如果提取导致值太大或太小,不适合value,则写入std::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中,上述代码使用了一个未初始化的变量,因此具有未定义的行为。

这个线程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);
}

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);
}

是的,在c++ 03和c++ 11之间,有许多更改会导致相同的代码产生不同的行为。排序规则的差异导致了一些有趣的变化,包括一些以前未定义的行为变得定义良好。

1. 初始化列表中相同变量的多次突变

一个非常有趣的极端情况是同一个变量在初始化列表中的多个突变,例如:

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


return 0 ;
}

在c++ 03和c++ 11中,这都是很好的定义,但在c++ 03中求值的顺序是未指定的c++ 11按照它们出现的顺序求值中。因此,如果我们在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中不再是未定义的行为,这是在缺陷报告637。排序规则和例子并不一致

3.新的测序规则也使得++++i;在c++ 11中很好地定义

c++ 03之后采用的新的排序规则意味着:

int i = 0 ;
++++i ;

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

4. 稍微更合理的符号左移

c++ 11的后期草案包括N3485,我将其链接在修正了将1位移进或移过符号位的未定义行为下面。这在缺陷报告1457中也有涉及。Howard Hinnant在左移(<<)在c++ 11中是一个未定义的负整数行为吗?上评论了线程中这个变化的重要性。

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

c++ 11引入了constexpr函数:

constexpr说明符声明可以在编译时计算函数或变量的值。这样的变量和函数可以在只允许编译时常数表达式的地方使用。

虽然c++ 03没有constexpr特性,但我们不必显式地使用constexpr关键字,因为标准库在c++ 11中作为constexpr提供了许多函数。例如std:: numeric_limits:分钟。这会导致不同的行为,例如:

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

6. 在c++ 11中,noexcept异常规范是为析构函数隐式生成的

由于在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. 当关闭多个模板时,>>不再是病态的

使用>>关闭多个模板不再是格式错误的,但在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::向量构造函数:

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 ;
}

< B > 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)。如果需要窄化转换(见下文)来转换任何参数,则程序是格式错误的

这个和更多的实例将在c++标准草案部分annex C.2 c++和ISO c++ 2003中介绍。它还包括:

  • 新的字符串字面量[…]具体来说,名为R、u8、u8R、u、uR、u、uR或LR的宏在与字符串字面值相邻时不会展开,而是会被解释为字符串字面值的一部分。例如

    #define u8 "abc"
    const char *s = u8"def"; // Previously "abcdef", now "def"
    
  • User-defined literal string support [...]Previously, #1 would have consisted of two separate preprocessing tokens and the macro _x would have been expanded. In this International Standard, #1 consists of a single preprocessing tokens, so the macro is not expanded.

    #define _x "there"
    "hello"_x // #1
    
  • Specify rounding for results of integer / and % [...] 2003 code that uses integer division rounds the result toward 0 or toward negative infinity, whereas this International Standard always rounds the result toward 0.

  • Complexity of size() member functions now constant [...] Some container implementations that conform to C++ 2003 may not conform to the specified size() requirements in this International Standard. Adjusting containers such as std::list to the stricter requirements may require incompatible changes.

  • Change base class of std::ios_base::failure [...] std::ios_base::failure is no longer derived directly from std::exception, but is now derived from std::system_error, which in turn is derived from std::runtime_error. Valid C++ 2003 code that assumes that std::ios_base::failure is derived directly from std::exception may execute differently in this International Standard.

下面是另一个例子:

#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上查看结果 .