2012-07-15 15 views
14

Otrzymałem dziwny błąd z gcc i nie mogę zrozumieć dlaczego. Zrobiłem poniższy przykład kodu, aby problem był bardziej przejrzysty. Zasadniczo istnieje zdefiniowana klasa, dla której konstruktor kopii i operator przypisania kopii są prywatni, aby zapobiec ich przypadkowemu wywołaniu.vector :: push_back nalega na użycie konstruktora kopiowania, chociaż konstruktor ruchu jest dostarczany

#include <vector> 
#include <cstdio> 
using std::vector; 

class branch 
{ 
public: 
    int th; 

private: 
    branch(const branch& other); 
    const branch& operator=(const branch& other); 

public: 

    branch() : th(0) {} 

    branch(branch&& other) 
    { 
    printf("called! other.th=%d\n", other.th); 
    } 

    const branch& operator=(branch&& other) 
    { 
    printf("called! other.th=%d\n", other.th); 
    return (*this); 
    } 

}; 



int main() 
{ 
    vector<branch> v; 
    branch a; 
    v.push_back(std::move(a)); 

    return 0; 
} 

Oczekuję, że ten kod się skompiluje, ale nie powiedzie się z gcc. W rzeczywistości gcc skarży się, że "branch branch" (const branch &) jest prywatne ", co, jak rozumiem, nie powinno być wywoływane.

Operator przypisania działa, ponieważ jeśli wymienić korpus główny() z

branch a; 
branch b; 
b = a; 

będzie opracować i prowadzony, jak oczekiwano.

Czy to jest prawidłowe zachowanie gcc? Jeśli tak, to co jest nie tak z powyższym kodem? Każda sugestia jest dla mnie pomocna. Dziękuję Ci!

+0

Działa dla mnie z gcc-4.6.1. –

+0

Używałem gcc 4.7.1-2. Spróbuję 4.6.1. Dzięki! – BreakDS

+2

Po przeczytaniu N3242, ten kod powinien być dozwolony (ale jeśli konstruktor ruchu wyrzuci wyjątek, program ma niezdefiniowane zachowanie). – aschepler

Odpowiedz

16

Spróbuj dodać "noexcept" do deklaracji konstruktora ruchu.

Nie mogę zacytować standardu, ale ostatnie wersje gcc wymagają, aby konstruktor kopiowania był publiczny lub aby konstruktor ruchu był uznany za "noexcept". Bez względu na kwalifikator "noexcept", jeśli upublicznisz konstruktora kopii, zachowa się on tak, jak oczekujesz w czasie wykonywania.

+3

Jeśli udostępni publicznie konstruktor kopiowania, obiekt zostanie skopiowany, czego wyraźnie próbuje uniknąć. W każdym przypadku poprawne jest tutaj zastosowanie konstruktora ruchu 'noexcept', więc +1. – ildjarn

+0

Dziękuję wam obu, konstruktowi noexcept i publicznemu kopiowaniu są poprawne. Jest jednak trochę sprzeczne z intuicją, że konstruktor kopiowania nie może być prywatny. – BreakDS

+0

@ BreakDS: Kopiuj konstruktor może być prywatny (zakładając poprawną implementację biblioteki standardowej), jeśli konstruktor ruchu nie jest 'noexcept'. – ildjarn

9

W przeciwieństwie do sugestii z poprzedniej odpowiedzi gcc 4.7 był nieprawidłowy, aby odrzucić ten kod, błąd, który został corrected in gcc 4.8.

Pełne zachowanie standardowej zgodnej dla vector<T>::push_back jest:

  • Jeśli jest tylko konstruktor kopiujący i nie konstruktor ruch, push_back skopiuje swój argument i daje silną gwarancję bezpieczeństwa wyjątek. Oznacza to, że jeśli parametr push_back zakończy się niepowodzeniem z powodu wyjątku spowodowanego przez ponowne przydzielenie pamięci wektorowej, oryginalny wektor pozostanie niezmieniony i będzie można go używać. Jest to znane zachowanie z C++ 98 i jest to również przyczyna bałaganu, który następuje.
  • Jeśli istnieje konstruktor ruchu noexcept dla T, push_back będzie przenieść z argumentu i daje silną gwarancję wyjątku. Bez niespodzianek tutaj.
  • Jeśli istnieje konstruktor pojedynek jest nienoexcept i istnieje również konstruktor kopiujący, push_back będzie kopia przedmiot i dać silną gwarancję bezpieczeństwa wyjątek. Na pierwszy rzut oka jest to nieoczekiwane. Podczas gdy push_back może się tutaj poruszać, będzie to możliwe tylko kosztem poświęcenia silnej gwarancji wyjątku. Jeśli przeniesiesz kod z C++ 98 na C++ 11, a Twój typ jest ruchomy, to w sposób dyskretny zmieni on zachowanie istniejących połączeń push_back. Aby uniknąć tej pułapki i utrzymać kompatybilność z kodem C++ 98, C++ 11 cofa się do wolniejszej kopii. Na tym polega zachowanie gcc 4.7. Ale jest więcej ...
  • Jeśli istnieje konstruktor ruch, który nie jest noexcept ale nie konstruktora kopia w ogóle - to znaczy, że element może być przeniesiony, a nie skopiowany tylko - push_back wykona ruch, ale nie dać silną gwarancję bezpieczeństwa wyjątek. To tam gcc 4.7 poszło nie tak. W C++ 98 nie istnieją żadne typy, które są ruchome, ale nie mogą być kopiowane. Dlatego poświęcenie silnego bezpieczeństwa wyjątku nie złamie istniejącego kodu. To dlatego jest dozwolone, a oryginalny kod jest w rzeczywistości legalny C++ 11.

Zobacz cppreference.com na push_back:

Jeśli jest wyjątek, funkcja ta nie ma żadnego wpływu (silny gwarancyjny wyjątku).

Jeśli konstruktor ruchu T nie jest zerowy, a konstruktor kopiowania jest niedostępny, wektor użyje konstruktora ruchu rzucania . Jeśli rzuci, gwarancja zostanie uchylona, ​​a skutki nie zostaną określone.

Albo trochę bardziej zawiłe §23.3.6.5 z C++ 11 Standard (podkreślenie dodane przeze mnie):

Powoduje realokacji jeśli nowy rozmiar jest większy niż stary pojemności. Jeśli nie nastąpi żadna realokacja, wszystkie punkty iteracyjne i odniesienia przed punkt wstawienia zachowują ważność. Jeśli wyjątek jest zgłaszany innym niż przez konstruktora kopiowania, konstruktor ruchu, operator przypisania lub operator przeniesienia ruchu T lub dowolna operacja InputIterator nie ma żadnych efektów. Jeśli konstruktor ruchu zrzucony przez konstruktor ruchu obiektu , który nie jest woluminem CopyDsertable, efekty są nieokreślone.

Lub jeśli nie lubisz czytać, Scott Meyer's Going Native 2013 talk (począwszy od 0:30:20 z interesującej części około 0:42:00).

Powiązane problemy