Come eludere la copia durante il concatenamento?


10

Sto creando una classe di tipo concatenamento, come il piccolo esempio di seguito. Sembra che quando si concatenano funzioni membro, viene invocato il costruttore di copie. C'è un modo per sbarazzarsi della chiamata del costruttore della copia? Nel mio esempio di giocattolo qui sotto, è ovvio che ho a che fare solo con i provvisori e quindi "dovrebbe" (forse non secondo gli standard, ma logicamente) essere un'elezione. La seconda scelta migliore, per copiare l'elisione, sarebbe quella di chiamare il costruttore di mosse, ma non è così.

class test_class {
    private:
    int i = 5;
    public:
    test_class(int i) : i(i) {}
    test_class(const test_class& t) {
        i = t.i;
        std::cout << "Copy constructor"<< std::endl;
    }
    test_class(test_class&& t) {
        i = t.i;
        std::cout << "Move constructor"<< std::endl;
    }
    auto& increment(){
        i++;
        return *this;
    }
};
int main()
{
    //test_class a{7};
    //does not call copy constructor
    auto b = test_class{7};
    //calls copy constructor
    auto b2 = test_class{7}.increment();
    return 0;
}

Modifica: alcuni chiarimenti. 1. Questo non dipende dal livello di ottimizzazione. 2. Nel mio codice reale, ho oggetti più complessi (ad es. Allocazione di heap) rispetto agli ints


Quale livello di ottimizzazione usi per la compilazione?
JVApen

2
auto b = test_class{7};non chiama il costruttore di copie perché è veramente equivalente test_class b{7};e i compilatori sono abbastanza intelligenti da riconoscere questo caso e possono quindi facilmente eludere qualsiasi copia. Lo stesso non si può fare per b2.
Qualche programmatore, amico,

Nell'esempio mostrato, potrebbe non esserci alcuna differenza tra mossa e copia e non tutti ne sono consapevoli. Se hai bloccato qualcosa come un grosso vettore lì dentro potrebbe essere una questione diversa. Normalmente spostare ha senso solo per i tipi che usano le risorse (come usare un sacco di mem heap, ecc.) - è così?
1919

L'esempio sembra inventato. Hai effettivamente I / O ( std::cout) nel tuo ctor copia? Senza di essa, la copia dovrebbe essere ottimizzata.
Rustyx,

@rustyx, rimuovere std :: cout e rendere esplicito il costruttore della copia. Ciò dimostra che l'eliminazione della copia non dipende dallo std :: cout.
DDaniel

Risposte:


7
  1. Risposta parziale (non costruisce b2sul posto, ma trasforma la costruzione della copia in una costruzione di spostamento): puoi sovraccaricare la incrementfunzione membro sulla categoria di valore dell'istanza associata:

    auto& increment() & {
        i++;
        return *this;
    }
    
    auto&& increment() && {
        i++;
       return std::move(*this);
    }

    Questo causa

    auto b2 = test_class{7}.increment();

    spostare-costruzione b2perché test_class{7}è temporaneo e viene chiamato il &&sovraccarico di test_class::increment.

  2. Per una vera costruzione sul posto (cioè nemmeno una costruzione di movimento), puoi trasformare tutte le funzioni speciali e non speciali in constexprversioni. Quindi puoi farlo

    constexpr auto b2 = test_class{7}.increment();

    e non devi pagare né una mossa né una costruzione di copie. Questo è, ovviamente, possibile per il semplice test_class, ma non per uno scenario più generale che non consente le constexprfunzioni membro.


La seconda alternativa rende anche b2non modificabile.
Qualche programmatore, amico,

1
@Someprogrammerdude Buon punto. Immagino che constinitsarebbe il modo di andare lì.
lubgr,

Qual è lo scopo delle e commerciali dopo il nome della funzione? Non l'ho mai visto prima.
Philip Nelson,

1
@PhilipNelson sono ref-qualificazioni e può dettare ciò che thisè proprio la stessa di constfunzioni membro
kmdreko

1

Fondamentalmente, l'assegnazione di un a un richiede l'invocazione di un costruttore, ovvero una copia o uno spostamento . Ciò è diverso della cui è noto che su entrambi i lati della funzione è lo stesso oggetto distinto. Anche un può fare riferimento a un oggetto condiviso in modo molto simile a un puntatore.


Il modo più semplice è probabilmente quello di rendere il costruttore di copie completamente ottimizzato. L'impostazione del valore è già ottimizzata dal compilatore, è solo quella std::coutche non può essere ottimizzata.

test_class(const test_class& t) = default;

(o rimuovi sia la copia che il costruttore di spostamento)

esempio dal vivo


Poiché il problema riguarda sostanzialmente il riferimento, probabilmente una soluzione non sta restituendo un riferimento all'oggetto se si desidera interrompere la copia in questo modo.

  void increment();
};

auto b = test_class{7};//does not call copy constructor
b.increment();//does not call copy constructor

Un terzo metodo si basa solo sull'eliminazione della copia in primo luogo, tuttavia ciò richiede una riscrittura o incapsulamento dell'operazione in una funzione e quindi evitare del tutto il problema (sono consapevole che questo potrebbe non essere quello che vuoi, ma potrebbe essere un soluzione per altri utenti):

auto b2 = []{test_class tmp{7}; tmp.increment().increment().increment(); return tmp;}(); //<-- b2 becomes 10 - copy constructor not called

Un quarto metodo utilizza invece una mossa, invocata in modo esplicito

auto b2 = std::move(test_class{7}.increment());

o come visto in questa risposta .


@ O'Neil stava pensando a un altro caso - ty, corretto
darune
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.