2011-02-01 10 views
7

Supponiamo che io ho una classe base astratta, che appena definisce un contenitore su cui può essere eseguita Inoltre:Qual è il modo corretto per sovraccaricare gli operatori in classi base astratte?

class Base { 
public: 
    virtual ~Base() {} 
    virtual Base operator+(const Base& rhs) =0; 
}; 

poi voglio sottoclassi di base per fornire l'effettivo funzionamento:

class Derived: public Base { 
public: 
    Base operator+(const Base& rhs) { // won't compile 
     // actual implementation 
    } 
}; 

Ecco il mio problema: operator +() dovrebbe restituire un nuovo oggetto Base, ma Base essendo astratto non verrà compilato.

Ho cercato di aggirare questo usando una fabbrica per restituire un riferimento ad un oggetto Base, ma poi nel corpo dell'operatore mi trovo a fare calchi, perché l'aggiunta ha senso solo sugli oggetti derivati.

In ogni caso, mi sento come se mi stessi mordendo la coda, c'è una soluzione adeguata a questo?

AGGIORNAMENTO: Sulla base delle risposte finora, sembra che sto usando il modello sbagliato. Voglio separare l'interfaccia dall'implementazione, così che il codice della libreria deve solo conoscere l'interfaccia e il codice client fornisce l'implementazione. Ho cercato di farlo fornendo l'interfaccia come una classe base astratta e l'implementazione come sottoclassi.

