Возможно ли, что код С++ соответствует как стандарту C++03, так и C++11 стандарт, но делать разные вещи в зависимости от того, какой стандарт он компилируется?
Может ли код С++ быть действительным как в С++ 03, так и в С++ 11, но делать разные вещи?
Ответ 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
В С++ 11 только литералы являются целыми константами нулевого указателя:
void f(void *); // #1
void f(...); // #2
template<int N> void g() {
f(0*N); // Calls #2; used to call #1
}
Закругленные результаты после целочисленного деления и по модулю
В С++ 03 компилятору разрешалось либо округлять к 0, либо к отрицательной бесконечности. В С++ 11 обязательно округлять до 0
int i = (-1) / 2; // Might have been -1 in C++03, is now ensured to be 0
Пробелы между закрытыми скобками вложенных шаблонов → vs →
Внутри специализации или инстанцирования >>
вместо этого можно интерпретировать как сдвиг вправо в С++ 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
}
Объявленные пользователем деструкторы имеют неявную спецификацию исключений пример из Какие нарушающие изменения внесены в С++ 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::exception
, может вести себя по-другому.
Ответ 2
Я указываю вам на эту статью и последующую, в которой есть хороший пример того, как >>
может изменить значение с С++ 03 на С++ 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
, которая является выражением.
В С++ 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
Поздравления, два разных результата для одного и того же выражения. Конечно, С++ 03 придумал предупреждающую форму Clang, когда я ее протестировал.
Ответ 3
Да, есть ряд изменений, которые приведут к тому, что один и тот же код приведет к другому поведению между С++ 03 и С++ 11. Различия в правилах последовательности приводят к некоторым интересным изменениям, в том числе к тому, что ранее принятое поведение undefined становится четким.
1. несколько мутаций одной и той же переменной в списке инициализаторов
Один очень интересный краевой случай имел бы несколько мутаций одной и той же переменной в списке инициализаторов, например:
int main()
{
int count = 0 ;
int arrInt[2] = { count++, count++ } ;
return 0 ;
}
В С++ 03 и С++ 11 это четко определено, но порядок оценки в С++ 03 не указан, но в С++ 11 они оцениваются в том порядке, в котором они отображаются. Поэтому, если мы скомпилируем с использованием clang
в режиме С++ 03, оно предоставит следующее предупреждение (посмотреть его в прямом эфире):
warning: multiple unsequenced modifications to 'count' [-Wunsequenced]
int arrInt[2] = { count++, count++ } ;
^ ~~
но не дает предупреждения в С++ 11 (видеть его в прямом эфире).
2. Новые правила последовательности делают я = ++ я + 1; хорошо определенная в С++ 11
Новые правила последовательности, принятые после С++ 03, означают, что:
int i = 0 ;
i = ++ i + 1;
больше не является undefined поведением в С++ 11, это описано в отчет о дефекте 637. Правила и примеры секвенирования не согласуются
3. Новые правила последовательности также делают ++++ i; хорошо определенная в С++ 11
Новые правила последовательности, принятые после С++ 03, означают, что:
int i = 0 ;
++++i ;
больше не является undefined поведением в С++ 11.
4. Чуть более чувствительные подписи с левым сдвигом
Более поздние черновики С++ 11 включают N3485
, которые я ссылаюсь ниже исправил поведение undefined смещения 1 бит в или мимо знака бит. Это также описано в отчете о дефекте 1457. Ховард Хиннант прокомментировал значение этого изменения в потоке на Является ли сдвиг слева (<) отрицательным целым числом undefined в С++ 11?,
5. Функции constexpr можно рассматривать как выражения постоянной времени компиляции в С++ 11
С++ 11 представил constexpr функции, которые:
Спецификатор constexpr объявляет, что во время компиляции можно оценить значение функции или переменной. Такие переменные и функции могут использоваться тогда, когда допускаются только компиляции констант времени.
в то время как С++ 03 не имеет функции constexpr, нам не нужно явно использовать ключевое слово constexpr, поскольку стандартная библиотека предоставляет множество функций в С++ 11 как constexpr. Например std:: numeric_limits:: min. Это может привести к разному поведению, например:
#include <limits>
int main()
{
int x[std::numeric_limits<unsigned int>::min()+2] ;
}
Используя clang
в С++ 03, это приведет к тому, что x
будет массивом переменной длины, который расширение и будет вызывать следующее предупреждение:
warning: variable length arrays are a C99 feature [-Wvla-extension]
int x[std::numeric_limits<unsigned int>::min()+2] ;
^
а в С++ 11 std::numeric_limits<unsigned int>::min()+2
- это выражение постоянной времени компиляции и не требует расширения VLA.
6. В С++ 11 noexcept спецификации исключения неявно генерируются для ваших деструкторов
Так как в С++ 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";
}
В С++ 11 вызовет std::terminate
, но будет успешно запущен в С++ 03.
7. В С++ 03 аргументы шаблона не могут иметь внутренней связи
Это хорошо описано в Почему std:: sort не принимает классы сравнения, объявленные в функции. Поэтому следующий код не должен работать в С++ 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
разрешает этот код в режиме С++ 03 с предупреждением, если вы не используете флаг -pedantic-errors
, который является своего рода icky, см. его в прямом эфире.
8. → не закрывается при закрытии нескольких шаблонов
Использование >>
для закрытия нескольких шаблонов уже не плохо сформировано, но может привести к коду с разными результатами в С++ 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';
}
а результат в С++ 03:
0
3
и в С++ 11:
0
0
9. С++ 11 изменяет некоторые конструкторы std::vector
Немного измененный код из этого ответапоказывает, что используя следующий конструктор из std::vector:
std::vector<T> test(1);
производит разные результаты в С++ 03 и С++ 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. Сужение конверсий в агрегатных инициализаторах
В С++ 11 сужение конверсии в агрегатных инициализаторах плохо сформировано, и похоже, что gcc
допускает это как в С++ 11, так и в С++ 03, хотя он предоставляет предупреждение по умолчанию в С++ 11
int x[] = { 2.0 };
Это описано в стандартном разделе проекта С++ 11 8.5.4
Элемент списка инициализации списка 3:
Список-инициализация объекта или ссылки типа T определяется следующим образом:
и содержит следующую марку (выделено мной):
В противном случае, если T - тип класса, рассматриваются конструкторы. Применяемые конструкторы перечисляются, а лучший выбирается с помощью разрешения перегрузки (13.3, 13.3.1.7). Если для преобразования любого из аргументов требуется сужение преобразования (см. ниже), программа плохо сформирована
Этот и многие другие примеры рассмотрены в черновик стандарта С++ annex C.2
С++ и ISO С++ 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() теперь постоянна [...] Некоторые реализации контейнеров, которые соответствуют С++ 2003, могут не соответствовать указанным требованиям размера() в этом Международном стандарте. Настройка контейнеров, таких как std:: list, на более строгие требования, может потребовать несовместимых изменений.
-
Измените базовый класс std:: ios_base:: failure [...] std:: ios_base:: failure больше не выводится непосредственно из std:: exception, но теперь выведен из std:: system_error, который, в свою очередь, получен из std:: runtime_error. Действительный код С++ 2003, предполагающий, что std:: ios_base:: failure выводится непосредственно из std:: exception, может выполняться по-другому в этом международном стандарте.
Ответ 4
Одно потенциально опасное обратное-несовместимое изменение заключается в конструкторах контейнеров последовательностей, таких как std::vector
, в частности, при перегрузке с указанием начального размера. Где в С++ 03 они скопировали созданный по умолчанию элемент, в С++ 11 они по умолчанию построили каждый.
Рассмотрим этот пример (используя boost::shared_ptr
, чтобы он был действительным С++ 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';
}
Причина в том, что С++ 03 указала одну перегрузку как для "указать размер и элемент прототипа", так и "указать только размер", как это (аргументы распределителя опущены для краткости):
container(size_type size, const value_type &prototype = value_type());
Это всегда будет копировать prototype
в контейнер size
раз. При вызове только с одним аргументом он создает size
копии созданного по умолчанию элемента.
В С++ 11 эта подпись конструктора была удалена и заменена этими двумя перегрузками:
container(size_type size);
container(size_type size, const value_type &prototype);
Второй работает по-прежнему, создавая size
копии элемента prototype
. Однако первый (который теперь обрабатывает вызовы только с указанным аргументом размера) по умолчанию создает каждый элемент по отдельности.
Мое предположение по причине этого изменения заключается в том, что перегрузка С++ 03 не будет использоваться с типом элемента только для перемещения. Тем не менее, это нарушение, тем не менее, и редко документируется.
Ответ 5
Результат неудачного чтения из std::istream
изменился. CppReference резюмирует его:
Если извлечение завершится неудачно (например, если была введена буква, в которой ожидается цифра),
value
остается неизмененным иfailbit
установлен. (до С++ 11)Если извлечение завершится неудачно, нуль записывается в
value
иfailbit
. Если извлечения приводит к слишком большому или слишком маленькому значению, чтобы соответствоватьvalue
, пишетсяstd::numeric_limits<T>::max()
илиstd::numeric_limits<T>::min()
и установлен флагfailbit
. (начиная с С++ 11)
Это прежде всего проблема, если вы привыкли к новой семантике, а затем должны писать с использованием С++ 03. Не особенно хорошая практика, но четко определена в С++ 11:
int x, y;
std::cin >> x >> y;
std::cout << x + y;
Однако в С++ 03 приведенный выше код использует неинициализированную переменную и, следовательно, имеет поведение undefined.
Ответ 6
Этот поток Какие различия, если таковые имеются, между С++ 03 и С++ 0x могут быть обнаружены во время выполнения, есть примеры (скопированные из этого потока) для определения языковых различий, например, путем использования слияния С++ 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);
}
и С++ 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