6

czytałem Want Speed? Pass by Value na C++ Next blog i stworzył this program aby poczuć kopiowania elizji i przenieść semantykę w C++ 0x:Kopiowanie elizja w Visual C++ 2010 Beta 2

#include <vector> 
#include <iostream> 

class MoveableClass { 
public: 
    MoveableClass() : m_simpleData(0), instance(++Instances) { 
     std::cout << "Construct instance " << instance << " (no data)" << std::endl; 
    } 

    MoveableClass(std::vector<double> data) : m_data(std::move(data)), m_simpleData(0), instance(++Instances) { 
     std::cout << "Construct instance " << instance << " (with data)" << std::endl; 
    } 

    MoveableClass(int simpleData) : m_simpleData(simpleData), instance(++Instances) { 
     std::cout << "Construct instance " << instance << " (with simple data)" << std::endl; 
    } 

    MoveableClass(const MoveableClass& other) 
     : m_data(other.m_data), m_simpleData(other.m_simpleData), instance(++Instances) 
    { 
     std::cout << "Construct instance " << instance << " from a copy of " << other.instance << std::endl; 
     Elided = false; 
    } 

    MoveableClass(MoveableClass&& other) 
     : m_data(std::move(other.m_data)), m_simpleData(other.m_simpleData), instance(++Instances) 
    { 
     std::cout << "Construct instance " << instance << " from a move of " << other.instance << std::endl; 
     Elided = false; 
    } 

    MoveableClass& operator=(MoveableClass other) { 
     std::cout << "Assign to instance " << instance << " from " << other.instance << std::endl; 
     other.Swap(*this); 
     return *this; 
    } 

    ~MoveableClass() { 
     std::cout << "Destroy instance " << instance << std::endl; 
     --Instances; 
    } 

    void Swap(MoveableClass& other) { 
     std::swap(m_data, other.m_data); 
     std::swap(m_simpleData, other.m_simpleData); 
    } 

    static int Instances; 
    static bool Elided; 

private: 
    int instance; 
    int m_simpleData; 
    std::vector<double> m_data; 
}; 

int MoveableClass::Instances = 0; 
bool MoveableClass::Elided = true; 

std::vector<double> BunchOfData() { 
    return std::vector<double>(9999999); 
} 

int SimpleData() { 
    return 9999999; 
} 

MoveableClass CreateRVO() { 
    return MoveableClass(BunchOfData()); 
} 

MoveableClass CreateNRVO() { 
    MoveableClass named(BunchOfData()); 
    return named; 
} 

MoveableClass CreateRVO_Simple() { 
    return MoveableClass(SimpleData()); 
} 

MoveableClass CreateNRVO_Simple() { 
    MoveableClass named(SimpleData()); 
    return named; 
} 

int main(int argc, char* argv[]) { 
    std::cout << "\nMove assign from RVO: " << '\n'; 
    { 
     MoveableClass a; 
     a = CreateRVO(); 
    } 
    std::cout << "Move elided: " << (MoveableClass::Elided ? "Yes" : "No") << '\n'; 
    MoveableClass::Elided = true; // reset for next test 

    std::cout << "\nMove assign from RVO simple: " << '\n'; 
    { 
     MoveableClass a; 
     a = CreateRVO_Simple(); 
    } 
    std::cout << "Move elided: " << (MoveableClass::Elided ? "Yes" : "No") << '\n'; 
    MoveableClass::Elided = true; // reset for next test 

    std::cout << "\nMove assign from NRVO: " << '\n'; 
    { 
     MoveableClass a; 
     a = CreateNRVO(); 
    } 
    std::cout << "Move elided: " << (MoveableClass::Elided ? "Yes" : "No") << '\n'; 
    MoveableClass::Elided = true; // reset for next test 

    std::cout << "\nMove assign from NRVO simple: " << std::endl; 
    { 
     MoveableClass a; 
     a = CreateNRVO_Simple(); 
    } 
    std::cout << "Move elided: " << (MoveableClass::Elided ? "Yes" : "No") << '\n'; 
    MoveableClass::Elided = true; // reset for next test 
} 

