Domanda

Come spiegato in Questa risposta , l'Idioma copy-and-swap è implementato come segue:

class MyClass
{
private:
    BigClass data;
    UnmovableClass *dataPtr;

public:
    MyClass()
      : data(), dataPtr(new UnmovableClass) { }
    MyClass(const MyClass& other)
      : data(other.data), dataPtr(new UnmovableClass(*other.dataPtr)) { }
    MyClass(MyClass&& other)
      : data(std::move(other.data)), dataPtr(other.dataPtr)
    { other.dataPtr= nullptr; }

    ~MyClass() { delete dataPtr; }

    friend void swap(MyClass& first, MyClass& second)
    {
        using std::swap;
        swap(first.data, other.data);
        swap(first.dataPtr, other.dataPtr);
    }

    MyClass& operator=(MyClass other)
    {
        swap(*this, other);
        return *this;
    }
};
.

Avere un valore di myclass come parametro per operatore=, il parametro può essere costruito dal costruttore di copia o dal costruttore Sposta. È quindi possibile estrarre in modo sicuro i dati dal parametro. Ciò impedisce la duplicazione del codice e gli assist in termini di sicurezza.

La risposta menziona che puoi scambiare o spostare le variabili nel temporaneo. Discute principalmente dello scambio. Tuttavia, uno scambio, se non ottimizzato dal compilatore, coinvolge tre operazioni di spostamento, e in casi più complessi fa ulteriori lavori extra. Quando tutto ciò che desideri, è quello di muovi il temporaneo nell'oggetto assegnato all'oggetto.

Considera questo esempio più complesso, coinvolgendo il Pattern Observer . In questo esempio, ho scritto manualmente il codice dell'operatore di assegnazione. L'enfasi è sul costruttore Sposta, l'operatore di assegnazione e il metodo di scambio:

class MyClass : Observable::IObserver
{
private:
    std::shared_ptr<Observable> observable;

public:
    MyClass(std::shared_ptr<Observable> observable) : observable(observable){ observable->registerObserver(*this); }
    MyClass(const MyClass& other) : observable(other.observable) { observable.registerObserver(*this); }
    ~MyClass() { if(observable != nullptr) { observable->unregisterObserver(*this); }}

    MyClass(MyClass&& other) : observable(std::move(other.observable))
    {
        observable->unregisterObserver(other);
        other.observable.reset(nullptr);
        observable->registerObserver(*this);
    }

    friend void swap(MyClass& first, MyClass& second)
    {
        //Checks for nullptr and same observable omitted
            using std::swap;
            swap(first.observable, second.observable);

            second.observable->unregisterObserver(first);
            first.observable->registerObserver(first);
            first.observable->unregisterObserver(second);
            second.observable->registerObserver(second);
    }

    MyClass& operator=(MyClass other)
    {
        observable->unregisterObserver(*this);
        observable = std::move(other.observable);

        observable->unregisterObserver(other);
        other.observable.reset(nullptr);
        observable->registerObserver(*this);
    }
}
.

Chiaramente, la parte duplicata del codice in questo operatore di assegnazione scritta manualmente è identica a quella del costruttore Sposta. È possibile eseguire uno swap nell'operatore di assegnazione e il comportamento sarebbe giusto, ma avrebbe potenzialmente più mosse ed eseguire una registrazione extra (nello swap) e la non registrazione (nel distruttore).

Non farebbe molto più senso riportare il codice del costruttore della mossa in Stead?

private:
    void performMoveActions(MyClass&& other)
    {
        observable->unregisterObserver(other);
        other.observable.reset(nullptr);
        observable->registerObserver(*this);
    }

public:
    MyClass(MyClass&& other) : observable(std::move(other.observable))
    {
        performMoveActions(other);
    }

    MyClass& operator=(MyClass other)
    {
        observable->unregisterObserver(*this);
        observable = std::move(other.observable);

        performMoveActions(other);
    }
.

Mi sembra che questo approccio non sia mai inferiore all'approccio di swap. Ho ragione nel pensare che l'idioma copia-e-swap sarebbe meglio fuori come idioma di copia e muoversi in C ++ 11, o mi manca qualcosa di importante?

È stato utile?

Soluzione 3

È passato molto tempo da quando ho fatto questa domanda, e ho conosciuto la risposta per un po 'ora, ma ho messo a scrivere la risposta per questo. Eccolo.

La risposta è no. L'idioma copia-and-swap non dovrebbe diventare l'idioma copia-and-spostamento.

