(Я ищу пару примеров, чтобы доказать свою точку зрения, а не список.)
Было ли когда-нибудь изменение стандарта C ++ (например, с 98 на 11, с 11 на 14 и т. Д.) Изменяло поведение существующего, правильно сформированного пользовательского кода с определенным поведением - незаметно? т.е. без предупреждения или ошибок при компиляции с более новой стандартной версией?
Примечания:
- Я спрашиваю о поведении в соответствии со стандартами, а не о выборе автора / разработчика компилятора.
- Чем менее надуманный код, тем лучше (как ответ на этот вопрос).
- Я не имею в виду код с определением версии, например
#if __cplusplus >= 201103L
. - Ответы, связанные с моделью памяти, прекрасны.
c++
language-lawyer
standardization
Эйнпоклум
источник
источник
auto
. До C ++ 11auto x = ...;
объявленныйint
. После он объявляет все, что...
есть.auto
указали переменные типа-типа. Думаю, вы могли бы сосчитать по пальцам одну руку, количество людей в мире, которые напишут такой код, за исключением конкурсов запутанного кода C ...Ответы:
Тип возвращаемого значения
string::data
изменяется сconst char*
наchar*
в C ++ 17. Это определенно может иметь значение.void func(char* data) { cout << data << " is not const\n"; } void func(const char* data) { cout << data << " is const\n"; } int main() { string s = "xyz"; func(s.data()); }
Немного надуманная, но эта легальная программа изменит свой вывод с C ++ 14 на C ++ 17.
источник
std::string
изменения для C ++ 17. Во всяком случае, я бы подумал, что изменения C ++ 11 могли каким-то образом вызвать изменение тихого поведения. +1.Ответ на этот вопрос показывает, как инициализация вектора с использованием одного
size_type
значения может привести к различному поведению между C ++ 03 и C ++ 11.std::vector<Something> s(10);
C ++ 03 по умолчанию создает временный объект типа элемента
Something
и копирует каждый элемент в векторе из этого временного объекта .C ++ 11 по умолчанию создает каждый элемент вектора.
Во многих (в большинстве?) Случаев они приводят к эквивалентному конечному состоянию, но для этого нет никаких причин. Это зависит от реализации конструкторов по
Something
умолчанию / копий.См. Этот надуманный пример :
class Something { private: static int counter; public: Something() : v(counter++) { std::cout << "default " << v << '\n'; } Something(Something const & other) : v(counter++) { std::cout << "copy " << other.v << " to " << v << '\n'; } ~Something() { std::cout << "dtor " << v << '\n'; } private: int v; }; int Something::counter = 0;
C ++ 03 создаст один по умолчанию,
Something
аv == 0
затем скопирует еще десять из него. В конце вектор содержит десять объектов соv
значениями от 1 до 10 включительно.C ++ 11 будет строить каждый элемент по умолчанию. Копии не делаются. В конце вектор содержит десять объектов,
v
значения которых от 0 до 9 включительно.источник
cv::mat
. Конструктор по умолчанию выделяет новую память, а конструктор копирования создает новое представление для существующей памяти.В стандарте есть список критических изменений в Приложении C [diff] . Многие из этих изменений могут привести к изменению бесшумного поведения.
Пример:
int f(const char*); // #1 int f(bool); // #2 int x = f(u8"foo"); // until C++20: calls #1; since C++20: calls #2
источник
bool
версии не было намеренным изменением само по себе, а просто побочным эффектом других правил преобразования. Настоящее намерение заключалось в том, чтобы устранить некоторую путаницу между кодировками символов, фактическое изменение заключается в том, чтоu8
литералы давали,const char*
а теперь даютconst char8_t*
.Это происходит каждый раз, когда они добавляют новые методы (а часто и функции) в стандартную библиотеку.
Предположим, у вас есть библиотека стандартного типа:
struct example { void do_stuff() const; };
довольно просто. В какой-то стандартной ревизии добавляется новый метод или перегрузка, или что-то еще:
struct example { void do_stuff() const; void method(); // a new method };
это может незаметно изменить поведение существующих программ C ++.
Это связано с тем, что ограниченных в настоящее время возможностей отражения C ++ достаточно, чтобы определить, существует ли такой метод, и запустить на его основе другой код.
template<class T, class=void> struct detect_new_method : std::false_type {}; template<class T> struct detect_new_method< T, std::void_t< decltype( &T::method ) > > : std::true_type {};
Это относительно простой способ обнаружить новое
method
, существует множество способов.void task( std::false_type ) { std::cout << "old code"; }; void task( std::true_type ) { std::cout << "new code"; }; int main() { task( detect_new_method<example>{} ); }
То же самое может произойти, когда вы удаляете методы из классов.
В то время как этот пример напрямую определяет существование метода, такие вещи, происходящие косвенно, могут быть менее надуманными. В качестве конкретного примера у вас может быть механизм сериализации, который решает, может ли что-то быть сериализовано в качестве контейнера на основе того, является ли оно повторяемым, или если у него есть данные, указывающие на необработанные байты и член размера, с одним предпочтительным перед другой.
Стандарт добавляет
.data()
метод к контейнеру, и внезапно тип меняет путь, который он использует для сериализации.Все, что стандарт C ++ может сделать, если он не хочет «зависать», - это сделать код, который молча ломается, редким или каким-то образом неразумным.
источник
О мальчик ... Ссылка cpplearner при условии , это страшно .
Среди прочего, C ++ 20 запретил объявление структур в стиле C для структур C ++.
typedef struct { void member_foo(); // Ill-formed since C++20 } m_struct;
Если вас научили писать такие структуры (а люди, которые преподают «C с классами», учат именно этому), вы облажались .
источник
typedef
свои структуры, и я определенно не собираюсь тратить на это свой мел. Это определенно вопрос вкуса, и хотя есть очень влиятельные люди (Торвальдс ...), которые разделяют вашу точку зрения, другие люди, такие как я, укажут, что все, что нужно, - это соглашение об именах для типов. Загромождение кодаstruct
ключевыми словами мало что добавляет к пониманию того, что заглавная буква (MyClass* object = myClass_create();
) не передает. Я уважаю это, если вы хотите, чтобыstruct
в вашем коде. Но я не хочу этого в своем.struct
только для простых старых типов данных иclass
всего, что имеет функции-члены. Но вы не можете использовать это соглашение в C, как иclass
в C.struct
на самом деле является POD. Как я пишу код C, большинство структур затрагиваются только кодом в одном файле и функциями, которые несут имя своего класса. По сути, это ООП без синтаксического сахара. Это позволяет мне фактически контролировать, какие изменения внутри astruct
и какие инварианты гарантируются между его членами. Итак, у меня,structs
как правило, есть функции-члены, частная реализация, инварианты и абстракция от их элементов данных. Не похоже на POD?extern "C"
блоках, я не вижу никаких проблем с этим изменением. Никто не должен заниматься типизацией структур в C ++. Это не более серьезное препятствие, чем тот факт, что C ++ имеет другую семантику, чем Java. Когда вы изучаете новый язык программирования, вам может потребоваться освоить новые привычки.Вот пример, который печатает 3 в C ++ 03 и 0 в C ++ 11:
template<int I> struct X { static int const c = 2; }; template<> struct X<0> { typedef int c; }; template<class T> struct Y { static int const c = 3; }; static int const c = 4; int main() { std::cout << (Y<X< 1>>::c >::c>::c) << '\n'; }
Это изменение поведения было вызвано специальной обработкой для
>>
. До C ++ 11>>
всегда был оператор сдвига вправо. В C ++ 11>>
тоже может быть частью объявления шаблона.источник
>>
этого способа.Триграфы упали
Исходные файлы кодируются в физическом наборе символов, который определяется способом, определяемым реализацией, на исходный набор символов , который определен в стандарте. Чтобы приспособить отображения из некоторых физических наборов символов, которые изначально не имели всей пунктуации, необходимой для исходного набора символов, язык определил триграфы - последовательности из трех общих символов, которые можно было использовать вместо менее распространенного символа пунктуации. Для их обработки требовались препроцессор и компилятор.
В C ++ 17 триграфы были удалены. Таким образом, некоторые исходные файлы не будут приняты более новыми компиляторами, если они не будут сначала переведены из физического набора символов в какой-либо другой физический набор символов, который однозначно отображает исходный набор символов. (На практике большинство компиляторов просто сделали интерпретацию триграфов необязательной.) Это не тонкое изменение поведения, а критическое изменение, предотвращающее компиляцию ранее приемлемых исходных файлов без внешнего процесса перевода.
Больше ограничений на
char
Стандарт также относится к набору символов выполнения , который определяется реализацией, но должен содержать как минимум весь исходный набор символов плюс небольшое количество управляющих кодов.
Стандарт C ++, определенный
char
как целочисленный тип, возможно, без знака, который может эффективно представлять каждое значение в наборе символов выполнения. По мнению юриста-лингвиста, вы можете утверждать, что achar
должно быть не менее 8 бит.Если в вашей реализации используется беззнаковое значение для
char
, то вы знаете, что оно может находиться в диапазоне от 0 до 255 и, следовательно, подходит для хранения всех возможных байтовых значений.Но если ваша реализация использует значение со знаком, у нее есть варианты.
Большинство из них используют дополнение до двух, что дает
char
минимальный диапазон от -128 до 127. Это 256 уникальных значений.Но другой вариант - знак + величина, где один бит зарезервирован, чтобы указать, является ли число отрицательным, а остальные семь битов указывают величину. Это даст
char
диапазон от -127 до 127, что составляет всего 255 уникальных значений. (Потому что вы теряете одну полезную комбинацию битов для представления -0.)Я не уверен, что комитет когда-либо явным образом обозначил это как дефект, но это произошло потому, что вы не могли полагаться на стандарт, чтобы гарантировать, что круговой рейс
unsigned char
тудаchar
и обратно сохранит исходное значение. (На практике все реализации использовали, потому что все они использовали дополнение до двух для целочисленных типов со знаком.)Только недавно (C ++ 17?) Была исправлена формулировка, обеспечивающая циклическое переключение. Это исправление, наряду со всеми другими требованиями
char
, фактически требует дополнения до двух для подписи,char
не говоря об этом явно (даже несмотря на то, что стандарт продолжает разрешать представления знак + величина для других целочисленных типов со знаком). Есть предложение потребовать, чтобы все подписанные интегральные типы использовали два дополнения, но я не помню, вошло ли оно в C ++ 20.Так что это своего рода противоположность тому, что вы ищете, потому что оно дает ранее
некорректный ичрезмерно самонадеянный код обратного исправления.источник
Не уверен, что вы сочтете это критическим изменением правильного кода, но ...
До C ++ 11 компиляторам разрешалось, но не требовалось, исключать копии при определенных обстоятельствах, даже когда конструктор копирования имел наблюдаемые побочные эффекты. Теперь у нас есть гарантированное копирование. Поведение по существу перешло от определенного реализацией к требуемому.
Это означает, что побочные эффекты конструктора копирования могли возникнуть в старых версиях, но никогда не появятся в новых. Вы можете утверждать, что правильный код не должен полагаться на результаты, определяемые реализацией, но я не думаю, что это то же самое, что сказать, что такой код неверен.
источник
Поведение при чтении (числовых) данных из потока и сбое чтения было изменено, начиная с C ++ 11.
Например, чтение целого числа из потока, не содержащего целого числа:
#include <iostream> #include <sstream> int main(int, char **) { int a = 12345; std::string s = "abcd"; // not an integer, so will fail std::stringstream ss(s); ss >> a; std::cout << "fail = " << ss.fail() << " a = " << a << std::endl; // since c++11: a == 0, before a still 12345 }
Поскольку c ++ 11 установит целое число чтения в 0, когда это не удалось; при c ++ <11 целое число не изменилось. Тем не менее, gcc, даже при принудительном возврате стандарта к c ++ 98 (с -std = c ++ 98) всегда показывает новое поведение, по крайней мере, с версии 4.4.7.
(Имхо, старое поведение было на самом деле лучше: зачем менять значение на 0, которое само по себе действительно, когда ничего нельзя было прочитать?)
Ссылка: см. Https://en.cppreference.com/w/cpp/locale/num_get/get
источник