Dlaczego nie ma domyślnego move-assignment/move-constructor?

Jestem prostym programistą. Zmienne członków mojej klasy najczęściej składają się z POD-typów i STL-kontenerów. Z tego powodu rzadko muszę pisać operatory przypisań lub konstruktory kopiujące, ponieważ są one domyślnie zaimplementowane.

Dodaj do tego, Jeśli używam std::move na obiektach nie ruchomych, to wykorzystuje operator przypisania, co oznacza, że {[0] } jest całkowicie bezpieczny.

Ponieważ jestem prostym programistą, chciałbym skorzystać z możliwości move-a bez dodawania ruchu konstruktor/operator przypisania do każdej klasy, którą piszę, ponieważ kompilator mógł po prostu zaimplementować je jako "this->member1_ = std::move(other.member1_);..."

Ale tak nie jest (przynajmniej nie w Visual 2010), Czy Jest jakiś szczególny powód?

[[3]} ważniejsze; [12]}czy jest jakiś sposób, aby to obejść?

Update: Jeśli spojrzysz na odpowiedź Gmannickga, zapewnia on świetne makro do tego. A jeśli nie wiesz, jeśli zaimplementujesz semantykę move-semantics, możesz usunąć swap member funkcja.

Author: Viktor Sehr, 2011-01-27

4 answers

Domyślne generowanie konstruktorów ruchu i operatorów przydziałów było sporne i w ostatnich wersjach standardu C++ wprowadzono poważne zmiany, więc obecnie dostępne Kompilatory będą prawdopodobnie zachowywać się inaczej w odniesieniu do domyślnego generowania.

[[5]} aby dowiedzieć się więcej o historii wydania, zobacz listę artykułów WG21 z 2010 roku i wyszukaj "mov"

Obecna Specyfikacja (N3225, od listopada) stwierdza (N3225 12.8/8):

Jeśli definicja klasy X nie deklaruje jawnie konstruktora move, zostanie ona domyślnie zadeklarowana jako domyślna wtedy i tylko wtedy, gdy

  • X nie posiada deklarowanego przez użytkownika konstruktora kopiującego oraz

  • X nie posiada zadeklarowanego przez użytkownika operatora przypisywania kopii,

  • X nie posiada zadeklarowanego przez użytkownika operatora przypisania ruchu,

  • X nie posiada destruktora zadeklarowanego przez użytkownika oraz

  • Konstruktor move nie byłby domyślnie zdefiniowany jako usunięty.

Istnieje podobny język w 12.8/22 określający, kiedy operator przypisania move jest domyślnie zadeklarowany jako domyślny. Pełną listę zmian wprowadzonych w celu obsługi aktualnej specyfikacji generowania ruchów niejawnych można znaleźć w N3203: zaostrzenie warunków generowania ruchów niejawnych , która w dużej mierze opierała się na jednej z rezolucji zaproponowanych przez Bjarne Tekst stroustrupa N3201: Moving right along .

 73
Author: James McNellis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-22 06:12:55

Domyślnie generowane konstruktory ruchu były brane pod uwagę dla standardu, ale mogą być niebezpieczne. Zobacz analizę Dave ' a Abrahamsa .

W końcu jednak standard zawierał Ukryte generowanie konstruktorów ruchu i operatorów przydziału ruchu, choć z dość dużą listą ograniczeń:

Jeśli definicja klasy X nie deklaruje jawnie konstruktora move, zostanie ona domyślnie zadeklarowana jako defaulted wtedy i tylko wtedy, gdy
- X nie posiada deklarowanego przez użytkownika konstruktora kopiującego,
- X nie posiada zadeklarowanego przez użytkownika operatora przypisywania kopii,
- X nie posiada zadeklarowanego przez użytkownika operatora przypisania ruchu,
- X nie posiada destruktora zadeklarowanego przez użytkownika i
- konstruktor move nie będzie domyślnie zdefiniowany jako usunięty.

To jednak nie wszystko. Ctor może być zadeklarowany, ale nadal zdefiniowany jako usunięty:

Domyślnie zadeklarowana Kopia/ruch constructor jest inline publicznym członkiem swojej klasy. Domyślny konstruktor kopiowania/przenoszenia dla klasy X jest zdefiniowany jako usunięty (8.4.3), jeśli X ma:

- element wariantowy z nietrywialnym odpowiednikiem konstruktora i X jest klasą podobną do Unii,
- niestatyczny element danych klasy typu M (lub ich tablica), który nie może być kopiowany / przenoszony, ponieważ rozdzielczość przeciążenia (13.3), zastosowana do odpowiedniego konstruktora m, powoduje niejednoznaczność lub funkcję, która jest usuwana lub niedostępna od domyślnego konstruktora,
- bezpośrednia lub wirtualna klasa bazowa B, której nie można skopiować/przenieść, ponieważ rozdzielczość przeciążenia (13.3), zastosowana do odpowiedniego konstruktora B, powoduje niejednoznaczność lub funkcję, która jest usuwana lub niedostępna z domyślnego konstruktora,
- Dowolna bezpośrednia lub wirtualna klasa bazowa lub niestatyczny element danych typu z destruktorem, który jest usuwany lub Niedostępny z domyślnego konstruktora,
- dla konstruktora kopiującego dane niestatyczne członek typu referencyjnego rvalue, lub
- dla konstruktora move, niestatyczny element danych lub bezpośrednia lub wirtualna klasa bazowa z typem, który nie ma konstruktora move i nie jest trywialnie kopiowalny.

 12