UPDATE2: La mia domanda era in realtà 2 domande, una concreta (circa l'overloading degli operatori in classi astratte) e un'altra sulle mie intenzioni (come posso consentire al client di personalizzare l'implementazione). Il primo ha avuto risposta: no. Per quest'ultimo, sembra che il modello di classe di interfaccia che uso sia effettivamente valido per risolvere il problema (secondo lo Griffiths and Radford), è solo che non dovrei fare casino con gli operatori sovraccaricati.

+0

Ah, un compagno /. reader – John

risposta

8

La cosa migliore è non farlo.

operator+ restituisce un valore e non è possibile restituire un valore di un tipo astratto, per definizione. Sovraccaricare gli operatori solo per i tipi concreti ed evitare di ereditare da tipi concreti per impedire "slicing by overload operator".

Sovraccarico di operatori binari simmetrici come operator+ come funzioni libere ed è possibile controllare quali combinazioni di tipi possono essere sensibilmente combinate e impedire al contrario la combinazione di oggetti di tipi per i quali la combinazione non ha senso.

Se si dispone di un modo valido per eseguire un "add" tramite due riferimenti di classe base e la creazione di un nuovo oggetto, è necessario tornare tramite un puntatore, un riferimento o un oggetto smart con il puntatore. Poiché non puoi conservare la semantica convenzionale di +, ti consigliamo di utilizzare una funzione con nome, ad es. Add() invece di creare un operator+ con una sintassi "sorprendente".

+0

Ho aggiornato la domanda per chiarire il mio intento: l'unico caso in cui l'aggiunta ha senso è per 2 oggetti derivati, ma il codice della libreria conosce solo l'interfaccia (es. Base), non l'implementazione (es. Derivata) – AnonymousCoward

+0

@AnonymousCoward: I have leggi il tuo aggiornamento, ma non penso che ciò influisca davvero su ciò che ho detto. Se un client dell'interfaccia non ha conoscenza di "Derivato", non può ricevere un nuovo oggetto "Derivato" per valore, quindi è necessario utilizzare puntatori o riferimenti a "Base". –

+0

Ok, ma come faccio a garantire che la libreria richiami la funzione Add() fornita dal client se non posso più fare affidamento sulle funzioni dei membri virtuali per farlo? – AnonymousCoward

1
template<class C> 
class Base { 
public: 
    virtual ~Base() {} 
    virtual C operator+(const C& rhs) =0; 
}; 

class Derived: public Base<Derived> { 
public: 

potrebbe funzionare (salvo problema di classe incompleta).

+1

In questa soluzione, ogni classe derivata è derivata da una classe * diversa * Base <>, che è un po 'contraria all'idea del polimorfismo. – Amnon

+0

@Ammom si, vero. tuttavia ho usato un trucco simile poche volte, anche se per un motivo diverso. – Anycorn

0

Base essendo astratto, non è possibile istanziarlo, quindi il ritorno di valore è fuori questione. Ciò significa che dovrai tornare tramite puntatore o riferimento. Il rinvio per riferimento è pericoloso poiché è probabile che operator+ restituisca un valore temporaneo.

Questo lascia puntatore, ma sarebbe molto strano. E c'è tutta la questione su come rilasciare il puntatore quando non ne hai più bisogno. L'utilizzo di un puntatore intelligente potrebbe essere una soluzione a questo problema, ma si ha ancora l'intero problema "operator+ restituisce un puntatore".

Quindi, cosa stai cercando di ottenere? Cosa rappresenta Base? Ha senso aggiungere due istanze di una sottoclasse Base?

+0

Ho aggiornato la domanda per chiarire il mio intento – AnonymousCoward

2

La solita soluzione a questo è il modello di Gerarchia algebrica di Jim Coplein. Vedere il seguente:

Coplein's original paper (in particolare Algebraic Hierarchy)

Wikibooks

Elaborare un po ', è essenzialmente bisogno di una classe wrapper di cemento che contiene un puntatore polimorfica per l'oggetto reale classe derivata. Definisci gli operatori in questione per inoltrare la rappresentazione sottostante preservando la semantica del valore (piuttosto che la semantica dell'oggetto) che stai cercando. Accertati che la classe wrapper utilizzi l'idioma in modo da non perdere memoria con ogni oggetto temporaneo.

0

Desidero separare l'interfaccia dall'implementazione, in modo che il codice della libreria debba solo conoscere l'interfaccia e il codice client fornisce l'implementazione. Ho cercato di farlo fornendo l'interfaccia come una classe base astratta e l'implementazione come sottoclassi.

Utilizzare la pimpl idiom:

struct Base { 
    virtual ~Base() = 0; 
    virtual std::auto_ptr<Base> clone() = 0; 
    virtual void add(Base const &x) = 0; // implements += 
}; 

struct UserInterface { 
    UserInterface() : _pimpl(SomeFactory()) {} 
    UserInterface(UserInterface const &x) : _pimpl(x._pimpl->clone()) {} 

    UserInterface& operator=(UserInterface x) { 
    swap(*this, x); 
    return *this; 
    } 

    UserInterface& operator+=(UserInterface const &x) { 
    _pimpl->add(*x._pimpl); 
    } 

    friend void swap(UserInterface &a, UserInterface &b) { 
    using std::swap; 
    swap(a._pimpl, b._pimpl); 
    } 

private: 
    std::auto_ptr<Base> _pimpl; 
}; 

UserInterface operator+(UserInterface a, UserInterface const &b) { 
    a += b; 
    return a; 
} 

di applicare concretamente Base :: aggiungere, avrete bisogno o 1) doppio spedizione e trattare con l'esplosione di sovraccarico conseguente, 2) richiedono che solo una classe derivata di base viene utilizzato per l'esecuzione del programma (utilizzando ancora Pimpl come firewall di compilazione, ad esempio per lo scambio di librerie condivise) o 3) richiede che le classi derivate sappiano come gestire una base generica o generino un'eccezione.

-2

utilizzare il tipo dinamico e il tipo di base. nella classe base per enumerare la classe derivata è impossibile poiché l'overloading dell'operatore deve essere statico in ogni momento. L'opzione più comoda sarebbe usare il tipo 'dinamico' negli argomenti di sovraccarico. Di seguito è un esempio.

public class ModelBase 
{ 
    public abstract static string operator +(ModelBase obj1, dynamic obj2) 
    { 
     string strValue = ""; 
     dynamic obj = obj1; 
     strValue = obj.CustomerID + " : " + obj2.CustomerName; 
     return strValue; 
    } 
} 
+0

Questo è disponibile solo in C++/CLI no? –

Problemi correlati