Perché nessuna commessa di spostamento / spostamento-costruttore predefinita?

Sono un programmatore semplice. Le variabili dei membri della mia class sono molto spesso composte da tipi POD e contenitori STL. Per questo motivo raramente devo scrivere gli operatori di assegnazione o copiare i costruttori, poiché questi sono implementati di default.

Aggiungete a questo, se uso std::move su oggetti non mobili, utilizza l’operatore di assegnazione, il che significa che std::move è perfettamente sicuro.

Poiché sono un programmatore semplice, vorrei sfruttare le funzionalità di spostamento senza aggiungere un operatore di costruzione / assegnazione delle mosse a ogni class che scrivo, in quanto il compilatore potrebbe semplicemente implementarle come ” this->member1_ = std::move(other.member1_);...

Ma non lo fa (almeno non in Visual 2010), c’è qualche ragione particolare per questo?

Ma ancora più importante; C’è un modo per aggirarlo?

Aggiornamento: se osservi la risposta di GManNickG, fornisce una grande macro per questo. E se non lo sapessi, se implementi la semantica del movimento puoi rimuovere la funzione membro di scambio.

La generazione implicita di costruttori di mosse e operatori di assegnazione è stata controversa e ci sono state revisioni importanti nelle bozze recenti dello standard C ++, quindi i compilatori attualmente disponibili si comportano probabilmente diversamente rispetto alla generazione implicita.

Per ulteriori informazioni sulla storia del problema, consultare l’elenco dei documenti WG21 2010 e cercare “mov”

La specifica corrente (N3225, da novembre) afferma (N3225 12.8 / 8):

Se la definizione di una class X non dichiara esplicitamente un costruttore di movimento, uno sarà implicitamente dichiarato come predefinito se e solo se

  • X non ha un costruttore di copia dichiarato dall’utente e

  • X non ha un operatore di assegnazione copia dichiarato dall’utente,

  • X non ha un operatore di assegnazione del movimento dichiarato dall’utente,

  • X non ha un distruttore dichiarato dall’utente, e

  • il costruttore di movimento non sarebbe definito implicitamente come eliminato.

C’è un linguaggio simile in 12.8 / 22 che specifica quando l’operatore di assegnazione del movimento è implicitamente dichiarato come predefinito. È ansible trovare l’elenco completo delle modifiche apportate per supportare le specifiche correnti della generazione di spostamenti impliciti in N3203: Rafforzamento delle condizioni per la generazione di mosse implicite , che si basava in gran parte su una delle risoluzioni proposte dal documento N3201 di Bjarne Stroustrup : Spostamento lungo .

Costruttori di movimenti generati in modo implicito sono stati considerati per lo standard, ma possono essere pericolosi. Vedi l’ analisi di Dave Abrahams.

Alla fine, tuttavia, lo standard includeva la generazione implicita di costruttori di mosse e gli operatori di spostamento delle mosse, sebbene con una lista abbastanza ampia di limitazioni:

Se la definizione di una class X non dichiara esplicitamente un costruttore di movimento, uno sarà implicitamente dichiarato come predefinito se e solo se
– X non ha un costruttore di copia dichiarato dall’utente,
– X non ha un operatore di assegnazione copia dichiarato dall’utente,
– X non ha un operatore di assegnazione del movimento dichiarato dall’utente,
– X non ha un distruttore dichiarato dall’utente, e
– il costruttore di movimento non sarebbe definito implicitamente come cancellato.

Non è tutto quello che c’è nella storia però. Un ctor può essere dichiarato, ma definito come cancellato:

Un costruttore di copia / spostamento implicitamente dichiarato è un membro pubblico in linea della sua class. Un costruttore di copia / spostamento predefinito per una class X è definito come cancellato (8.4.3) se X ha:

– un membro variante con un costruttore corrispondente non banale e X è una class tipo unione,
– un membro di dati non statici di class tipo M (o sua matrice) che non può essere copiato / spostato perché la risoluzione di sovraccarico (13.3), applicata al costruttore di M corrispondente, produce un’ambiguità o una funzione che è cancellata o inaccessibile dal costruttore predefinito,
– una class di base B diretta o virtuale che non può essere copiata / spostata perché la risoluzione di sovraccarico (13.3), applicata al costruttore di B corrispondente, genera un’ambiguità o una funzione che è cancellata o inaccessibile dal costruttore predefinito,
– qualsiasi class di dati diretta o virtuale di base o membro di dati non statici di un tipo con un distruttore che viene eliminato o inaccessibile dal costruttore predefinito,
– per il costruttore di copie, un membro di dati non statici di tipo di riferimento rvalue o
– per il costruttore di movimento, un membro di dati non statici o una class di base diretta o virtuale con un tipo che non ha un costruttore di movimento e non è banalmente copiabile.

(per ora, sto lavorando a una stupida macro …)

Sì, ho seguito anche quella strada. Ecco la tua macro:

 // detail/move_default.hpp #ifndef UTILITY_DETAIL_MOVE_DEFAULT_HPP #define UTILITY_DETAIL_MOVE_DEFAULT_HPP #include  #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 

(Ho rimosso i commenti reali, che sono lunghi e documentari.)

Si specificano le basi e / oi membri della class come un elenco di preprocessore, ad esempio:

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

E viene fuori un costruttore di mosse e un operatore di assegnazione del movimento.

(Per inciso, se qualcuno sa come combinare i dettagli in un’unica macro, sarebbe bello.)

VS2010 non lo fa perché non erano standard al momento dell’implementazione.