Perché nessun mossa-assegnazione / costruttore di mosse predefinito?


89

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

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

Dato che sono un semplice programmatore, mi piacerebbe sfruttare le capacità di spostamento senza aggiungere un costruttore di spostamento / operatore di assegnazione a ogni classe che scrivo, poiché il compilatore potrebbe semplicemente implementarle come " this->member1_ = std::move(other.member1_);..."

Ma non è così (almeno non in Visual 2010), c'è una ragione particolare per questo?

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

Aggiornamento: se guardi la risposta di GManNickG, ti fornisce un'ottima macro per questo. E se non lo sapevi, se implementi la semantica di spostamento puoi rimuovere la funzione membro di scambio.


5
sai che puoi fare in modo che il compilatore generi un ctor di spostamento predefinito
aaronman

3
std :: move non esegue una mossa, si limita a lanciare da un valore l a un valore r. Lo spostamento viene comunque eseguito dal costruttore di spostamenti.
Owen Delahoy

1
Stai parlando MyClass::MyClass(Myclass &&) = default;?
Sandburg

Sì, al giorno d'oggi :)
Viktor Sehr

Risposte:


76

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

Per ulteriori informazioni sulla storia del problema, vedere l'elenco dei documenti del WG21 del 2010 e cercare "mov"

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

Se la definizione di una classe Xnon dichiara esplicitamente un costruttore di mosse, uno sarà implicitamente dichiarato come predefinito se e solo se

  • X non dispone di un costruttore di copie dichiarato dall'utente e

  • X non dispone di un operatore di assegnazione della copia dichiarato dall'utente,

  • X non dispone di un operatore di assegnazione di spostamento dichiarato dall'utente,

  • X non dispone di un distruttore dichiarato dall'utente e

  • il costruttore di spostamento non verrebbe implicitamente definito come cancellato.

Esiste un linguaggio simile in 12.8 / 22 che specifica quando l'operatore di assegnazione dello spostamento viene dichiarato implicitamente come predefinito. Puoi trovare l'elenco completo delle modifiche apportate per supportare l'attuale specifica della generazione di mosse implicite in N3203: Rafforzare le condizioni per la generazione di mosse implicite , che si basava in gran parte su una delle risoluzioni proposte dal documento di Bjarne Stroustrup N3201: Moving right along .


4
Ho scritto un piccolo articolo con alcuni diagrammi che descrivono le relazioni per il costruttore / incarico implicito (spostamento) qui: mmocny.wordpress.com/2010/12/09/implicit-move-wont-go
mmocny

Uffa, quindi ogni volta che devo definire distruttori vuoti in classi base polimorfiche solo per il gusto di specificarlo come virtuale, devo definire esplicitamente anche il costruttore di spostamento e l'operatore di assegnazione :(.
someguy

@ James McNellis: Questo è qualcosa che ho provato in precedenza, ma al compilatore non sembrava piacere. Stavo per pubblicare il messaggio di errore proprio in questa risposta, ma dopo aver provato a riprodurre l'errore, mi sono reso conto che lo menziona cannot be defaulted *in the class body*. Quindi, ho definito il distruttore all'esterno e ha funzionato :). Lo trovo un po 'strano, però. Qualcuno ha una spiegazione? Il compilatore è gcc 4.6.1
someguy

3
Forse potremmo ottenere un aggiornamento a questa risposta ora che C ++ 11 è stato ratificato? Curioso di quali comportamenti hanno vinto.
Joseph Garvin

2
@Guy Avraham: Penso che quello che stavo dicendo (sono passati 7 anni) è che se ho un distruttore dichiarato dall'utente (anche uno virtuale vuoto), nessun costruttore di mosse verrà dichiarato implicitamente come predefinito. Suppongo che ciò comporterebbe la semantica della copia? (Non ho toccato C ++ da anni.) James McNellis ha poi commentato che virtual ~D() = default;dovrebbe funzionare e consentire ancora un costruttore di mosse implicito.
someguy

13

I costruttori di mosse generati implicitamente 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 spostamento e operatori di assegnazione di spostamento, sebbene con un elenco abbastanza sostanziale di limitazioni:

