Чи може код C ++ бути дійсним як для C ++ 03, так і для C ++ 11, але чи можна робити різні речі?


298

Чи можливо код C ++ відповідати як стандарту C ++ 03, так і стандарту C ++ 11 , але чи можна робити різні речі залежно від того, для якого стандарту він складається?


26
Я впевнений, що це autoможе призвести до подібної ситуації
OMGtechy

8
Так. Один із прикладів - >>при використанні в шаблоні. Можна створити ситуацію, коли вона може складати обидва стандарти. Ще одна, для якої я впевнений, що було б легко знайти зміни, - це ініціалізація.
chris

5
Ось приємна стаття про ситуацію >>: gustedt.wordpress.com/2013/12/15/…
chris

6
@OMGtechy: Я не думаю, що це auto може спричинити. Зі старим значенням для autoдекларації потрібна назва типу; з новим значенням ім’я типу не дозволяється.
Кіт Томпсон

2
Як це відкрито? Ви самі вказували через інше запитання, що відповідь на це питання "так, ось приклад того, як". На це ви є дуже певна відповідь, як ви самі вказали.
jalf

Відповіді:


283

Відповідь - це певне "так". На плюсі ​​є:

  • Код, який раніше неявно копіював об'єкти, тепер неявно переміщатиме їх, коли це можливо.

З негативної сторони в додатку С стандарту наведено кілька прикладів. Незважаючи на те, що існує набагато більше негативних, ніж позитивних, кожен з них набагато рідше трапляється.

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

#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. Але сподіваюся, що кожен, хто написав такий код, буде радий цьому!
type1232

4
Але сам std :: system_error (опосередковано) походить від std :: винятку, тому catch (std::exception &)все одно ловить std::ios_base::failure.
користувач2665887

@ user2665887 ви праві. це все ще може впливати на поведінку програми, але я не можу зараз придумати мінімальний приклад.
приклад

4
Я супер розгублений, оскільки те, про що ви говорите, operator newє точним (це тепер може кинути std::bad_array_new_length), але ваш приклад цього зовсім не показує. Код, який ви показуєте, однаковий у C ++ 03 та C ++ 11 AFAIK.
Mooing Duck

2
Перегортання списку :: розмір O (1) полягає в тому, що зрощення зараз є O (n)
Тоні Делрой

55

Я вказую на цю статтю та подальші дії , в яких є хороший приклад того, як >>можна змінити значення з 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для ::twoверсії C ++ 03
zahir

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

@zahir, Це не тип, а лише значення.
chris

добре, правильні параметри cmdline попереджають про це ( warning: comparisons like ‘X<=Y<=Z’ do not have their mathematical meaning [-Wparentheses]), але все-таки приємний приклад того, як неоднозначний ::оператор змінює значення (або посилаючись на глобальний обсяг, або перенаправляючи той, що стоїть прямо перед ним)
наприклад,

@example. Як не дивно, GCC подає це попередження, але Кланг ні.
chris

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 };

Це розглядається в 11 стандартний розділ проекту C ++ 8.5.4 Список ініціалізації пункту 3 :

Ініціалізація списку об'єкта або посилання типу T визначається наступним чином:

і містить таку кульку ( акцентна міна ):

В іншому випадку, якщо T - клас класу, конструктори розглядаються. Застосовувані конструктори перераховуються, а найкращий вибирається за допомогою роздільної здатності перевантаження (13.3, 13.3.1.7). Якщо для перетворення будь-якого з аргументів потрібно звуження перетворення (див. Нижче), програма неправильно формується

Цей та багато інших прикладів висвітлено у проекті стандартних розділів annex C.2 C ++ та 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.

  • Складність функцій-членів розміру () тепер постійна [...] Деякі реалізації контейнерів, які відповідають C ++ 2003, можуть не відповідати заданим вимогам розміру () цього Міжнародного стандарту. Настроювання контейнерів, таких як std :: list, суворішим вимогам може вимагати несумісних змін.

  • Зміна базового класу std :: ios_base :: fail [...] std :: ios_base :: відмова більше не походить безпосередньо від std :: винятку, але тепер походить від std :: system_error, який у свою чергу походить від std :: runtime_error. Дійсний код C ++ 2003, який передбачає, що std :: ios_base :: похибка походить безпосередньо від std :: виключення може виконуватися по-різному в цьому Міжнародному стандарті.


Тож більшість прикладів звужується до того, що раніше не визначена поведінка зараз чітко визначена?
MatthiasB

@MatthiasB 2, 3 і 4 про це, тому на даний момент вони вже не є більшістю прикладів. Сумніваюсь, я знайду ще багато не визначених прикладів поведінки, тому що я додаю більше, то вони стануть меншим набором.
Шафік Ягмур

Ну, поведінка №1 не визначена, тому я вважаю це невизначеною поведінкою (принаймні, ви не можете розраховувати, що ви отримаєте конкретний результат із c ++ 03, зараз ви можете з c ++ 11), №5 використовує не- стандартне розширення c ++. Але я думаю, ти маєш рацію. Чим більше ви її шукаєте, тим більше прикладів ви знайдете, які визначені в обох стандартах, але дають різні результати.
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.