Oto wynik otrzymuję kiedy zestawiane w trybie zwolnienia w Visual C++ 10,0 (Beta 2):

Ruch przypisać Rvo:
Konstrukt wystąpienie 1 (brak danych)
Construct wystąpienie 2 (z danymi)
Konstrukt wystąpienie 3 z przejściem 2
zniszczyć przykład 2
przydzielanie do przykładu 1 z 3
zniszczyć przykład 3
zniszczyć przykład 1
Ruch pomijana: Nie

Ruch przypisać Rvo prosta
Konstrukt wystąpienie 1 (brak danych)
Konstrukt wystąpienie 2 (z prostych danych)
przydzielanie do przykładu 1, z 2
Destro Y przykład 2
zniszczyć przykład 1
Ruch pomijana Tak

Ruch przypisać z NRVO:
Konstrukt wystąpienie 1 (brak danych)
Construct wystąpienie 2 (z danymi)
Przypisanie do przykładu 1 z kwasu 2
Destroy instancja 2
Zniszcz instancję 1
Move pomijana: Tak

Move przypisać od NRVO prosty:
Konstrukt wystąpienie 1 (brak danych)
Construct wystąpienie 2 (z prostych danych)
przydzielanie do przykładu 1, z 2
zniszczyć przykład 2
zniszczyć przykład 1
Ruch pomijana Tak

Jednak Jestem zakłopotany jedną rzeczą. Jak widać, wszystkie ruchy są usuwane z wyjątkiem pierwszego. Dlaczego kompilator nie może wykonać RVO z klasą MoveableClass (std :: vector) w linii 86, ale czy może za pomocą klasy MoveableClass (int) w linii 97? Czy jest to po prostu błąd w MSVC, czy też jest ku temu dobry powód? A jeśli jest jakiś dobry powód, dlaczego nadal może wykonywać NRVO na MoveableClass (std :: vector) w linii 91?

Chciałbym to zrozumieć, więc mogę iść spać szczęśliwy. :)

+1

Bardzo dobre pytanie. Dla tego, co jest warte, g ++ 4.3.3 usuwa wszystkie te ruchy, nawet z flagą '-O0'. – Thomas

+0

Dzięki Thomas. To ciekawe, że działa na GCC. Może to sugeruje, że coś jest nie tak z implementacją MSVC. – dvide

+1

Myślę, że to tylko podkreśla, jak ogromna jest przepaść pomiędzy "kompilatorem" i "kompilatorem". – Crashworks

Odpowiedz

1

Hmm.

Wydaje się, że jeśli zmienisz konstruktora danych w

MoveableClass::MoveableClass(std::vector<double> data) 

przyjąć wektor przez odniesienie, jak tak,

MoveableClass::MoveableClass(const std::vector<double>& data) 

działa dobrze! Dlaczego nie działa, jeśli przekazujesz wektor według wartości?

Również tutaj jest wersja, która powinna się kompilować we wcześniejszych wersjach MSVC, jeśli ktoś chce uruchomić tam test. Nie zawiera żadnych funkcji C++ 0x: http://pastebin.com/f3bcb6ed1

0

Może warto byłoby zaktualizować i konserwować wersję this example z cpp-next wersją testu, która się nie powiedzie, dlatego może istnieć jeden kompleksowy test kanoniczny.

2

Dzięki za odpowiedź Dave'a.

Dodałem moich testów do tego przykładu:
pastebin.com/f7c8ca0d6

Co ciekawe to pokazuje, że wszystkie rodzaje elisions nie są wykonywane poza NRVO!
Edycja: Właściwie to przypuszczam, że to dlatego, że jest to jedyny test, w którym obiekt kiedykolwiek ma nazwę.

Próbowałem również innych typów STL i otrzymałem taki sam wynik. Jednak podczas próby moich własnych typów nie-pod działa zgodnie z oczekiwaniami. Nie mogę myśleć, co jest specjalnego w przypadku typów STL, które mogą być przyczyną tego, więc nie wiem, co jeszcze spróbować.

Prześlę raport o błędzie.
Edit: Submitted here

Dzięki