2011-01-31 13 views
17

Spesso mi imbatto nel problema, che devo estendere un costruttore di copia generato dal compilatore. Esempio:Come estendere un costruttore di copie generato dal compilatore

class xyz; 
class C 
{ 
    ... 
    int a, b, c; 
    std::set<int> mySet; 
    xyz *some_private_ptr; 
}; 

assumere, che some_private_ptr dovrebbe essere copiati solo a determinate condizioni. Per altre condizioni, è necessario reimpostare su NULL in copia. Quindi devo scrivere un costruttore di copia come:

C::C(const C &other) : 
    a(other.a), 
    b(other.b), 
    c(other.c), 
    mySet(other.mySet) 
{  
    if(CanCopy(other.some_private_ptr)) // matches condition 
     some_private_ptr = other.some_private_ptr; 
    else 
     some_private_ptr = NULL; 
} 

Il problema è che la classe potrebbe avere un certo numero di membri di dati, e che molto probabilmente può dimenticare di aggiornare il costruttore di copia quando aggiungo un membro di dati. Sarebbe molto bello se potessi scrivere.

C::C(const C &other) : 
    C::default_copy(other) 
{  
    if(CanCopy(other.some_private_ptr)) // matches condition 
     some_private_ptr = other.some_private_ptr; 
    else 
     some_private_ptr = NULL; 
} 

Ciò renderebbe il mio codice più sicuro e più facile da mantenere. Purtroppo non conosco questa possibilità. C'è qualche?

risposta

16

nel momento in cui definite il vostro copione, il compilatore non si preoccupa di generarne uno per voi. Sfortunatamente questo significa che devi fare tutto da solo! È possibile raggruppare i membri in una sorta di struttura impl_ all'interno della classe e quindi fare affidamento sul copiatore per quello.

ad esempio:

class xyz; 
class C 
{ 
    struct impl_ 
    { 
    int a, b, c; 
    std::set<int> mySet; 
    xyz *some_private_ptr; 
    }; 

    impl_ data; 
}; 

ora nella vostra copia ctor

C::C(const C &other) : data(other.data) 
{ 
// specific stuff...  
} 
+0

+1 per questa bella idea .. visto questo ho cancellato il mio post: P – Nawaz

+0

@Fred, grazie per la modifica! Avevo intenzione di dire "default copy ctor", ma mi mancava la * copia *, immagino che ora significhi la stessa cosa ... – Nim

+0

Sapevo cosa volevi dire, pensavo fosse un semplice refuso. :) "Default" è complicato perché "default ctor" ha un significato specifico, "copy ctor generato dal compilatore" o semplicemente "cctor generato" è meno ambiguo. –

18

Il modo più semplice è quello di introdurre una classe base:

class xyz; 

struct CDetail { 
    //... 
    int a, b, c; 
    std::set<int> mySet; 
    xyz *some_private_ptr; 
}; 

struct C : private CDetail { 
    C(C const &other) 
    : CDetail(other) 
    { 
    if (!CanCopy(other.some_private_ptr)) 
     some_private_ptr = 0; 
    // opposite case already handled 
    } 
}; 

Questo è un abuso di eredità una misura, ma i vantaggi rispetto a una classe "impl" annidata sono 1) è possibile accedere a ciascun membro come "nome" anziché "dat" a.name"(codice riducendo cambia quando refactoring), e 2) (anche se solo a volte lo si desidera) si può 'promuovere' i singoli membri per protetto o pubblico without affecting other members:

struct C : private CDetail { 
protected: 
    using CDetail::a; 
}; 

struct D : C { 
    void f() { 
    cout << a; 
    } 
}; 

int main() { 
    D d; 
    d.f(); // D can access 'a' 
    cout << d.a; // main cannot access 'a' 
    return 0; 
} 
+0

+1 per questa bella idea ... vedendo l'idea tua e di Nim, ho cancellato il mio post: P – Nawaz

12

Il problema qui è la classe sta cercando di fare troppo O utilizza una risorsa o gestisce una risorsa. Non lo fai mai, mai, perché il tuo codice diventerà un insicuro pasticcio. E non va bene.

È necessario progettare una classe che gestisce una risorsa che viene copiata solo in determinate condizioni. Non hai davvero ampliato su quali siano queste condizioni e perché siano lì in primo luogo (questo è un modo terribilmente strano di "copiare" i dati, sei sicuro che questa sia la via migliore?), Ma sarebbe qualcosa in questo modo:

// pointer to your condition member (not sure if this is even needed, 
// is this condition knowable via the pointer alone? you get the point) 
template <typename T, typename D, class Tag = void> 
class copy_conditional_ptr 
{ 
public: 
    copy_conditional_ptr(bool (D::*condition)(T*) const, T* value = 0) : 
    mCondition(condition), 
    mValue(value) 
    {} 

    // here's where the unique copy-semantics go 
    copy_conditional_ptr(const copy_conditional_ptr& other) : 
    mCondition(other.mCondition), 
    mValue(do_copy(other.mValue) ? other.mValue : 0) 
    {} 

    // other stuff for a smart pointer, 
    // copy-and-swap, etc... 

protected: 
    // protected because it's meant to be a base class 
    ~copy_conditional_ptr() 
    { 
     // whatever 
    } 

private: 
    bool do_copy(T* value) const 
    { 
     const D& self = static_cast<const D&>(*this); 
     return (self.*mCondition)(other.value); 
    } 

    bool (D::*mCondition)(T*) const; 
    T* mValue; 
}; 

quindi si utilizza in questo modo:

class xyz; 

class C : private copy_conditional_ptr<xyz, C> 
{ 
public: 
    C() : 
    /* others, */ 
    copy_conditional_ptr(&C::CanCopy) 
    {} 

private: 
    int a, b, c; 
    std::set<int> mySet; 
}; 

e lasciare che la gestione sia automatica per il resto della classe. Il tag è in modo da poter avere più nella stessa classe:

class C : private copy_conditional_ptr<xyz, C, struct C_first>, 
      private copy_conditional_ptr<xyz, C, struct C_second> 
{ 
    // ... 
}; 
+5

+1 avrei inverso più se possibile. questa risposta va alle cause principali invece di affrontare solo i sintomi. –

+0

@ Alf: Grazie per il sentimento. :) – GManNickG

+0

@GMan: per questo, devi definire la funzione 'CanCopy', giusto? ... anche io non capisco come sia ora la gestione automatica? puoi spiegarci un po 'di più? – Nawaz

0

direi creare un puntatore intelligente che gestisce la copia, e quindi utilizzarlo come un membro della vostra classe. Questi codici possono darvi un'idea:

A seconda di come viene avviato il costruttore di chiamata di base, i costruttori del membro verranno chiamati allo stesso modo. Per esempio, cominciamo con:

struct ABC{ 
    int a; 
    ABC() : a(0) { printf("Default Constructor Called %d\n", a); }; 

    ABC(ABC & other) 
    { 
     a=other.a; 
     printf("Copy constructor Called %d \n" , a) ; 
    }; 
}; 

struct ABCDaddy{ 
    ABC abcchild; 
}; 

si possono fare queste prove:

printf("\n\nTest two, where ABC is a member of another structure\n"); 
ABCDaddy aD; 
aD.abcchild.a=2; 

printf("\n Test: ABCDaddy bD=aD; \n"); 
ABCDaddy bD=aD; // Does call the copy constructor of the members of the structure ABCDaddy (ie. the copy constructor of ABC is called) 

printf("\n Test: ABCDaddy cD(aD); \n"); 
ABCDaddy cD(aD); // Does call the copy constructor of the members of the structure ABCDaddy (ie. the copy constructor of ABC is called) 

printf("\n Test: ABCDaddy eD; eD=aD; \n"); 
ABCDaddy eD; 
eD=aD;   // Does NOT call the copy constructor of the members of the structure ABCDaddy (ie. the copy constructor of ABC is not called) 

uscita:

Default Constructor Called 0 

Test: ABCDaddy bD=aD; 
Copy constructor Called 2 

Test: ABCDaddy cD(aD); 
Copy constructor Called 2 

Test: ABCDaddy eD; eD=aD; 
Default Constructor Called 0 

godere.

0

si potrebbe inserire il vostro membro speciale in una classe base, qualcosa di simile:

class xyz; 

class SpecialCopyXYZ 
{ 
public: 
    SpecialCopyXYZ() = default; 
    SpecialCopyXYZ(const SpecialCopyXYZ& rhs) 
    { 
     if (CanCopy(other.some_private_ptr)) { 
      some_private_ptr = other.some_private_ptr; 
     } else { 
      some_private_ptr = nullptr; 
     } 
    } 

    // SpecialCopyXYZ& operator=(const SpecialCopyXYZ& rhs) 

protected: 
    xyz *some_private_ptr = nullptr; 
}; 


class C : private SpecialCopyXYZ 
{ 
public: 
    C(const C &other) = default; 
private: 
    int a, b, c; 
    std::set<int> mySet; 
}; 

If SpecialCopyXYZ necessità C dei dati, è possibile utilizzare CRTP e abbattuto.

Problemi correlati