Costruttore di copia di classe derivata in C ++

2

Se ho una classe astratta Drink che ha una classe derivata Coffee , come sarebbe il costruttore di copie per Coffee? Se accetta un riferimento di Type Coffee come parametro, non sarei in grado di passare un'istanza che è stata istanziata in questo modo: Drink* coffee = new Coffee(..); Ma se accetto riferimenti a Drink allora non avrei mezzi di sapere se Drink è effettivamente di tipo Coffee o Tea per esempio.

    
posta FoxTrod 05.12.2016 - 20:17
fonte

3 risposte

4

Il costruttore di copie - per definizione - prende sempre un riferimento allo stesso identico tipo per cui è stato dichiarato. Pertanto la firma del costruttore di copie per Coffee sarebbe Coffee(const Coffee&) . Qualsiasi altro costruttore non è un costruttore di copie.

Il costruttore di copie può essere usato come un normale ctor, ma è usato implicitamente dal linguaggio per l'assegnazione della copia Coffe c = other_coffee , che è quindi equivalente a Coffee c(other_coffee) e quando si passa l'oggetto per valore a una funzione, ad es. drink(Coffee) richiede una copia.

Quando hai un Drink che può essere un Coffee o qualche altra sottoclasse Drink , significa necessariamente che hai un puntatore o un riferimento a Drink . In tal caso, di solito non è necessario creare una copia del valore puntato: puoi semplicemente copiare il puntatore.

Se in effetti hai bisogno di copiare un Drink , dovrai creare un metodo virtuale in modo che ogni tipo di Drink possa clonare se stesso:

class Drink {
public:
  virtual std::unique_ptr<Drink> clone() const = 0;
  ...
};

class Coffee : public Drink {
  ...
  Coffee(Coffee const&);
  std::unique_ptr<Drink> clone() const override;
};

std::unique_ptr<Drink> Coffee::clone() const {
  return std::make_unique<Coffee>(*this);  // requires C++ 14
}

Quindi: std::unique_ptr<Drink const> a = ...; std::unique_ptr<Drink const> b = a->clone() . Ovviamente puoi anche usare i puntatori grezzi, se ti piace questo genere di cose.

    
risposta data 05.12.2016 - 21:24
fonte
2

But if I accept references to Drink then I wouldn't have means of knowing if the Drink is indeed of type Coffee or Tea for example

Non puoi copiare costruire una sottoclasse ( Coffee ) da tutti gli oggetti arbitrari che sub-classi da super-classe Drink . Quindi, la risposta deve essere che devi limitare il costruttore della copia ad accettare la stessa classe ( Coffee ) per il costruttore di copie.

Hai ragione nel notare che non sarai in grado di invocare il costruttore di copia desiderato da Drink *coffee = new Coffee(...); . Tuttavia, se fai Coffee coffee(...); , sarai in grado di utilizzare il costruttore di copie.

Uno degli usi principali del costruttore di copie è che il compilatore lo chiama automaticamente quando passa oggetti (valori oggetto, non puntatori o riferimenti) alle funzioni e le restituisce. In quanto tale, il costruttore di copie serve per copiare valori, e quei valori che si presume abbiano uno specifico tipo noto in fase di compilazione .

Quindi stai pensando di usare la copia per valore con gli oggetti per mezzo di puntatori e sottoclassi / polimorfismo, che è un territorio piuttosto strano. Il meccanismo della copia per valore presuppone che tu sappia, in fase di compilazione, il tipo desiderato (e la dimensione da allocare) della copia dell'entità risultante.

Ho il sospetto che invece tu voglia clonare il tipo dinamico o runtime dell'oggetto, non il tipo statico o in fase di compilazione dell'oggetto. Per clonare il tipo dinamico dell'oggetto è necessario introdurre un metodo di clonazione virtuale. Tuttavia, puoi invocare il metodo clone virtuale dal tuo costruttore di copie, che potrebbe fornirti ciò che desideri.

Puoi cercare il modello "costruttore di copia virtuale".

risposta data 05.12.2016 - 21:23
fonte
0

Le funzioni di conversione sono la risposta a questo

Poiché il copyctor accetta sempre il proprio tipo come argomento, non c'è spazio per accettare un Drink nel copy-ctor di Coffee .

class Coffee: public Drink
{
    ...
public:
    Coffee(const Coffee& other);
    ...
};

Se vuoi ancora inizializzare un caffè da un drink (possibilmente caffè), puoi utilizzare un operatore di conversione. Nota che vuoi decidere cosa fare se Drink NON è un Coffee . Ho inserito nel codice sotto un'eccezione in modo che se i dati di origine non sono un Drink , non voglio procedere con la creazione di un oggetto Coffee .

class Coffee: public Drink
{
    ...
public:
    //Standard copy-ctor
    Coffee(const Coffee& other);

    //Conversion function
    Coffee(const Drink& drink)
    {
        const Coffee& other = dynamic_cast<const Coffee&>(drink);
        //this->x = other.x;
        //this->y = other.y;
    }
};

In questo modo, se il riferimento passato è di un tipo diverso (ad esempio Drink o Tea), viene generata un'eccezione std::bad_cast e la creazione dell'oggetto viene interrotta.

. . .

//d1: drink reference pointing to object of type 'Drink' OR 'Tea' OR 'Coffee'
try
{
    Coffee c1(d1);
    cout << "Created coffee from drink" << endl;
}
catch(bad_cast& ex)
{
    cout << "Exception : " << ex.what() << endl;
}

Il codice sopra funzionerà correttamente per Coffee oggetto e genererà eccezioni per qualsiasi altro tipo.

Se desideri lavorare diversamente, puoi gestire l'eccezione nel ctor e andare da lì. Si noti che la gestione delle eccezioni nel ctor è generalmente disapprovata, a ragione.

NOTA CHE dynamic_cast funzionerà per le gerarchie polimorfiche, ad esempio con almeno 1 funzione virtuale nella classe base.

. . .

    
risposta data 14.12.2016 - 14:24
fonte

Leggi altre domande sui tag