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


299

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


26
Я уверен, что это autoможет привести к такой ситуации
— OMGtechy

8
Да. Одним из примеров является >>использование в шаблоне. Вы можете столкнуться с ситуацией, когда он может компилироваться для обоих стандартов. Еще один, который, я уверен, будет легко найти изменения в инициализации.
— Крис

5
Вот хорошая статья о ситуации >>: gustedt.wordpress.com/2013/12/15/…
— Крис

6
@OMGtechy: я не думаю, что auto может вызвать это. Со старым значением для autoобъявления требуется имя типа; с новым значением имя типа не допускается.
— Кит Томпсон

2
Как это открыто? Вы сами указали через другой вопрос, что ответ на этот вопрос «да, вот пример того, как». На этот вопрос, как вы сами указали, есть очень определенный ответ.
— Джалф

Ответы:


284

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

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

С отрицательной стороны, несколько примеров перечислены в приложении С стандарта. Хотя негативных и позитивных гораздо больше, вероятность возникновения каждого из них значительно ниже.

Строковые литералы

#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

4
Но сам std :: system_error (косвенно) является производным от std :: exception, так что catch (std::exception &)все равно ловит std::ios_base::failure.
— user2665887

@ user2665887 ты прав. это все еще может влиять на поведение программы, но я не могу думать о минимальном примере прямо сейчас.
— пример

4
Я очень запутался, потому что то, о чем вы говорите, operator newявляется точным (теперь оно может std::bad_array_new_lengthпоказывать), но ваш пример совсем не показывает этого. Код, который вы показываете, одинаков в AFAIK C ++ 03 и C ++ 11.
— Утка

2
Обратная сторона списка :: size, равная O (1), состоит в том, что теперь сращивание O (n)
— Тони Делрой

56

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

В С ++ 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для ::twoC ++ 03 версии
— zahir

3
Хорошо, когда все сводится к оценке trueили falseдля различных стандартов. Может быть, мы могли бы использовать его в качестве функционального теста </ joke>
— cmaster - восстановить монику

@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 . Говард Хиннант прокомментировал значение этого изменения в потоке. Является ли сдвиг влево (<<) отрицательным целочисленным неопределенным поведением в 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 спецификации исключений неявно генерируются для ваших деструкторов

Поскольку в 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 List-initialization, параграф 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 :: fail [...] std :: ios_base :: fail больше не выводится напрямую из std :: exception, а теперь выводится из std :: system_error, который, в свою очередь, выводится из станд :: runtime_error. Допустимый код C ++ 2003, который предполагает, что std :: ios_base :: fail является производным непосредственно от std :: exception, может выполняться по-другому в этом международном стандарте.


Таким образом, большинство примеров сужаются до того факта, что ранее неопределенное поведение теперь хорошо определено?
— MatthiasB

@MatthiasB 2, 3 и 4 об этом, поэтому на данный момент они больше не являются примерами. Я сомневаюсь, что найду еще много неопределенных примеров поведения, так что, если я добавлю больше, они станут меньше.
— Шафик Ягмур

Что ж, поведение # 1 не определено, поэтому я бы посчитал его неопределенным поведением (по крайней мере, вы не можете ожидать, что получите конкретный результат с помощью c ++ 03, теперь вы можете использовать c ++ 11), # 5 использует не стандартное расширение с ++. Но я думаю, ты прав. Чем больше вы ищете, тем больше примеров вы найдете, которые определены в обоих стандартах, но дают разные результаты.
— MatthiasB

@MatthiasB да, и неопределенное, и неопределенное поведение имеют нежелательные результаты. Что касается расширений, рассматривающих Linux, зависит от ряда расширений gcc, мы должны предположить, что в реальном мире они имеют значение. Я не ожидал найти так много примеров, когда я впервые ответил на этот вопрос.
— Шафик Ягмур

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 Живой пример

Причина в том, что 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элемента. Однако первый (который теперь обрабатывает вызовы только с указанным аргументом размера) по умолчанию создает каждый элемент отдельно.

Я полагаю, что причина этого изменения в том, что перегрузка C ++ 03 не может быть использована с типом элемента только для перемещения. Но, тем не менее, это серьезное изменение, которое редко документируется.


3
Хотя это явно серьезное изменение, я предпочитаю поведение C ++ 11. Я ожидаю, что в результате будет dequeполучено десять отдельных виджетов, а не десять виджетов, использующих один и тот же ресурс.
— Agentlien

19

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

Если извлечение не удается (например, если буква была введена там, где ожидается цифра), valueона остается неизменной и failbitустанавливается. (до C ++ 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 приведенный выше код использует неинициализированную переменную и, следовательно, имеет неопределенное поведение.


4
Вы можете добавить, что в C ++ 03 можно было использовать это стандартизированное поведение для предоставления значения по умолчанию, как в int x = 1, y = 1; cin >> x >> y; cout << x*y;. С C ++ 03 это будет правильно производить, xкогда не yможет быть прочитано.
— cmaster - восстановить монику

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.