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

Eri*_*und 294 c++ language-lawyer c++11 c++03

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

exa*_*ple 279

答案肯定是肯定的.从好的方面来说,有:

  • 以前隐式复制对象的代码现在将隐式移动它们.

从消极方面来看,标准的附录C中列出了几个例子.即使有更多的负面因素而不是正面,每一个都不太可能发生.

字符串文字

#define u8 "abc"
const char* s = u8"def"; // Previously "abcdef", now "def"
Run Code Online (Sandbox Code Playgroud)

#define _x "there"
"hello "_x // Previously "hello there", now a user defined string literal
Run Code Online (Sandbox Code Playgroud)

输入0的转化次数

在C++ 11中,只有文字是整数空指针常量:

void f(void *); // #1
void f(...); // #2
template<int N> void g() {
    f(0*N); // Calls #2; used to call #1
}
Run Code Online (Sandbox Code Playgroud)

整数除法和模数后的舍入结果

在C++ 03中,允许编译器向0或向负无穷大舍入.在C++ 11中,必须向0舍入

int i = (-1) / 2; // Might have been -1 in C++03, is now ensured to be 0
Run Code Online (Sandbox Code Playgroud)

嵌套模板关闭括号>> 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);
}
Run Code Online (Sandbox Code Playgroud)

运营商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
}
Run Code Online (Sandbox Code Playgroud)

用户声明的析构函数有一个隐式异常规范 示例来自C++ 11中引入了哪些重大更改?

struct A {
    ~A() { throw "foo"; } // Calls std::terminate in C++11
};
//...
try { 
    A a; 
} catch(...) { 
    // C++03 will catch the exception
} 
Run Code Online (Sandbox Code Playgroud)

size() 容器现在需要在O(1)中运行

std::list<double> list;
// ...
size_t s = list.size(); // Might be an O(n) operation in C++03
Run Code Online (Sandbox Code Playgroud)

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";
}
Run Code Online (Sandbox Code Playgroud)

  • 不错,+1.另一个是用户现在声明析构函数是隐式的'noexecpt(true)`所以析构函数中的`throw`现在会调用`std :: terminate`.但我希望任何编写此类代码的人都会对此感到高兴! (11认同)
  • 但是std :: system_error本身是(间接)从std :: exception派生的,所以`catch(std :: exception&)`仍然捕获`std :: ios_base :: failure`. (4认同)
  • 我非常困惑,因为你所说的'operator new`是准确的(它现在可以抛出`std :: bad_array_new_length`),但你的例子根本没有表明.您在C++ 03和C++ 11 AFAIK中显示的代码是相同的. (4认同)
  • list :: size为O(1)的另一面是splice现在为O(n) (2认同)

chr*_*ris 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  
}
Run Code Online (Sandbox Code Playgroud)

关键部分是行main,它是一个表达式.

在C++ 03中:

1 >> ::three = 0
=> fon< fun< 0 >::two >::one;

fun< 0 >::two = int
=> fon< int >::one

fon< int >::one = true
=> true
Run Code Online (Sandbox Code Playgroud)

在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
Run Code Online (Sandbox Code Playgroud)

恭喜,同一表达的两个不同结果.当然,在我测试它时,C++ 03确实提出了警告形式Clang.

  • 很好的一个,让它归结为评估为'true`或`false`的不同标准.也许我们可以将它用作功能测试</ joke> (3认同)

Sha*_*our 37

是的,有许多更改会导致相同的代码导致C++ 03和C++ 11之间的不同行为.排序规则的差异带来了一些有趣的变化,包括一些先前未定义的行为变得很明确.

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

一个非常有趣的角点案例是初始化列表中同一变量的多个突变,例如:

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

    return 0 ;
}
Run Code Online (Sandbox Code Playgroud)

在C++ 03和C++ 11中,这是明确定义的,但C++ 03中的评估顺序未指定,但在C++ 11中,它们按照它们出现的顺序进行评估.因此,如果我们clang在C++ 03模式下进行编译,它会提供以下警告(请参见实时):

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

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

                           ^        ~~
Run Code Online (Sandbox Code Playgroud)

但是在C++ 11中没有提供警告(现场直播).

2.新的排序规则使i = ++ i + 1; 在C++ 11中很好地定义

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

int i = 0 ;
i = ++ i + 1;
Run Code Online (Sandbox Code Playgroud)

