Czy kod C ++ może być poprawny zarówno w C ++ 03, jak i C ++ 11, ale może robić różne rzeczy?

298

Czy jest możliwe, aby kod C ++ był zgodny zarówno ze standardem C ++ 03, jak i standardem C ++ 11 , ale robi różne rzeczy w zależności od tego, w ramach którego standardu jest kompilowany?

Erik Sjölund
źródło
26
Jestem prawie pewien, że automoże to doprowadzić do takiej sytuacji
OMGtechy
8
Tak. Jednym z przykładów jest >>użycie w szablonie. Możesz wymyślić sytuację, w której można skompilować oba standardy. Innym, którego jestem pewien, że łatwo byłoby znaleźć zmiany, jest inicjalizacja.
Chris
5
Oto fajny artykuł na temat >> sytuacji: gustedt.wordpress.com/2013/12/15/…
Chris
6
@OMGtechy: Nie sądzę, że auto może to powodować. Zgodnie ze starym znaczeniem autodeklaracja wymaga nazwy typu; w nowym znaczeniu nazwa typu jest niedozwolona.
Keith Thompson
2
Jak to jest otwarte? Sam wskazałeś w innym pytaniu, że odpowiedź na to pytanie brzmi „tak, oto przykład jak”. Jak już zauważyłeś, odpowiedź na pytanie jest bardzo wyraźna.
czerwiec

Odpowiedzi:

283

Odpowiedź jest jednoznaczna: tak. Na plus jest:

  • Kod, który wcześniej niejawnie kopiował obiekty, będzie teraz domyślnie przenosił je, jeśli to możliwe.

Po stronie negatywnej kilka przykładów wymieniono w załączniku C normy. Mimo że jest o wiele więcej negatywnych niż pozytywnych, każde z nich jest znacznie mniej prawdopodobne.

Literały łańcuchowe

#define u8 "abc"
const char* s = u8"def"; // Previously "abcdef", now "def"

i

#define _x "there"
"hello "_x // Previously "hello there", now a user defined string literal

Wpisz konwersje 0

W C ++ 11 tylko literały są stałymi zerowymi wskaźnikami liczb całkowitych:

void f(void *); // #1
void f(...); // #2
template<int N> void g() {
    f(0*N); // Calls #2; used to call #1
}

Zaokrąglone wyniki po dzieleniu liczb całkowitych i modulo

W C ++ 03 kompilatorowi wolno było zaokrąglać w kierunku 0 lub w kierunku ujemnej nieskończoności. W C ++ 11 obowiązkowe jest zaokrąglanie w kierunku 0

int i = (-1) / 2; // Might have been -1 in C++03, is now ensured to be 0

Białe spacje między nawiasami zamykającymi szablony >> vs>>

Wewnątrz specjalizacji lub instancji >>można zamiast tego interpretować jako przesunięcie w prawo w C ++ 03. Jest to bardziej prawdopodobne, że złamie istniejący kod: (z 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);
}

Operator newmoże teraz zgłaszać inne wyjątki niż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
}

Zadeklarowane przez użytkownika destruktory mają domyślny przykład specyfikacji wyjątku z Jakie przełamujące zmiany zostały wprowadzone w C ++ 11?

struct A {
    ~A() { throw "foo"; } // Calls std::terminate in C++11
};
//...
try { 
    A a; 
} catch(...) { 
    // C++03 will catch the exception
} 

size() kontenerów jest teraz wymagane do działania w O (1)

std::list<double> list;
// ...
size_t s = list.size(); // Might be an O(n) operation in C++03

std::ios_base::failurenie pochodzi std::exceptionjuż bezpośrednio

