【问题标题】:Can C++ code be valid in both C++03 and C++11 but do different things?C++ 代码可以在 C++03 和 C++11 中都有效但做不同的事情吗?
【发布时间】:2014-05-27 15:38:38
【问题描述】:

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

【问题讨论】:

  • 我很确定auto 可能会导致这样的情况
  • 是的。一个例子是 >> 在模板中使用时。您可以想出一种情况,它可以针对这两种标准进行编译。我确信很容易找到更改的另一个是初始化。
  • 这是一篇关于>>情况的好文章:gustedt.wordpress.com/2013/12/15/…
  • @OMGtechy:我不认为 auto 会导致这种情况。按照旧的意思,auto 声明需要一个类型名称;有了新的含义,类型名称是不允许的。
  • 它是如何开放式的?您自己通过另一个问题指出,这个问题的答案是“是的,这是一个如何做的例子”。正如您自己指出的那样,这个问题有一个非常明确的答案。

标签: c++ c++11 language-lawyer c++03


【解决方案1】:

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

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

在消极方面,标准的附录 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 > >

在特化或实例化中,&gt;&gt; 可能被解释为 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
}

用户声明的析构函数具有隐式异常规范 来自What breaking changes are introduced in 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";
}

【讨论】:

  • 很好,+1。另一个是用户声明的析构函数现在隐式为noexecpt(true),因此析构函数中的throw 现在将调用std::terminate。但我希望任何编写此类代码的人都会对此感到高兴!
  • 但是 std::system_error 本身是(间接)从 std::exception 派生的,所以 catch (std::exception &amp;) 仍然捕获 std::ios_base::failure
  • @user2665887 你是对的。它仍然可以影响程序的行为,但我现在想不出一个最小的例子。
  • 我非常困惑,因为您所说的 operator new 是准确的(它现在可以抛出 std::bad_array_new_length),但您的示例根本没有显示这一点。您显示的代码在 C++03 和 C++11 AFAIK 中是相同的。
  • list::size 为 O(1) 的另一面是 splice 现在为 O(n)
【解决方案2】:

我指给你看this articlethe follow-up,这是一个很好的例子,说明&gt;&gt; 如何将含义从 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++03 版本中的::two 不需要typename
  • 很好,归结为不同标准的truefalse。也许我们可以将其用作功能测试
  • @zahir,这不是一个类型,只是一个值。
  • 好吧,正确的 cmdline 选项会警告这一点 (warning: comparisons like ‘X&lt;=Y&lt;=Z’ do not have their mathematical meaning [-Wparentheses]),但仍然是模棱两可的 :: 运算符如何改变含义的一个很好的例子(引用全局范围或取消引用直接站立的那个)在它之前)
  • @example,令人惊讶的是,GCC 给出了警告,但 Clang 没有。
【解决方案3】:

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

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

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

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

    return 0 ;
}

在 C++03 和 C++11 中,这是很好的定义,但 order of evaluation in C++03 is unspecified 但在 C++11 they are evaluated in the order in which they appear 中。因此,如果我们在 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 中不再是未定义的行为,这在 defect report 637. Sequencing rules and example disagree 中有介绍

3.新的排序规则也使得 ++++i ;在 C++11 中定义良好

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

int i = 0 ;
++++i ;

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

4.稍微更明智的有符号左移

后来的 C++11 草案包括 N3485,我在下面链接 fixed the undefined behavior of shifting a 1 bit into or past the sign bit。这也包含在defect report 1457 中。 Howard Hinnant 在Is left-shifting (<<) a negative integer undefined behavior in 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] ;
}

在 C++03 中使用 clang 这将导致 x 成为可变长度数组,即 an extension 并将生成以下警告:

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

而在 C++11 中,std::numeric_limits&lt;unsigned int&gt;::min()+2 是编译时常量表达式,不需要 VLA 扩展。

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

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

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

Why std::sort doesn't accept Compare classes declared within a function 很好地介绍了这一点。所以下面的代码不应该在 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 标志,这有点恶心,see it live

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

使用&gt;&gt; 关闭多个模板不再是错误的格式,但会导致代码在 C++03 和 C+11 中产生不同的结果。下面的例子取自Right angle brackets and backwards compatibility

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

来自this answer 的稍微修改的代码表明使用来自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 中都允许这样做,尽管它在 C++11 中默认提供警告:

int x[] = { 2.0 };

这在草案 C++11 标准部分 8.5.4 List-initialization 段落 3 中有介绍:

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

并包含以下项目符号(强调我的):

否则,如果 T 是类类型,则考虑构造函数。枚举适用的构造函数,并通过重载决议(13.3、13.3.1.7)选择最佳构造函数。 如果需要缩小转换(见下文)来转换任何参数,则程序格式错误

draft C++ standard 部分 annex C.2C++ 和 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 2、3 和 4 与此有关,因此此时它们不再是大多数示例。我怀疑我会发现更多未定义的行为示例,因此当我添加更多时,它们将成为一个更小的集合。
  • 好吧,#1 行为未指定,所以我将其视为未定义行为(至少您不能期望使用 c++03 获得特定结果,现在使用 c++11 您可以),#5 使用 c++ 的非标准扩展。但我想你是对的。您寻找的越多,您就会发现越多的示例在两个标准中都有定义,但会产生不同的结果。
  • @MatthiasB 是的,未指定和未定义的行为都会产生不良结果。至于考虑 Linux depends on a number of gcc extensions 的扩展,我们应该假设在现实世界中它们很重要。当我第一次回答这个问题时,我没想到会找到这么多例子。
【解决方案4】:

一个潜在危险的向后不兼容更改是在序列容器的构造函数中,例如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++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 重载不能用于只移动元素类型。但这仍然是一个突破性的变化,而且很少记录在案。

【讨论】:

  • 虽然这显然是一个重大变化,但我更喜欢 C++11 的行为。我希望这会导致deque 持有十个单独的小部件,而不是十个共享相同资源的小部件。
【解决方案5】:

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

如果提取失败(例如,如果在需要数字的地方输入了字母),value 保持不变,failbit 被设置。 (C++11 前)

如果提取失败,则将零写入value 并设置failbit。如果提取导致值太大或太小而无法放入value,则写入std::numeric_limits&lt;T&gt;::max()std::numeric_limits&lt;T&gt;::min() 并设置failbit 标志。 (C++11 起)

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

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

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

【讨论】:

  • 您可能会补充说,在 C++03 中可以使用这种标准化行为来提供默认值,如int x = 1, y = 1; cin &gt;&gt; x &gt;&gt; y; cout &lt;&lt; x*y;。使用 C++03,当无法读取 y 时,这将正确生成 x
【解决方案6】:

此线程What differences, if any, between C++03 and C++0x can be detected at run-time 有示例(从该线程复制)以确定语言差异,例如通过利用 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
    

    See the result on Coliru

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-10-17
      • 2012-03-06
      • 1970-01-01
      • 2014-02-13
      • 2013-01-23
      • 2015-05-20
      相关资源
      最近更新 更多