Credo che tu abbia l'osservazione corretta ma l'interpretazione sbagliata!
La copia non verrà restituita restituendo il valore, poiché in questo caso ogni normale compilatore intelligente utilizzerà (N) RVO . Da C ++ 17 questo è obbligatorio, quindi non puoi vedere alcuna copia restituendo un vettore generato locale dalla funzione.
OK, giochiamo un po 'con std::vector
e cosa accadrà durante la costruzione o riempiendolo passo dopo passo.
Prima di tutto, generiamo un tipo di dati che rende ogni copia o spostamento visibile come questo:
template <typename DATA >
struct VisibleCopy
{
private:
DATA data;
public:
VisibleCopy( const DATA& data_ ): data{ data_ }
{
std::cout << "Construct " << data << std::endl;
}
VisibleCopy( const VisibleCopy& other ): data{ other.data }
{
std::cout << "Copy " << data << std::endl;
}
VisibleCopy( VisibleCopy&& other ) noexcept : data{ std::move(other.data) }
{
std::cout << "Move " << data << std::endl;
}
VisibleCopy& operator=( const VisibleCopy& other )
{
data = other.data;
std::cout << "copy assign " << data << std::endl;
}
VisibleCopy& operator=( VisibleCopy&& other ) noexcept
{
data = std::move( other.data );
std::cout << "move assign " << data << std::endl;
}
DATA Get() const { return data; }
};
E ora iniziamo alcuni esperimenti:
using T = std::vector< VisibleCopy<int> >;
T Get1()
{
std::cout << "Start init" << std::endl;
std::vector< VisibleCopy<int> > vec{ 1,2,3,4 };
std::cout << "End init" << std::endl;
return vec;
}
T Get2()
{
std::cout << "Start init" << std::endl;
std::vector< VisibleCopy<int> > vec(4,0);
std::cout << "End init" << std::endl;
return vec;
}
T Get3()
{
std::cout << "Start init" << std::endl;
std::vector< VisibleCopy<int> > vec;
vec.emplace_back(1);
vec.emplace_back(2);
vec.emplace_back(3);
vec.emplace_back(4);
std::cout << "End init" << std::endl;
return vec;
}
T Get4()
{
std::cout << "Start init" << std::endl;
std::vector< VisibleCopy<int> > vec;
vec.reserve(4);
vec.emplace_back(1);
vec.emplace_back(2);
vec.emplace_back(3);
vec.emplace_back(4);
std::cout << "End init" << std::endl;
return vec;
}
int main()
{
auto vec1 = Get1();
auto vec2 = Get2();
auto vec3 = Get3();
auto vec4 = Get4();
// All data as expected? Lets check:
for ( auto& el: vec1 ) { std::cout << el.Get() << std::endl; }
for ( auto& el: vec2 ) { std::cout << el.Get() << std::endl; }
for ( auto& el: vec3 ) { std::cout << el.Get() << std::endl; }
for ( auto& el: vec4 ) { std::cout << el.Get() << std::endl; }
}
Cosa possiamo osservare:
Esempio 1) Creiamo un vettore da un elenco di inizializzatori e forse ci aspettiamo di vedere 4 volte il costrutto e 4 mosse. Ma ne otteniamo 4 copie! Sembra un po 'misterioso, ma il motivo è l'implementazione dell'elenco di inizializzatori! Semplicemente non è consentito spostarsi dall'elenco poiché l'iteratore dall'elenco è un const T*
elemento che rende impossibile spostare elementi da esso. Una risposta dettagliata su questo argomento è disponibile qui: initializer_list e sposta la semantica
Esempio 2) In questo caso, otteniamo una costruzione iniziale e 4 copie del valore. Non è niente di speciale ed è ciò che possiamo aspettarci.
Esempio 3) Anche qui, abbiamo la costruzione e alcune mosse come previsto. Con la mia implementazione stl il vettore cresce ogni volta di fattore 2. Quindi vediamo un primo costrutto, un altro e poiché il vettore si ridimensiona da 1 a 2, vediamo lo spostamento del primo elemento. Aggiungendo il 3, vediamo un ridimensionamento da 2 a 4 che richiede uno spostamento dei primi due elementi. Tutto come previsto!
Esempio 4) Ora riserviamo spazio e riempiamo in seguito. Ora non abbiamo più copia e nessuna mossa!
In tutti i casi, non vediamo alcuna mossa né copia restituendo il vettore al chiamante! (N) RVO è in corso e non sono necessarie ulteriori azioni in questo passaggio!
Torna alla tua domanda:
"Come trovare operazioni di copia spuria in C ++"
Come visto sopra, è possibile introdurre una classe proxy tra a scopo di debug.
Rendere privato il copy-ctor potrebbe non funzionare in molti casi, poiché potresti avere alcune copie desiderate e alcune nascoste. Come sopra, solo il codice per esempio 4 funzionerà con un copy-ctor privato! E non posso rispondere alla domanda, se l'esempio 4 è il più veloce, poiché riempiamo la pace con la pace.
Mi dispiace che non posso offrire una soluzione generale per trovare copie "indesiderate" qui. Anche se digiti il tuo codice per le chiamate di memcpy
, non troverai tutto, poiché anche memcpy
tu sarai ottimizzato e vedrai direttamente alcune istruzioni dell'assemblatore che fanno il lavoro senza una chiamata alla tua memcpy
funzione di libreria .
Il mio suggerimento non è quello di concentrarsi su un problema così piccolo. Se hai problemi di prestazioni reali, prendi un profiler e misura. Ci sono così tanti potenziali killer delle prestazioni, che investire molto tempo memcpy
sull'uso spurio non sembra un'idea così utile.
std::vector
qualsiasi mezzo non sia ciò che pretende di essere . Il tuo esempio mostra una copia esplicita ed è naturale e l'approccio corretto (di nuovo imho) applicare lastd::move
funzione come ti suggerisci se una copia non è quella che desideri. Si noti che alcuni compilatori potrebbero omettere la copia se i flag di ottimizzazione sono attivati e il vettore è invariato.