Podczas gdy bezpośrednia klasa podstawowa jest nowa, std::runtime_errornie jest. A zatem:

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";
}
przykład
źródło
11
Fajnie, +1. Innym jest to, że użytkownik zadeklarował, że destruktor jest teraz domyślnie, noexecpt(true)więc throww destruktorze będzie teraz wywoływał std::terminate. Ale mam nadzieję, że każdy, kto napisał taki kod, będzie z tego zadowolony!
typ1232
4
Ale sam std :: system_error pochodzi (pośrednio) z std :: wyjątku, więc catch (std::exception &)nadal się łapie std::ios_base::failure.
user2665887
@ user2665887 masz rację. wciąż może wpływać na zachowanie programu, ale nie mogę teraz wymyślić minimalnego przykładu.
przykład
4
Jestem bardzo zdezorientowany, ponieważ to, co mówisz, operator newjest dokładne (może teraz rzucać std::bad_array_new_length), ale twój przykład wcale tego nie pokazuje. Wyświetlany kod jest taki sam w C ++ 03 i C ++ 11 AFAIK.
Mooing Duck
2
Drugą stroną listy :: rozmiar będący O (1) jest to, że splice jest teraz O (n)
Tony Delroy
55

Zwracam uwagę na ten artykuł i kontynuację , która zawiera ładny przykład tego, jak >>zmienić znaczenie z C ++ 03 na C ++ 11, jednocześnie kompilując oba.

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  
}

Kluczową częścią jest linia wejściowa main, która jest wyrażeniem.

W C ++ 03:

1 >> ::three = 0
=> fon< fun< 0 >::two >::one;

fun< 0 >::two = int
=> fon< int >::one

fon< int >::one = true
=> true

W C ++ 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

Gratulacje, dwa różne wyniki dla tego samego wyrażenia. To prawda, że ​​C ++ 03 wymyślił ostrzeżenie od Clanga, kiedy go testowałem.

Chris
źródło
to jest dziwne, że nie wymaga typenamedo ::twow C ++ 03 wersji
Zahir
3
Niezły, sprowadzający go do oceny różnych standardów truelub falsedla nich. Może moglibyśmy użyć go jako testu funkcji </joke>
cmaster - przywrócić monikę
@zahir, To nie jest typ, tylko wartość.
Chris
no cóż, odpowiednie opcje cmdline ostrzegają o tym ( warning: comparisons like ‘X<=Y<=Z’ do not have their mathematical meaning [-Wparentheses]), ale wciąż jest to dobry przykład tego, jak niejednoznaczny ::operator zmienia znaczenie (albo odnosząc się do zakresu globalnego, albo dereferencjonując tego, który stoi bezpośrednio przed nim)
przykład
@przykład, co zaskakujące, GCC daje to ostrzeżenie, ale Clang nie.
Chris
39

Tak, istnieje wiele zmian, które spowodują, że ten sam kod spowoduje różne zachowanie między C ++ 03 a C ++ 11. Różnice w regułach sekwencjonowania powodują, że niektóre interesujące zmiany, w tym niektóre niezdefiniowane zachowania, stają się dobrze zdefiniowane.

1. wiele mutacji tej samej zmiennej na liście inicjalizacyjnej

Jeden bardzo interesujący przypadek narożny zawierałby wiele mutacji tej samej zmiennej na liście inicjalizacyjnej, na przykład:

int main()
{
    int count = 0 ;
    int arrInt[2] = { count++, count++ } ;

    return 0 ;
}

Zarówno w C ++ 03, jak i C ++ 11 jest to dobrze określone, ale kolejność oceny w C ++ 03 jest nieokreślona, ale w C ++ 11 są one oceniane w kolejności, w jakiej występują . Jeśli więc skompilujemy clangw trybie C ++ 03, wyświetli się następujące ostrzeżenie ( zobacz na żywo ):

warning: multiple unsequenced modifications to 'count' [-Wunsequenced]

    int arrInt[2] = { count++, count++ } ;

                           ^        ~~

ale nie zapewnia ostrzeżenia w C ++ 11 ( zobacz na żywo ).

2. Nowe reguły sekwencjonowania sprawiają, że i = ++ i + 1; dobrze zdefiniowany w C ++ 11

Nowe reguły sekwencjonowania przyjęte po C ++ 03 oznaczają, że:

int i = 0 ;
i = ++ i + 1;

nie jest już niezdefiniowanym zachowaniem w C ++ 11, jest to opisane w raporcie o defektach 637. Reguły sekwencjonowania i przykład nie zgadzają się

3. Nowe reguły sekwencjonowania sprawiają, że ++++ i; dobrze zdefiniowany w C ++ 11

