Domanda

Esiste un modo per aggiungere nuovi metodi a una classe, senza modificare la definizione della classe originale (ovvero .lib compilata contenente la classe e il corrispondente file .h) come i metodi di estensione della classe di C #?

È stato utile?

Soluzione

No. C ++ non ha tale capacità.

Come menzionato in altre risposte, le soluzioni alternative comuni sono:

  • Definire una classe derivata, magari con una factory per nascondere la classe di implementazione effettiva
  • Definisci una decoratore class
  • Definisci funzioni non membro che operano su istanze della classe

Altri suggerimenti

No, non puoi farlo in C ++.

Se vuoi ottenere qualcosa del genere hai 2 opzioni,

  • Potresti ereditare dalla classe (se questa è un'opzione, potrebbe non essere legale in quanto la classe potrebbe non essere stata scritta per consentire l'ereditarietà)
  • Puoi scrivere la tua classe wrapper che ha la stessa interfaccia + i tuoi nuovi metodi e delegare a quella che vuoi estendere.

Preferisco l'approccio della delega.

I metodi di estensione della classe C # sono per lo più zucchero sintattico. Ottieni la stessa funzionalità con funzioni gratuite (ad esempio, funzioni con un riferimento o un riferimento costante alla tua classe come primo parametro). Dal momento che questo funziona bene con la STL, perché non per la tua classe?

In C ++ puoi usare funzioni gratuite, ma a volte i metodi di estensione funzionano meglio quando annidi molte funzioni insieme. Dai un'occhiata a questo codice C #:

var r = numbers.Where(x => x > 2).Select(x => x * x);

Se dovessimo scriverlo in C ++ usando la funzione gratuita sembrerebbe questo:

auto r = select(where(numbers, [](int x) { return x > 2; }), [](int x) { return x * x; });

Non solo è difficile da leggere, ma è difficile da scrivere. Il modo comune per risolvere questo problema è creare quella che viene chiamata una funzione pipable. Queste funzioni vengono create sovraccaricando l'operatore pipe | (che è proprio l'operatore o). Quindi il codice sopra potrebbe essere scritto in questo modo:

auto r = numbers | where([](int x) { return x > 2; }) | select([](int x) { return x * x; });

Che è molto più facile da leggere e scrivere. Molte librerie usano la funzione pipable per gli intervalli, ma potrebbe essere estesa anche ad altre classi. Boost lo usa nei loro range library, pstade forno lo utilizza e anche questa libreria C ++ linq la utilizza anche.

Se desideri scrivere la tua funzione pipable, boost spiega come farlo qui . Altre librerie, tuttavia, forniscono adattatori di funzioni per renderlo più semplice. Pstade egg ha una pipable adattatore e linq fornisce l'adattatore range_extension per creare almeno una funzione pipable per gli intervalli.

Usando linq, devi prima creare la tua funzione come oggetto funzione come questo:

struct contains_t
{
    template<class Range, class T>
    bool operator()(Range && r, T && x) const
    { return (r | linq::find(x)) != boost::end(r); };
};

Quindi si inizializza la funzione usando l'inizializzazione statica in questo modo:

range_extension<contains_t> contains = {};

Quindi puoi usare la tua funzione pipable in questo modo:

if (numbers | contains(5)) printf("We have a 5");

Generalmente no. Tuttavia, se la libreria non crea istanze della classe che richiedono la tua estensione e sei in grado di modificare tutte le posizioni nell'app che creano un'istanza della classe e richiedono le tue estensioni, c'è un modo in cui puoi andare:

  • Crea una funzione factory chiamata in tutti i punti che richiedono un'istanza della classe e restituisce un puntatore all'istanza (google per Design Patterns Factory , ...).
  • Crea una classe derivata con le estensioni che desideri.
  • Rendi la funzione factory restituisce la tua classe derivata invece della classe originale.

Esempio:


    class derivedClass: public originalClass { /* ... */};

    originalClass* createOriginalClassInstance()
    {
         return new derivedClass();
    }
  • Ogni volta che devi accedere alle estensioni, ovviamente devi trasmettere il cast originale alla classe derivata.

Questo è approssimativamente come implementare il " eredit " metodo suggerito da Glen. Glen! & Quot; classe wrapper con la stessa interfaccia & Quot; il metodo è anche molto carino da un punto di vista teorico, ma ha proprietà leggermente diverse che rendono meno probabile il lavoro nel tuo caso.

C'è un modo in cui può essere fatto. E questo è rilassare un po 'le tue esigenze. In C ++, le persone spesso affermano che l'interfaccia di una classe non consiste solo nelle sue funzioni membro, ma di tutte le funzioni che funzionano sulla classe .

Cioè, le funzioni non membri che possono essere assegnate alla classe come parametro dovrebbero essere considerate parte della sua interfaccia.

Ad esempio, std::find() o std::sort() fanno parte dell'interfaccia di std::vector, anche se non sono membri della classe.

E se accetti questa definizione, puoi sempre estendere una classe semplicemente aggiungendo funzioni non membro.

Non è possibile aggiungere metodi o dati fisicamente al file di classe in formato binario. Tuttavia, è possibile aggiungere metodi e dati (funzionalità e stato) agli oggetti di quella classe scrivendo le classi di estensione. Questo non è semplice e richiede una programmazione basata su Meta-Object-Protocol e Interface. È necessario fare molto per raggiungere questo obiettivo in C ++ poiché non supporta Reflection immediatamente. In tale implementazione quando si esegue una query per l'interfaccia implementata dalla nuova classe di estensione tramite il puntatore dell'oggetto classe originale, l'implementazione del meta oggetto restituisce quel puntatore all'interfaccia tramite l'oggetto meta classe per la classe di estensione che viene creata in fase di esecuzione. Ecco come funzionano i framework di applicazioni software personalizzabili (basati su plug-in). Tuttavia, è necessario ricordare che richiede la scrittura di molti altri meccanismi MOP per istanziare meta oggetti per tutte le classi utilizzando dizionari in cui sono descritte le relazioni dell'oggetto e fornire i puntatori di interfaccia corretti per gli oggetti di classe originali ed estesi. CATIA V5 di Dassault Systemes è scritto in una tale architettura chiamata CAA V5 in cui è possibile estendere i componenti esistenti scrivendo nuove classi di estensione con la funzionalità desiderata.

Siamo spiacenti, no. Una volta che il tuo codice è in obj, non puoi cambiarlo. Se ciò fosse possibile nelle classi parziali VC sarebbero già supportate. C'è un'eccezione, tuttavia, i metodi dell'operatore possono essere estesi usando le funzioni globali, proprio come cout & Lt; & Lt; è implementato in STL.

Sicuro che puoi:


template <typename Ext>
class Class: public Ext { /* ... */ };

Ciò non significa che sia l'approccio migliore.

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