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


当前回答

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

c++ 11实例

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

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

这将始终复制原型到容器大小的次数。当只使用一个参数调用时,它将创建默认构造元素的大小副本。

在c++ 11中,这个构造函数签名被删除并替换为以下两个重载:

container(size_type size);

container(size_type size, const value_type &prototype);

第二个方法和前面一样,创建原型元素的大小副本。但是,第一个(现在只处理指定size参数的调用)默认单独构造每个元素。

我猜测这一更改的原因是c++ 03重载不能用于仅移动的元素类型。但这毕竟是一个突破性的变化,而且很少有文献记载。

其他回答

从std::istream读取失败的结果已经改变。CppReference总结得很好:

如果提取失败(例如,如果在需要数字的地方输入了字母),则value保持不变,并设置failbit。(直到c++ 11) 如果提取失败,则将0写入值并设置failbit。如果提取结果值太大或太小,不适合值,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之间改变含义,同时仍然在这两种语言中编译。

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"

and

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

Operator 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实例

c++ 11实例

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

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

这将始终复制原型到容器大小的次数。当只使用一个参数调用时,它将创建默认构造元素的大小副本。

在c++ 11中,这个构造函数签名被删除并替换为以下两个重载:

container(size_type size);

container(size_type size, const value_type &prototype);

第二个方法和前面一样,创建原型元素的大小副本。但是,第一个(现在只处理指定size参数的调用)默认单独构造每个元素。

我猜测这一更改的原因是c++ 03重载不能用于仅移动的元素类型。但这毕竟是一个突破性的变化,而且很少有文献记载。