Nowe reguły sekwencjonowania przyjęte po C ++ 03 oznaczają, że:

int i = 0 ;
++++i ;

nie jest już niezdefiniowanym zachowaniem w C ++ 11.

4. Nieco bardziej rozsądne podpisane lewe przesunięcia

Późniejsze wersje C ++ 11, N3485które poniżej łączę, naprawiły niezdefiniowane zachowanie przesunięcia 1 bitu do lub powyżej bitu znaku . Jest to również uwzględnione w raporcie wad 1457 . Howard Hinnant skomentował znaczenie tej zmiany w wątku Czy przesuwanie w lewo (<<) jest zachowaniem ujemnej liczby całkowitej w C ++ 11? .

5. Funkcje constexpr mogą być traktowane jako wyrażenia stałej czasowej kompilacji w C ++ 11

C ++ 11 wprowadził funkcje constexpr, które:

Specyfikator constexpr deklaruje, że możliwe jest oszacowanie wartości funkcji lub zmiennej w czasie kompilacji. Takie zmienne i funkcje mogą być następnie użyte, gdy dozwolone są tylko wyrażenia stałej czasowej kompilacji.

podczas gdy C ++ 03 nie ma funkcji constexpr , nie musimy jawnie używać słowa kluczowego constexpr, ponieważ standardowa biblioteka udostępnia wiele funkcji w C ++ 11 jako constexpr . Na przykład std :: numeric_limits :: min . Co może prowadzić do różnych zachowań, na przykład:

#include <limits>

int main()
{
    int x[std::numeric_limits<unsigned int>::min()+2] ;
}

Użycie clangw C ++ 03 spowoduje, że xbędzie to tablica o zmiennej długości, która jest rozszerzeniem i wygeneruje następujące ostrzeżenie:

warning: variable length arrays are a C99 feature [-Wvla-extension]
    int x[std::numeric_limits<unsigned int>::min()+2] ;
         ^

podczas gdy w C ++ 11 std::numeric_limits<unsigned int>::min()+2jest wyrażeniem stałej czasowej kompilacji i nie wymaga rozszerzenia VLA.

6. W C ++ 11 specyfikacje wyjątków nie są generowane domyślnie dla twoich destruktorów

Ponieważ w C ++ 11 zdefiniowany przez użytkownika destruktor ma niejawną noexcept(true)specyfikację, jak wyjaśniono w noexcept destruktorach , oznacza to, że następujący program:

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

W C ++ 11 zadzwoni, std::terminateale będzie działać poprawnie w C ++ 03.

7. W C ++ 03 argumenty szablonu nie mogą mieć wewnętrznego powiązania

Ładnie to opisano w Dlaczego std :: sort nie akceptuje Porównaj klasy zadeklarowane w funkcji . Poniższy kod nie powinien działać w 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;
    }
}

ale obecnie clangzezwala na ten kod w trybie C ++ 03 z ostrzeżeniem, chyba że użyjesz -pedantic-errorsflagi, co jest dość trudne, zobacz na żywo .

8. >> nie jest już źle sformułowany przy zamykaniu wielu szablonów

Używanie >>do zamykania wielu szablonów nie jest już źle sformułowane, ale może prowadzić do kodu z różnymi wynikami w C ++ 03 i C + 11. Poniższy przykład pochodzi z nawiasów kątowych i kompatybilności wstecznej :

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

a wynik w C ++ 03 to:

0
3

oraz w C ++ 11:

0
0

9. C ++ 11 zmienia niektóre konstruktory std :: vector

Nieznacznie zmodyfikowany kod z tej odpowiedzi pokazuje, że użycie następującego konstruktora z std :: vector :

std::vector<T> test(1);

daje różne wyniki w C ++ 03 i 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. Zawężanie konwersji w zbiorczych inicjalizatorach

W C ++ 11 zawężająca się konwersja inicjatorów agregujących jest źle sformułowana i wygląda na to, że gccpozwala na to zarówno w C ++ 11, jak i C ++ 03, chociaż domyślnie wyświetla ostrzeżenie w C ++ 11:

int x[] = { 2.0 };