Se la definizione di una classe X non dichiara esplicitamente un costruttore di spostamento, 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 di copia dichiarato dall'utente ,
- X non ha un operatore di assegnazione di spostamento dichiarato dall'utente,
- X non ha un distruttore dichiarato dall'utente e
- il costruttore di spostamento non sarebbe implicitamente definito come cancellato.

Tuttavia, non è tutto quello che c'è nella storia. Un ctor può essere dichiarato, ma comunque definito come cancellato:

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

- un membro variante con un costruttore corrispondente non banale e X è una classe simile a un'unione,
- un membro dati non statico di tipo di classe M (o array dello stesso) che non può essere copiato / spostato a causa della risoluzione del sovraccarico (13.3), come applicato al costruttore corrispondente di M, risulta in un'ambiguità o una funzione che è cancellata o inaccessibile dal costruttore predefinito,
- una classe di base B diretta o virtuale che non può essere copiata / spostata a causa della risoluzione del sovraccarico (13.3), come applicata al costruttore corrispondente di B , si traduce in un'ambiguità o in una funzione che viene eliminata o inaccessibile dal costruttore predefinito,
- qualsiasi classe di base diretta o virtuale o membro di dati non statici di un tipo con un distruttore che viene eliminato o inaccessibile dal costruttore predefinito,
- per il costruttore di copia, un membro dati non statico di tipo riferimento rvalue, o
- per il costruttore di spostamento, un membro dati non statico o una classe di base diretta o virtuale con un tipo che non ha un costruttore di spostamento e non è banalmente copiabile.


L'attuale bozza di lavoro consente la generazione di mosse implicite in determinate condizioni e penso che la risoluzione affronti ampiamente le preoccupazioni di Abrahams.
James McNellis

Non sono sicuro di aver capito quale mossa potrebbe interrompersi nell'esempio tra Tweak 2 e Tweak 3. Potrebbe spiegarlo?
Matthieu M.

@ Matthieu M .: sia Tweak 2 che Tweak 3 sono rotti, e in modi abbastanza simili, davvero. In Tweak 2, ci sono membri privati ​​con invarianti che possono essere interrotti dal ctor di movimento. In Tweak 3, la classe non ha soci privati si , ma dal momento che utilizza l'ereditarietà privata, il pubblico e membri protetti della base diventano membri privati del derivato, che porta allo stesso problema.
Jerry Coffin

1
Non capivo davvero come il costruttore di mosse avrebbe interrotto l'invariante di classe Tweak2. Suppongo che abbia qualcosa a che fare con il fatto che Numbersarebbe stato spostato e vectorsarebbe stato copiato ... ma non sono sicuro: / Capisco che il problema si verificherebbe a cascata Tweak3.
Matthieu M.

Il collegamento che hai dato sembra essere morto?
Wolf

8

(per ora sto lavorando a una stupida macro ...)

Sì, anch'io ho seguito quella strada. Ecco la tua macro:

// 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

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

Specifichi le basi e / oi membri nella tua classe come un elenco di preprocessori, 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 ne esce un costruttore di mosse e un operatore di assegnazione di mosse.

(Per inciso, se qualcuno sa come posso combinare i dettagli in una macro, sarebbe fantastico.)


Grazie mille, il mio è abbastanza simile, tranne per il fatto che ho dovuto passare il numero di variabili membro come argomento (che fa davvero schifo).
Viktor Sehr

1
@Viktor: nessun problema. Se non è troppo tardi, penso che dovresti contrassegnare una delle altre risposte come accettata. Il mio era più un "a proposito, ecco un modo" e non una risposta alla tua vera domanda.
GManNickG

1
Se sto leggendo la tua macro correttamente, non appena il tuo compilatore implementa i membri di spostamento predefiniti, i tuoi esempi sopra diventeranno non copiabili. La generazione implicita di membri di copia è inibita quando sono presenti membri di spostamento dichiarati esplicitamente.
Howard Hinnant

@ Howard: Va bene, è una soluzione temporanea fino ad allora. :)
GManNickG

GMan: Questa macro aggiunge moveconstructor \ assign se hai una funzione di scambio:
Viktor Sehr

4

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.