Domanda

Come ha scritto Scott Myers, puoi trarre vantaggio da un rilassamento nel sistema di tipi di C ++ per dichiarare clone () per restituire un puntatore al tipo effettivo dichiarato:

class Base
{
    virtual Base* clone() const = 0;
};

class Derived : public Base
{
    virtual Derived* clone() const
};

Il compilatore rileva che clone () restituisce un puntatore al tipo di oggetto e consente a Derived di sovrascriverlo per restituire un puntatore a derivato.

Sarebbe auspicabile che clone () restituisca un puntatore intelligente che implica il trasferimento della semantica della proprietà, come il seguente:

class Base
{
   virtual std::auto_ptr<Base> clone() const = 0;
};

class Derived : public Base
{
    virtual std::auto_ptr<Derived> clone() const;
};

Sfortunatamente, il rilassamento delle convenzioni non si applica ai puntatori intelligenti basati su modelli e il compilatore non consentirà l'override.

Quindi, mi sembra che mi restino due opzioni:

  1. Avere clone () restituisce un " dumb " puntatore e documento che i clienti sono responsabili della loro eliminazione.
  2. Chiedi a clone () di restituire un puntatore base intelligente e ai client di utilizzare dynamic_cast per salvarli su un puntatore derivato se ne hanno bisogno.

Uno di questi approcci è preferito? O c'è un modo per me di mangiare il mio trasferimento di semantica di proprietà e avere anche la mia sicurezza di tipo forte?

È stato utile?

Soluzione

Dipende dal tuo caso d'uso. Se pensi mai che dovrai chiamare clone su un oggetto derivato di cui conosci il tipo dinamico (ricorda, il punto centrale di <=> è consentire la copia di senza conoscere il tipo dinamico), quindi dovresti probabilmente restituire un puntatore stupido e caricarlo in un puntatore intelligente nel codice chiamante. In caso contrario, devi solo restituire uno smart_ptr e quindi puoi sentirti libero di restituirlo in tutte le sostituzioni.

Altri suggerimenti

Utilizza il modello virtuale pubblico non virtuale / privato:

class Base {
    public:
    std::auto_ptr<Base> clone () { return doClone(); }
    private:
    virtual Base* doClone() { return new (*this); }
};
class Derived : public Base {
    public:
    std::auto_ptr<Derived> clone () { return doClone(); }
    private:
    virtual Derived* doClone() { return new (*this); }
};

La sintassi non è altrettanto bella, ma se aggiungi questo al tuo codice sopra, non risolve tutti i tuoi problemi?

template <typename T>
std::auto_ptr<T> clone(T const* t)
{
    return t->clone();
}

Penso che la semantica della funzione sia così chiara in questo caso che c'è poco spazio per la confusione. Quindi penso che tu possa usare la versione covariante (quella che restituisce un puntatore stupido al tipo reale) con una coscienza semplice, e i tuoi chiamanti sapranno che stanno ottenendo un nuovo oggetto la cui proprietà viene trasferita su di loro.

Tr1::shared_ptr<> può essere lanciato come se fosse un puntatore non elaborato.

Penso che clone () restituisca un puntatore shared_ptr<Base> è una soluzione abbastanza pulita. Puoi lanciare il puntatore su shared_ptr<Derived> mediante tr1::static_pointer_cast<Derived> o tr1::dynamic_pointer_cast<Derived> nel caso in cui non sia possibile determinare il tipo di oggetto clonato al momento della compilazione.

Per garantire che il tipo di oggetto sia prevedibile, puoi usare un cast polimorfico per shared_ptr come questo:

template <typename R, typename T>
inline std::tr1::shared_ptr<R> polymorphic_pointer_downcast(T &p)
{
    assert( std::tr1::dynamic_pointer_cast<R>(p) );
    return std::tr1::static_pointer_cast<R>(p);
}

Il sovraccarico aggiunto dall'asserzione verrà eliminato nella versione di rilascio.

Questo è uno dei motivi per usare boost::intrusive_ptr invece di shared_ptr o auto/unique_ptr. Il puntatore non elaborato contiene il conteggio dei riferimenti e può essere utilizzato più facilmente in situazioni come questa.

Aggiornamento Risposta di MSalter per C ++ 14:

#include <memory>

class Base
{
public:
    std::unique_ptr<Base> clone() const
    {
        return do_clone();
    }
private:
    virtual std::unique_ptr<Base> do_clone() const
    {
        return std::make_unique<Base>(*this);
    }
};

class Derived : public Base
{
private:
    virtual std::unique_ptr<Base> do_clone() const override
    {
        return std::make_unique<Derived>(*this);
    }
}

Potresti avere due metodi, un clone virtuale () che restituisce un wrapper di puntatore intelligente attorno al tipo di base e un clone2 non virtuale () che restituisce il tipo corretto di puntatore intelligente.

clone2 sarebbe ovviamente implementato in termini di clone e incapsulerebbe il cast.

In questo modo è possibile ottenere il puntatore intelligente più derivato che si conosce al momento della compilazione. Potrebbe non essere il tipo più derivato in generale, ma utilizza tutte le informazioni disponibili per il compilatore.

Un'altra opzione sarebbe quella di creare una versione modello del clone che accetta il tipo che ci si aspetta, ma che aggiunge più onere al chiamante.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top