Jest to omówione w projekcie standardowej sekcji C ++ 11 akapit 3 : 8.5.4 Inicjalizacja listy :

Inicjalizacja listy obiektu lub odwołania typu T jest zdefiniowana następująco:

i zawiera następujący punktor ( moje wyróżnienie ):

W przeciwnym razie, jeśli T jest typem klasy, brane są pod uwagę konstruktory. Odpowiednie konstruktory są wyliczone, a najlepszy wybiera się na podstawie rozdzielczości przeciążenia (13.3, 13.3.1.7). Jeśli do konwersji któregokolwiek z argumentów wymagane jest zawężenie konwersji (patrz poniżej), program jest źle sformułowany

To i wiele więcej instancji są uwzględnione w projekcie C ++ standardowej sekcji annex C.2 C ++ i C ++ ISO 2003 . Obejmuje również:

  • Nowe rodzaje literałów ciągów [...] W szczególności makra o nazwach R, u8, u8R, u, uR, U, UR lub LR nie będą rozszerzane, gdy sąsiadują z literałem ciągów, ale będą interpretowane jako część literału ciągów . Na przykład

    #define u8 "abc"
    const char *s = u8"def"; // Previously "abcdef", now "def"
  • Zdefiniowana przez użytkownika obsługa literałów [...] Poprzednio nr 1 składał się z dwóch oddzielnych tokenów przetwarzania wstępnego, a makro _x byłoby rozwinięte. W tym standardzie międzynarodowym nr 1 składa się z pojedynczych tokenów przetwarzania wstępnego, więc makro nie jest rozwijane.

    #define _x "there"
    "hello"_x // #1
  • Określ zaokrąglenie dla wyników liczb całkowitych / i% [...] 2003, które wykorzystują dzielenie liczb całkowitych, zaokrągla wynik w kierunku 0 lub w kierunku ujemnej nieskończoności, podczas gdy ten Międzynarodowy Standard zawsze zaokrągla wynik w kierunku 0.

  • Złożoność funkcji składowych size () jest teraz stała [...] Niektóre implementacje kontenerów zgodne z C ++ 2003 mogą nie spełniać określonych wymagań size () w niniejszej Normie Międzynarodowej. Dostosowanie kontenerów takich jak std :: list do surowszych wymagań może wymagać niezgodnych zmian.

  • Zmień klasę podstawową std :: ios_base :: awaria [...] std :: ios_base :: awaria nie jest już uzyskiwana bezpośrednio ze std :: wyjątku, ale teraz pochodzi od std :: system_error, który z kolei pochodzi od std :: runtime_error. Prawidłowy kod C ++ 2003, który zakłada, że ​​błąd std :: ios_base :: wywodzi się bezpośrednio ze std :: wyjątek, może działać inaczej w tym standardzie międzynarodowym.

Shafik Yaghmour
źródło
Czyli większość przykładów ogranicza się do tego, że wcześniej niezdefiniowane zachowanie jest teraz dobrze zdefiniowane?
MatthiasB
@ MatthiasB 2, 3 i 4 są na ten temat, więc w tym momencie nie są już większością przykładów. Wątpię, czy znajdę wiele innych niezdefiniowanych przykładów zachowań, więc gdy dodam więcej, staną się one mniejszym zestawem.
Shafik Yaghmour
Cóż, zachowanie nr 1 jest nieokreślone, więc policzyłbym to jako zachowanie nieokreślone (przynajmniej nie można oczekiwać, że uzyska się konkretny wynik z c ++ 03, teraz z c ++ 11 możesz), nr 5 używa nie- standardowe rozszerzenie c ++. Ale chyba masz rację. Im więcej go szukasz, tym więcej przykładów znajdziesz, które są zdefiniowane w obu standardach, ale dają różne wyniki.
MatthiasB
@MatthiasB tak, zarówno nieokreślone, jak i niezdefiniowane zachowanie ma niepożądane skutki. Jeśli chodzi o rozszerzenia uwzględniające Linuksa, to zależy ono od wielu rozszerzeń gcc, które powinniśmy założyć w świecie rzeczywistym, że mają znaczenie. Nie spodziewałem się znaleźć tak wielu przykładów, kiedy po raz pierwszy odpowiedziałem na to pytanie.
Shafik Yaghmour
35

