Может ли код c++ быть действительным как в C++03, так и в C++11, но делать разные вещи?

возможно ли, чтобы код c++ соответствовал обоим C++03 стандартные и C++11 стандарт, но делают разные вещи в зависимости от того, под каким стандартом он компилируется?

7 ответов


ответ-Определенно да. С положительной стороны есть:

  • код, который ранее неявно копировал объекты, теперь будет неявно перемещать их, когда это возможно.

с отрицательной стороны, несколько примеров, перечисленных в приложении 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";
}

Я вам!--6-->в этой статье и в последующем, который имеет хороший пример того, как >> может изменить значение С 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 один придумал предупреждающая форма звякнула, когда я проверил ее.


Да, есть ряд изменений, которые приведут к тому, что один и тот же код приведет к различному поведению между 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. Говард Хиннант прокомментировал значение этого изменения в нить на является ли смещение влево ( .

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 не принимает классы сравнения, объявленные в функции. Поэтому следующий код не должен работать в 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:: 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 список-инициализации абзац 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
    
  • укажите округление для результатов integer / и % [...] Код 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. Допустимый код C++ 2003, который предполагает, что std::ios_base::failure является производным непосредственно от std:: exception может выполняться по-разному в этом международном стандарте.


одно потенциально опасное обратно несовместимое изменение находится в конструкторах контейнеров последовательности, таких как 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 не будет использоваться с типом элемента только для перемещения. Но тем не менее это разительное изменение, к тому же редко задокументированное.


результат неудачного чтения из std::istream изменилось. CppReference обобщает это красиво:

если извлечение не удается (например, если была введена буква, где ожидается цифра),value остается неизмененным и failbit - это набор. (до С++11)

если извлечение не удается, ноль записывается в 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);
}

вот еще один пример:

#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