在C++ 11中不再是未定义的行为,这在缺陷报告637中有所涉及.排序规则和示例不一致

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

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

int i = 0 ;
++++i ;
Run Code Online (Sandbox Code Playgroud)

在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] ;
}
Run Code Online (Sandbox Code Playgroud)

使用clang在C++ 03,这将导致x为可变长度数组,这是一个扩展并会产生下列警告:

warning: variable length arrays are a C99 feature [-Wvla-extension]
    int x[std::numeric_limits<unsigned int>::min()+2] ;
         ^
Run Code Online (Sandbox Code Playgroud)

而在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";
}
Run Code Online (Sandbox Code Playgroud)

在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;
    }
}
Run Code Online (Sandbox Code Playgroud)

但目前clang允许在C++ 03模式下使用此代码并发出警告,除非您使用-pedantic-errors标记,这有点icky,看到它是实时的.

关闭多个模板时,>> >>不再格式错误

使用>>关闭多个模板不再是病态的,但会导致代码在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';
}
Run Code Online (Sandbox Code Playgroud)

并且C++ 03中的结果是:

0
3
Run Code Online (Sandbox Code Playgroud)

在C++ 11中:

0
0
Run Code Online (Sandbox Code Playgroud)

9. C++ 11更改了一些std :: vector构造函数

来自此答案的稍微修改过的代码显示使用std :: vector中的以下构造函数:

std::vector<T> test(1);
Run Code Online (Sandbox Code Playgroud)

在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 ;
}
Run Code Online (Sandbox Code Playgroud)

10.缩小聚合初始值设定项中的转换

在C++ 11中,聚合初始化器中的缩小转换是不正确的,看起来gcc在C++ 11和C++ 03中都允许这样,尽管它在C++ 11中默认提供警告:

int x[] = { 2.0 };
Run Code Online (Sandbox Code Playgroud)

这在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"
    
    Run Code Online (Sandbox Code Playgroud)
  • 用户定义的文字字符串支持[...]以前,#1将由两个单独的预处理标记组成,宏_x将被扩展.在本国际标准中,#1由单个预处理令牌组成,因此宏不会扩展.

    #define _x "there"
    "hello"_x // #1
    
    Run Code Online (Sandbox Code Playgroud)
  • 为整数/和%[...] 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代码可能在本国际标准中执行不同.


Rei*_*ica 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';
}
Run Code Online (Sandbox Code Playgroud)

C++ 03实例

C++ 11 Live示例

原因是C++ 03为"指定大小和原型元素"和"仅指定大小"指定了一个重载,就像这样(为简洁省略了allocator参数):

container(size_type size, const value_type &prototype = value_type());
Run Code Online (Sandbox Code Playgroud)

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

在C++ 11中,删除了此构造函数签名并替换为这两个重载:

container(size_type size);

container(size_type size, const value_type &prototype);
Run Code Online (Sandbox Code Playgroud)

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

我猜测这种变化的原因是C++ 03重载不适用于只移动元素类型.但它仍然是一个突破性的变化,而且很少有人记录在案.

  • 虽然这显然是一个突破性的变化,但我更喜欢C++ 11的行为.我希望这会导致一个`deque`拥有十个单独的小部件,而不是十个小部件共享相同的资源. (3认同)

Ant*_*lov 19

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

如果提取失败(例如,如果输入了预期数字的字母),value则保持未修改并failbit设置.(直到C++ 11)

如果提取失败,则写入零valuefailbit设置为零.如果提取导致值太大或太小而无法放入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;
Run Code Online (Sandbox Code Playgroud)

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

  • 您可以补充一点,在C++ 03中,可以使用此*标准化行为*来提供默认值,如`int x = 1,y = 1; cin >> x >> y; cout << x*y;`.使用C++ 03,当没有'y`可以被读取时,这将正确地产生`x`. (4认同)

uwe*_*sky 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); 
}
Run Code Online (Sandbox Code Playgroud)

和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);
}
Run Code Online (Sandbox Code Playgroud)


Sta*_*ked 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;
}
Run Code Online (Sandbox Code Playgroud)

打印:

Using c++03: no
Using c++11: yes
Run Code Online (Sandbox Code Playgroud)

查看Coliru的结果


归档时间:

查看次数:

16988 次

最近记录:

6 年,11 月 前