2017-02-08 15 views
11

Biorąc pod uwagę tę aplikację:Czy legalne jest, aby podczas inicjalizacji usunąć nietrywialny konstruktor copy/move?

#include <iostream> 

struct X { 
    X(int _x)     { x = _x  + 1; } 
    X(const X& that)   { x = that.x + 10; } 
    X& operator=(const X& that) { x = that.x + 100; return *this; } 
    X(X&& that)     { x = that.x + 1000; } 
    X& operator=(X&& that)  { x = that.x + 10000; return *this; } 
    int x; 
}; 

int main() { 
    X a(1); 
    std::cout << "a.x=" << a.x << std::endl; 
    X b = 2; 
    std::cout << "b.x=" << b.x << std::endl; 
    X c = X(3); 
    std::cout << "c.x=" << c.x << std::endl; 
    X d = a; 
    std::cout << "d.x=" << d.x << std::endl; 
} 

ja oczekiwane wyjście będzie:

a.x=2 
b.x=1003 
c.x=1004 
d.x=12 

Jeszcze co pojawia się:

a.x=2 
b.x=3 
c.x=4 
d.x=12 

Live example

Jedynym sposobem, aby dostać moje oczekiwane wyniki to kompilacja z -fno-elide-constructors (example)

Pomyślałem, że kompilator nie może przesuwać rzeczy, jeśli to wpłynie na obserwowane zachowanie, ale GCC, klang i MSVC wydają się właśnie to robić.

Czy brakuje jakiejś ogólnej zasady lub czy jest to specyficzne dla inicjalizacji obiektu z tymczasowym?

+0

dupe/powiązane z [this] (http://stackoverflow.com/questions/24559875/can-functions-be-optimized-away-if-they-have-side-effects) i/lub [to] (http://stackoverflow.com/questions/9253316/under-what-conditions-does-c-optimize-out-constructor-calls) – NathanOliver

+0

Ponadto, 'X b = 2;' inicjuje i nigdy nie używa operatora przypisania . –

Odpowiedz

9

Kopiowanie elizja może się zdarzyć, nawet jeśli ignoruje efekty uboczne:

[class.copy]/31: Kiedy pewne kryteria są spełnione, to implementacja wolno pominąć budowę Kopiuj/przenieś klasy obiektu nawet jeśli konstruktor wybrany dla operacji kopiowania/przenoszenia i/lub destruktora dla obiektu ma efekty uboczne. [...]

Dobra ogólna zasada to nie pisać kodu, który opiera się na efektach ubocznych konstruktora kopiowania/przenoszenia, ponieważ możesz łatwo zostać ukąszonym przez elizę. Jest to szczególnie prawdziwe w C++ 17, gdzie pewne przypadki kopiowania są obowiązkowe.

+3

Aby dodać do tego, 12.8.31.3: "* gdy obiekt klasy tymczasowej, który nie został powiązany z odnośnikiem (12.2), zostanie skopiowany/przeniesiony do obiektu klasy z tym samym typem bez cv, operacja kopiowania/przenoszenia można pominąć przez skonstruowanie obiektu tymczasowego bezpośrednio w celu pominiętej kopii/przeniesienia * " – rustyx

4

Cytując the standard 12.8.3:

Kiedy pewne kryteria są spełnione, to implementacja wolno pominąć budowę kopia/ruch obiektu klasy nawet jeśli konstruktor wybrany dla kopii/move operacja i/lub destruktor dla obiektu mają efekty uboczne.

(kopalni Nacisk)

Oznacza to, że kompilator może opuszczać kopii nawet jeśli kopia posiada skutki uboczne. Co dokładnie dzieje się w twoim przypadku.

+0

Jest to 12.8.3 w wersji C++ 17. Pytałem o C + 11. Ale znalazłem to, 12.8.31. – rustyx

+0

@RustyX Tak, jest (rodzaj) w tej samej sekcji, zarówno w * 12.8.3X * –