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

Angew больше не гордится SO
источник
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);
}
uwedolinsky
источник
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

StackedCrooked
источник