Author: Jerry Coffin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-28 19:02:01
(Jak na razie pracuję nad głupim makrem...)
Tak, ja też. Oto twoje makro:
// detail/move_default.hpp
#ifndef UTILITY_DETAIL_MOVE_DEFAULT_HPP
#define UTILITY_DETAIL_MOVE_DEFAULT_HPP

#include <boost/preprocessor.hpp>

#define UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR_BASE(pR, pData, pBase) pBase(std::move(pOther))
#define UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT_BASE(pR, pData, pBase) pBase::operator=(std::move(pOther));

#define UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR(pR, pData, pMember) pMember(std::move(pOther.pMember))
#define UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT(pR, pData, pMember) pMember = std::move(pOther.pMember);

#define UTILITY_MOVE_DEFAULT_DETAIL(pT, pBases, pMembers)                                               \
        pT(pT&& pOther) :                                                                               \
        BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(                                                       \
            UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR_BASE, BOOST_PP_EMPTY, pBases))                      \
        ,                                                                                               \
        BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(                                                       \
            UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR, BOOST_PP_EMPTY, pMembers))                         \
        {}                                                                                              \
                                                                                                        \
        pT& operator=(pT&& pOther)                                                                      \
        {                                                                                               \
            BOOST_PP_SEQ_FOR_EACH(UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT_BASE, BOOST_PP_EMPTY, pBases)  \
            BOOST_PP_SEQ_FOR_EACH(UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT, BOOST_PP_EMPTY, pMembers)     \
                                                                                                        \
            return *this;                                                                               \
        }

#define UTILITY_MOVE_DEFAULT_BASES_DETAIL(pT, pBases)                                                   \
        pT(pT&& pOther) :                                                                               \
        BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(                                                       \
            UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR_BASE, BOOST_PP_EMPTY, pBases))                      \
        {}                                                                                              \
                                                                                                        \
        pT& operator=(pT&& pOther)                                                                      \
        {                                                                                               \
            BOOST_PP_SEQ_FOR_EACH(UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT_BASE, BOOST_PP_EMPTY, pBases)  \
                                                                                                        \
            return *this;                                                                               \
        }

#define UTILITY_MOVE_DEFAULT_MEMBERS_DETAIL(pT, pMembers)                                               \
        pT(pT&& pOther) :                                                                               \
        BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(                                                       \
            UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR, BOOST_PP_EMPTY, pMembers))                         \
        {}                                                                                              \
                                                                                                        \
        pT& operator=(pT&& pOther)                                                                      \
        {                                                                                               \
            BOOST_PP_SEQ_FOR_EACH(UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT, BOOST_PP_EMPTY, pMembers)     \
                                                                                                        \
            return *this;                                                                               \
        }

#endif

// move_default.hpp
#ifndef UTILITY_MOVE_DEFAULT_HPP
#define UTILITY_MOVE_DEFAULT_HPP

#include "utility/detail/move_default.hpp"

// move bases and members
#define UTILITY_MOVE_DEFAULT(pT, pBases, pMembers) UTILITY_MOVE_DEFAULT_DETAIL(pT, pBases, pMembers)

// base only version
#define UTILITY_MOVE_DEFAULT_BASES(pT, pBases) UTILITY_MOVE_DEFAULT_BASES_DETAIL(pT, pBases)

// member only version
#define UTILITY_MOVE_DEFAULT_MEMBERS(pT, pMembers) UTILITY_MOVE_DEFAULT_MEMBERS_DETAIL(pT, pMembers)

#endif

(usunąłem prawdziwe komentarze, które są długie i dokumentalne.)

Określasz bazy i/lub członków w swojej klasie jako listę preprocesorów, na przykład:

#include "move_default.hpp"

struct foo
{
    UTILITY_MOVE_DEFAULT_MEMBERS(foo, (x)(str));

    int x;
    std::string str;
};

struct bar : foo, baz
{
    UTILITY_MOVE_DEFAULT_BASES(bar, (foo)(baz));
};

struct baz : bar
{
    UTILITY_MOVE_DEFAULT(baz, (bar), (ptr));

    void* ptr;
};

I wychodzi konstruktor ruchu i operator przypisania ruchu.

(na marginesie, jeśli ktoś wie, jak Mogę połączyć szczegóły w jeden makro, byłoby super.)

 8
Author: GManNickG,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-28 08:39:32

VS2010 tego nie robi, ponieważ nie były standardowe w momencie implementacji.

 4
Author: Puppy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-27 18:25:40