Una parte importante della copia-e-swap (che è anche spostamento-costruct-and-swap) è un modo per implementare gli operatori di assegnazione con una pulizia sicura. I vecchi dati sono scambiati in un temporaneo provvisorio da copia costruito o mossa. Quando viene eseguita l'operazione, il temporaneo viene cancellato e il suo distruttore è chiamato.

Il comportamento dello swap è lì per essere in grado di riutilizzare il distruttore, quindi non devi scrivere alcun codice di pulizia negli operatori di assegnazione.

Se non ci sono comportamenti di pulizia da fare e solo l'assegnazione, è necessario dichiarare gli operatori di assegnazione come impostazione predefinita e copia-e-swap non è necessario.

Il costruttore Sposta di solito non richiede alcun comportamento di pulizia, dal momento che è un nuovo oggetto. L'approccio generale generale è quello di effettuare il costruttore di spostamento invocare il costruttore predefinito, quindi scambiare tutti i membri con l'oggetto Move-from. L'oggetto spostamento sarà quindi come un oggetto Bland predefinito-costruito.

Tuttavia, nell'esempio del modello dell'osservatore di questa domanda, questa è in realtà un'eccezione in cui è necessario fare un lavoro di pulizia extra perché i riferimenti al vecchio oggetto devono essere modificati. In generale, consiglierei di rendere i tuoi osservatori e gli osservabili, e altri costrutti di progettazione basati sui riferimenti, non modificabili quando possibile.

Altri suggerimenti

Dare ad ogni membro speciale La tenera cura amorevole che merita e prova a default il più possibile:

class MyClass
{
private:
    BigClass data;
    std::unique_ptr<UnmovableClass> dataPtr;

public:
    MyClass() = default;
    ~MyClass() = default;
    MyClass(const MyClass& other)
        : data(other.data)
        , dataPtr(other.dataPtr ? new UnmovableClass(*other.dataPtr)
                                : nullptr)
        { }
    MyClass& operator=(const MyClass& other)
    {
        if (this != &other)
        {
            data = other.data;
            dataPtr.reset(other.dataPtr ? new UnmovableClass(*other.dataPtr)
                                        : nullptr);
        }
        return *this;
    }
    MyClass(MyClass&&) = default;
    MyClass& operator=(MyClass&&) = default;

    friend void swap(MyClass& first, MyClass& second)
    {
        using std::swap;
        swap(first.data, second.data);
        swap(first.dataPtr, second.dataPtr);
    }
};
.

Il distruttore potrebbe essere implicitamente inadempiente sopra se lo si desidera. Tutto il resto deve essere definito esplicitamente o inadempiente per questo esempio.

Riferimento: http://accu.org/content/conf2014/howard_hinnant_accu_2014.pdf

L'idioma copia / swap probabilmente ti costerà le prestazioni (vedere le diapositive). Ad esempio, mai chiedi perché ad alte prestazioni / spesso utilizzati STD :: Tipi come std::vector e std::string non utilizzare copia / swap? Le scarse prestazioni sono la ragione. Se BigClass contiene generatori std::vectors o std::strings (che sembra probabile), la tua scommessa migliore è chiamare i loro membri speciali dai tuoi membri speciali. Quanto sopra è come farlo.

Se è necessario una forte sicurezza delle eccezioni sull'assegnazione, consultare le diapositive per come offrire che oltre alle prestazioni (ricerca di "strong_assign").

Prima di tutto, in genere non è necessario scrivere una funzione swap in C ++ 11 a condizione che la classe sia mobile.Il swap predefinito ricorrerà alle mosse:

void swap(T& left, T& right) {
    T tmp(std::move(left));
    left = std::move(right);
    right = std::move(tmp);
}
.

Ed è così, gli elementi sono scambiati.

Secondo, in base a ciò, il copy-and-swap in realtà tiene ancora:

T& T::operator=(T const& left) {
    using std::swap;
    T tmp(left);
    swap(*this, tmp);
    return *this;
}

// Let's not forget the move-assignment operator to power down the swap.
T& T::operator=(T&&) = default;
.

copia e scambierà (che è una mossa) o si muoverà e lo spostamento (che è una mossa) e dovrebbe sempre raggiungere vicino alle prestazioni ottimali.Potrebbero esserci un paio di incarichi ridondanti, ma si spera che il tuo compilatore si prenderà cura di esso.

Modifica: Questo implementa solo l'operatore di assegnazione della copia;È inoltre richiesto un operatore di assegnazione di movimento separato, sebbene possa essere predefinito, altrimenti si verificherà un overflow dello stack (spostamento-assegnazione e scambio chiamaci a reciprocamente indefinitamente).

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top