2012-05-06 15 views
7

consideri queste classi:C++ 11 parametro ottimale passando

#include <iostream> 
#include <string> 

class A 
{ 
    std::string test; 
public: 
    A (std::string t) : test(std::move(t)) {} 
    A (const A & other) { *this = other; } 
    A (A && other) { *this = std::move(other); } 

    A & operator = (const A & other) 
    { 
     std::cerr<<"copying A"<<std::endl; 
     test = other.test; 
     return *this; 
    } 

    A & operator = (A && other) 
    { 
     std::cerr<<"move A"<<std::endl; 
     test = other.test; 
     return *this; 
    } 
}; 

class B 
{ 
    A a; 
public: 
    B (A && a) : a(std::move(a)) {} 
    B (A const & a) : a(a) {} 
}; 

Quando si crea una B, ho sempre un percorso in avanti ottimale per A, una mossa per rvalues ​​o una copia per lvalue.

È possibile ottenere lo stesso risultato con un costruttore? Non è un grosso problema in questo caso, ma per quanto riguarda i parametri multipli? Avrei bisogno di combinazioni di ogni possibile occorrenza di lvalue e rvalues ​​nella lista dei parametri.

Questo non è limitato ai costruttori, ma si applica anche ai parametri di funzione (ad es. Setter).

Nota: questa domanda è strettamente relativa a class B; class A esiste solo per visualizzare come vengono eseguite le chiamate di copia/spostamento.

+0

Si consiglia di leggere questi: http://stackoverflow.com/questions/8472208/under-what-conditions-should-i-be-thinking-about-implementing-a-move-constructor e http: // stackoverflow. it/questions/4782757/rule-of-three-gets-rule-of-five-with-c11 –

+0

@JamesCuster: volevo solo verificare quante volte sono stati chiamati i rispettivi costruttori/operatori. – fscan

risposta

9

L'approccio "in base al valore" è un'opzione. Non è così ottimale come quello che hai, ma richiede solo un sovraccarico:

class B 
{ 
    A a; 
public: 
    B (A _a) : a(move(_a)) {} 
}; 

Il costo è di 1 costruzione movimento extra per entrambi i lvalue e XValues, ma questo è ancora ottimale per prvalues ​​(1 MOVE). Un "xvalue" è un lvalue che è stato lanciato per rvalore usando std :: move.

si potrebbe anche provare una soluzione "inoltro perfetta":

class B 
{ 
    A a; 
public: 
    template <class T, 
       class = typename std::enable_if 
       < 
       std::is_constructible<A, T>::value 
       >::type> 
    B (T&& _a) : a(std::forward<T>(_a)) {} 
}; 

Questo ti porterà di nuovo al numero ottimale di costruzioni di copia/rinomina. Ma dovresti limitare il costruttore del modello in modo che non sia troppo generico. Potresti preferire usare is_convertible anziché is_constructible come ho fatto sopra. Questa è anche una singola soluzione di costruzione, ma con l'aggiunta di parametri, il vincolo diventa sempre più complicato.

Nota: Il motivo che il vincolo è necessaria sopra è perché senza, i clienti di B otterrà la risposta sbagliata quando esse richiedono std::is_constructible<B, their_type>::value. Risponderà erroneamente vero senza un vincolo appropriato su B.

Direi che nessuna di queste soluzioni è sempre migliore delle altre. Ci sono dei compromessi di ingegneria da fare qui.

+0

per valore-by: nel mio test ho avuto anche una mossa extra per i rvalues ​​reali (vs2010). – fscan

+0

Interessante. Grazie per questa informazione. Non ho accesso a vs2010. –

+0

Ok, questo è bello, ma rende il codice illeggibile se applicato a ogni costruttore/setter. C'è un caso che non vorrei questo comportamento da un costruttore/setter. In caso contrario, il compilatore non può eseguire questa operazione per impostazione predefinita se dichiaro per valore? (anche convertire automaticamente in xrvalue se lvalue non viene mai più utilizzato, come in questo costruttore) – fscan

2

utilizzare un tipo di parametro dedotto per il costruttore per B:

template <typename T> explicit B(T && x) : a(std::forward<T>(x) { } 

Questo funziona per qualsiasi argomento da cui un oggetto A è costruibile.

Se A dispone di più costruttori con un numero variabile di argomenti, è possibile rendere il tutto vario variadic aggiungendo ... ovunque.

Come dice @Howard, tuttavia, è necessario aggiungere un vincolo in modo che la classe non sia costruibile da argomenti da cui in realtà non lo è.

1

Se il campione 10 nel campione è std::string, semplicemente non importa: la copia e le chiamate predefinite forniscono i rispettivi membri.E std::string ha copia e sposta entrambi implementati, in modo che i temporanei vengano spostati, le variabili vengano copiate.

Non è necessario definire copia e spostamento specifici e assegnare. Si può solo lasciare con il costruttore

A::A(string s) :test(std::move(s)) {} 

In generale un'implementazione semplice di copia e spostamento può essere la seguente

class A 
{ 
public: 
    A() :p() {} 

    A(const A& a) :p(new data(*a.p)) {} //copy 
    A(A&& a) :p(a.p) { a.p=0; }   //move 

    A& operator=(A a) //note: pass by value 
    { clear(); swap(a); return *this; } 
    ~A() { clear(); } 

    void swap(A& a) { std::swap(p,a.p); } 
    void clear() { delete p; p=0; } 

private: 

    data* p; 
}; 

Il operator= assume un valore che viene spostato internamente. Se proviene da un temporaneo viene spostato, se viene da una variabile viene copiato. La differenza tra la copia e lo spostamento richiede costruttori distinti ma, se deriviamo A come

class B: public A 
{ 
... 
}; 

non v'è alcuna necessità di sovrascrivere qualsiasi cosa, dal momento che il copia-ctor predefinito per B chiama la copia per A, e la mossa di default per B chiama la mossa per A, e tutti gli operatori di assegnazione predefiniti per B chiamano l'unico definito per A (che si muove o copia a seconda di ciò che è stato inoltrato).