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


298

C ++代码是否可能既符合C ++ 03标准又符合C ++ 11标准,但是根据正在编译的标准,它会做不同的事情吗?


26
我很确定auto会导致这种情况
OMGtechy 2014年

8
是。一个示例是>>在模板中使用时。您可能会想到可以同时为两个标准进行编译的情况。我敢肯定,另一个容易找到更改的地方是初始化。
克里斯,2014年

5
这是一篇关于>>情况的好文章:gustedt.wordpress.com/2013/12/15/…–
chris

6
@OMGtechy:我不认为 auto可能会导致此。按照旧的含义,auto声明需要类型名称;具有新含义的类型名称是不允许的。
基思·汤普森

2
开放式如何?您自己通过另一个问题指出,该问题的答案是“是的,这是一个示例”。正如您自己指出的那样,对这个问题有非常明确的答案。
2014年

Answers:


283

答案是肯定的。从正面看,有:

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

不利的一面是,在标准附录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
}

用户声明的析构函数有一个隐式的异常说明 示例,它来自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";
}

11
不错,+ 1。另一个问题是,用户声明的析构函数现在是隐式的,noexecpt(true)因此throw在析构函数中现在将调用std::terminate。但是,我希望任何编写这种代码的人都会对此感到高兴!
typ1232 2014年

4
但是std :: system_error本身(间接)是从std :: exception派生的,因此catch (std::exception &)仍然catch std::ios_base::failure
user2665887'4

@ user2665887你是对的。它仍然可以影响程序的行为,但是我现在想不出一个最小的例子。
例如

4
我非常困惑,因为您所说的operator new是准确的(现在可以抛出std::bad_array_new_length),但是您的示例根本没有显示出来。您显示的代码在C ++ 03和C ++ 11 AFAIK中相同。
Mooing Duck

2
list :: size的反面是O(1),表示拼接现在是O(n)
Tony Delroy 2014年

55

我为您提供了这篇文章后续文章,其中有一个很好的示例,说明了如何>>在将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。


这是奇怪的,它不要求typename::two在C ++ 03版
查希尔

3
不错的一种,它可以归结为评估不同的标准truefalse针对不同的标准。也许我们可以将其用作功能测试</ joke>
cmaster-恢复monica 2014年

@zahir,这不是类型,只是一个值。
克里斯

好吧,适当的cmdline选项会对此(warning: comparisons like ‘X<=Y<=Z’ do not have their mathematical meaning [-Wparentheses])发出警告,但是仍然是歧义::运算符如何更改含义的好示例(引用全局范围或取消引用紧靠其前的范围)
示例

@example,令人惊讶的是,GCC发出了警告,但是Clang没有给出警告。
克里斯

39

是的,有许多更改将导致相同的代码导致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中,它们的显示顺序是按它们出现的顺序求值的。因此,如果我们clang在C ++ 03模式下进行编译,则会提供以下警告(实时查看):

warning: multiple unsequenced modifications to 'count' [-Wunsequenced]

    int arrInt[2] = { count++, count++ } ;

                           ^        ~~

但在C ++ 11中未提供警告(请实时查看)。

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

clang在C ++ 03中使用,这将导致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不接受在函数中声明的Compare类,对此进行了很好的介绍。因此,以下代码在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除非允许您使用-pedantic-errors标志(有点怪异)才能在C ++ 03模式下允许此代码带有警告,否则请实时查看

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 :: 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 ++ 11和C ++ 03中似乎都允许这样做:

int x[] = { 2.0 };

这在C ++ 11标准草案的8.5.4 列表初始化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"
  • 用户定义的文字字符串支持以前,#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代码在本国际标准中的执行方式可能有所不同。


因此,大多数示例都缩小了一个事实,即以前未定义的行为现在已经定义好了吗?
MatthiasB 2014年

@MatthiasB 2、3和4都是关于这个的,因此在这一点上,它们不再是大多数示例了。我怀疑我会发现更多未定义的行为示例,所以当我添加更多示例时,它们将成为更小的集合。
Shafik Yaghmour 2014年

好吧,#1行为是未指定的,因此我将其视为未定义的行为(至少您不能期望使用c ++ 03获得特定的结果,现在可以使用c ++ 11了),#5使用非- C ++的标准扩展。但是我想你是对的。您寻找的越多,就会发现在两个标准中都定义但产生不同结果的示例。
MatthiasB 2014年

@MatthiasB是的,未指定和未定义的行为都会产生不良结果。至于考虑Linux 的扩展,我们应该在现实世界中假设它们取决于许多gcc扩展。当我第一次回答这个问题时,我并不期望找到这么多例子。
Shafik Yaghmour 2014年

35

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

原因是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);

第二个和以前一样工作,创建元素的size副本prototype。但是,第一个(现在仅使用指定的size参数处理调用)默认构造每个元素。

由于这种更改,我的猜测是C ++ 03重载不适用于仅移动元素类型。但这仍然是一个重大变化,很少有人对此进行记录。


3
尽管这显然是一个重大变化,但我更喜欢C ++ 11的行为。我希望这会导致deque持有十个单独的小部件,而不是十个共享相同资源的小部件。
Agentlien 2014年

19

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

如果提取失败(例如,如果在预期的数字中输入了字母),则将其value保留不变并failbit进行设置。(直到C ++ 11)

如果提取失败,则将零写入valuefailbit设置。如果提取导致该值太大或太小而无法容纳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中,以上代码使用了未初始化的变量,因此具有未定义的行为。


4
您可能会补充说,在C ++ 03中,可能已经使用了这种标准化行为来提供默认值,如中所述int x = 1, y = 1; cin >> x >> y; cout << x*y;。使用C ++ 03时,x如果y无法读取,则可以正确产生。
cmaster-恢复莫妮卡2014年

15

该线程可以在运行时检测到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);
}

7

这是另一个例子:

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.