Jedną potencjalnie niebezpieczną niezgodną wstecz wsteczną zmianą są konstruktory kontenerów sekwencji, takie jak std::vectorprzeciążenie określające rozmiar początkowy. Tam, gdzie w C ++ 03, skopiowali domyślnie skonstruowany element, w C ++ 11 domyślnie konstruują każdy z nich.

Rozważ ten przykład (używając boost::shared_ptrpoprawnego 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 Przykład na żywo

C ++ 11 Przykład na żywo

Powodem jest to, że C ++ 03 określił jedno przeciążenie zarówno dla „określ rozmiar, jak i element prototypowy” i „określ tylko rozmiar”, jak poniżej (argumenty alokatora pominięto dla zwięzłości):

container(size_type size, const value_type &prototype = value_type());

To zawsze będzie kopiowane prototypedo czasów kontenera size. Po wywołaniu z jednym argumentem utworzy sizekopie domyślnie zbudowanego elementu.

W C ++ 11 ta sygnatura konstruktora została usunięta i zastąpiona tymi dwoma przeciążeniami:

container(size_type size);

container(size_type size, const value_type &prototype);

Drugi działa jak poprzednio, tworząc sizekopie prototypeelementu. Jednak pierwszy (który obsługuje teraz wywołania tylko z podanym argumentem size) domyślnie konstruuje każdy element osobno.

Domyślam się, że powodem tej zmiany jest to, że przeciążenie C ++ 03 nie byłoby możliwe w przypadku elementu typu tylko ruch. Niemniej jednak jest to przełomowa zmiana i rzadko ją udokumentowano.

Angew nie jest już dumny z SO
źródło
3
Chociaż jest to oczywiście przełomowa zmiana, wolę zachowanie w C ++ 11. Spodziewałbym się, że spowoduje to dequeprzechowywanie dziesięciu osobnych widgetów, a nie dziesięciu widgetów współużytkujących ten sam zasób.
Agentlien
19

Wynik nieudanego odczytu z std::istreamzmienił się. CppReference ładnie to podsumowuje:

Jeśli wyodrębnienie nie powiedzie się (np. Jeśli litera została wprowadzona w miejscu, w którym spodziewana jest cyfra), valuepozostanie niezmodyfikowana i failbitustawiona. (do C ++ 11)

Jeśli ekstrakcja nie powiedzie się, zero jest zapisywane valuei failbitustawiane. Jeśli ekstrakcja powoduje, że wartość jest zbyt duża lub zbyt mała, aby ją zmieścić value, std::numeric_limits<T>::max()lub std::numeric_limits<T>::min()jest zapisywana, a failbitflaga jest ustawiona. (od C ++ 11)

Jest to przede wszystkim problem, jeśli jesteś przyzwyczajony do nowej semantyki, a następnie musisz pisać za pomocą C ++ 03. Następujące nie jest szczególnie dobrą praktyką, ale dobrze zdefiniowane w C ++ 11:

int x, y;
std::cin >> x >> y;
std::cout << x + y;

Jednak w C ++ 03 powyższy kod używa niezainicjowanej zmiennej, a zatem ma niezdefiniowane zachowanie.

Anton Golov
źródło
4
Można dodać, że w C ++ 03 można było użyć tego standardowego zachowania do zapewnienia wartości domyślnej, jak w int x = 1, y = 1; cin >> x >> y; cout << x*y;. W przypadku C ++ 03 poprawnie wygenerowałoby się, xgdyby nie ymożna było odczytać.
cmaster
15

Wątek Jakie ewentualne różnice między C ++ 03 a C ++ 0x, które można wykryć w czasie wykonywania, zawiera przykłady (skopiowane z tego wątku) w celu ustalenia różnic językowych, na przykład poprzez wykorzystanie zwinięcia referencji 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); 
}

oraz c ++ 11 zezwalający na typy lokalne jako parametry szablonu:

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
źródło
7

Oto inny przykład:

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

Wydruki:

Using c++03: no
Using c++11: yes

Zobacz wynik na Coliru

StackedCrooked
źródło