Classi base astratte e costruzione della copia, regole empiriche


10

Spesso è una buona idea avere una classe base astratta per isolare l'interfaccia dell'oggetto.

Il problema è che la costruzione della copia, IMHO, è praticamente rotta per impostazione predefinita in C ++, con i costruttori di copie generati per impostazione predefinita.

Quindi, quali sono i gotcha quando hai una classe base astratta e puntatori grezzi nelle classi derivate?

class IAbstract
{
    ~IAbstract() = 0;
}

class Derived : public IAbstract
{
    char *theProblem;
    ...
}

IAbstract *a1 = new Derived();
IAbstract a2 = *a1;//???

E ora disabiliti in modo pulito la costruzione delle copie per l'intera gerarchia? Dichiarare la costruzione della copia come privata in IAbstract?

Ci sono delle regole di tre con classi base astratte?


1
usa i riferimenti anziché i puntatori :)
tp1

@ tp1: o almeno qualche puntatore intelligente.
Benjamin Bannier,

1
A volte devi solo lavorare con il codice esistente ... Non puoi cambiare tutto in un istante.
Coder

Perché pensi che il costruttore di copie predefinito sia rotto?
BЈовић,

2
@Coder: la guida di stile di Google è un mucchio di spazzatura e fa assolutamente schifo per qualsiasi sviluppo C ++.
DeadMG

Risposte:


6

La costruzione di copie su una classe astratta dovrebbe essere resa privata nella maggior parte dei casi, così come l'operatore di assegnazione.

Le classi astratte sono, per definizione, fatte per essere un tipo polimorfico. Quindi non sai quanta memoria utilizza la tua istanza e quindi non puoi copiarla o assegnarla in modo sicuro. In pratica, rischi di tagliare: /programming/274626/what-is-the-slicing-problem-in-c

Il tipo polimorfico, in C ++, non deve essere manipolato dal valore. Li manipoli per riferimento o per puntatore (o qualsiasi puntatore intelligente).

Questo è il motivo per cui Java ha reso l'oggetto manipolabile solo come riferimento e perché C # e D hanno la separazione tra classi e strutture (il primo è polimorfico e tipo di riferimento, il secondo non polimorfico e tipo di valore).


2
Le cose in Java non sono migliori. In Java è doloroso copiare qualsiasi cosa, anche quando davvero, davvero è necessario e molto facile dimenticare di farlo. Quindi si finiscono con cattivi bug in cui due strutture di dati hanno un riferimento alla stessa classe di valore (ad es. Data), quindi una di esse modifica la Data e l'altra struttura di dati ora è rotta.
Kevin Cline,

3
Meh. È un non-problema: chiunque conosca il C ++ non commetterà questo errore. Ancora più importante, non c'è alcun motivo per manipolare i tipi polimorfici in base al valore se sai cosa stai facendo. Stai iniziando una reazione istintiva totale su una possibilità tecnicamente sottile. I puntatori NULL rappresentano un problema maggiore.
DeadMG

8

Naturalmente, potresti renderlo protetto e vuoto, in modo che le classi derivate possano scegliere. Tuttavia, più in generale, il tuo codice è proibito comunque perché è impossibile creare un'istanza IAbstract- perché ha una funzione virtuale pura. Pertanto, si tratta in genere di un problema: le tue classi di interfaccia non possono essere istanziate e quindi non possono mai essere copiate, e le tue classi più derivate possono vietare o continuare a copiare come desiderano.


E se ci fosse una gerarchia Abstract -> Derived1 -> Derived2 e Derived2 sia assegnato a Derived1? Questi angoli oscuri della lingua mi sorprendono ancora.
Coder

@Coder: di solito è visto come PEBKAC. Tuttavia, più direttamente, puoi sempre dichiarare un privato operator=(const Derived2&)in Derived1.
DeadMG

Ok, forse questo non è davvero un problema. Voglio solo assicurarmi che la classe sia al sicuro dagli abusi.
Coder

2
E per questo dovresti ottenere una medaglia.
Ingegnere mondiale

2
@Coder: abuso da parte di chi? Il meglio che puoi fare è semplificare la scrittura del codice corretto. È inutile cercare di difendersi da programmatori che non conoscono il C ++.
Kevin Cline,

2

Rendendo privato ctor e assegnazione (o dichiarandoli come = delete in C ++ 11) si disabilita la copia.

Il punto qui è DOVE devi farlo. Per rimanere con il tuo codice, IAbstract non è un problema. (nota che facendo ciò che hai fatto, assegni l' *a1 IAbstractoggetto secondario a a2, perdendo qualsiasi riferimento a Derived. L'assegnazione del valore non è polimorfica)

Il problema si presenta Derived::theproblem. Copiare un Derivato in un altro può infatti condividere i *theproblemdati che potrebbero non essere progettati per essere condivisi (ci sono due istanze che possono essere chiamate delete theproblemnel loro distruttore).

In tal caso, è Derivednecessario che non sia copiabile e non assegnabile. Naturalmente, se si rende privata la copia IAbstract, poiché la copia predefinita per Derivednecessità, Derivednon sarà neppure copiabile. Ma se definisci il tuo Derived::Derived(const Derived&)senza chiamare IAbtractcopia, puoi comunque copiarlo.

Il problema è in Derivato e la soluzione deve rimanere in Derivato: se deve essere un oggetto solo dinamico a cui si accede solo da puntatori o riferimenti, è lo stesso Derivato che deve avere

class Derived
{
    ...
    Derived(const Derived&) = delete;
    Derived& operator=(const Derived&) = delete;
};

In sostanza spetta al progettista della classe Derived (che dovrebbe sapere come funziona Derived e come theproblemviene gestito) decidere cosa fare con l'assegnazione e la copia.

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.