Domanda Metodi di estensione in c ++


Stavo cercando un'implementazione dei metodi di estensione in c ++ e mi sono imbattuto questa discussione comp.std.c ++ che lo menziona polymorphic_map può essere utilizzato per i metodi associati a una classe, ma il collegamento fornito sembra essere morto. Qualcuno sa a cosa si riferiva questa risposta o se esiste un altro modo per estendere le classi in modo simile ai metodi di estensione (magari attraverso l'uso di mixin?).

So che la soluzione canonica di C ++ consiste nell'utilizzare funzioni libere; questo è più per curiosità che altro.


44
2018-03-28 17:54


origine


risposte:


Lingue diverse affrontano lo sviluppo in modi diversi. In particolare C # e Java hanno un forte punto di vista rispetto a OO che porta a tutto è un oggetto mentalità (C # è un po 'più rilassato qui). In questo approccio, i metodi di estensione forniscono un modo semplice per estendere un oggetto o un'interfaccia esistente per aggiungere nuove funzionalità.

Non ci sono metodi di estensione in C ++, né sono necessari. Quando sviluppi C ++, dimentica che tutto è un paradigma oggetto - che, a proposito, è falso anche in Java / C # [*]. Una mentalità diversa viene presa in C ++, ci sono oggetti, e gli oggetti hanno operazioni che sono intrinsecamente parte dell'oggetto, ma ci sono anche altre operazioni che fanno parte dell'interfaccia e non devono necessariamente far parte della classe. È una lettura di Herb Sutter Cosa c'è in una classe?, dove l'autore difende (e sono d'accordo) che puoi facilmente estendere qualsiasi classe data con semplici funzioni libere.

Come un semplice esempio particolare, la classe basata su modelli standard basic_ostream ha alcuni metodi per scaricare i contenuti di alcuni tipi primitivi e quindi è arricchito con (anche con modelli) funzioni libere che estendono tale funzionalità ad altri tipi utilizzando l'interfaccia pubblica esistente. Per esempio, std::cout << 1; è implementato come una funzione membro, mentre std::cout << "Hi"; è una funzione gratuita implementata in termini di altri membri di base.

L'estensibilità in C ++ si ottiene mediante funzioni libere, non con l'aggiunta di nuovi metodi agli oggetti esistenti.

[*] Tutto è non un oggetto.

In un dato dominio conterrà un insieme di oggetti reali che possono essere modellati e le operazioni che possono essere applicati a loro, in alcuni casi quelle operazioni faranno parte dell'oggetto, ma in altri casi non lo faranno. In particolare troverai classi di utilità nelle lingue che affermano che tutto è un oggetto e quelli classi di utilità non sono altro che uno strato che cerca di nascondere il fatto che quei metodi non appartengono ad alcun oggetto particolare.

Anche alcune operazioni implementate come funzioni membro non sono realmente operazioni sull'oggetto. Considerare l'aggiunta per a Complex classe numero, come sta sum (o +) più di un'operazione sul primo argomento rispetto al secondo? Perché a.sum(b); o b.sum(a), non dovrebbe essere sum( a, b )?

Costringere le operazioni a essere metodi membri produce effetti strani - ma siamo abituati a loro: a.equals(b);e b.equals(a); potrebbe avere risultati completamente diversi anche se l'implementazione di equals è completamente simmetrico. (Considerare cosa succede quando entrambi a o b è un puntatore nullo)


64
2018-03-28 19:42



L'approccio della libreria Boost Range utilizza l'operatore | ().

r | filtered(p);

Posso scrivere anche trim per string come segue allo stesso modo.

#include <string>

namespace string_extension {

struct trim_t {
    std::string operator()(const std::string& s) const
    {
        ...
        return s;
    }
};

const trim_t trim = {};

std::string operator|(const std::string& s, trim_t f)
{
    return f(s);
}

} // namespace string_extension

int main()
{
    const std::string s = "  abc  ";

    const std::string result = s | string_extension::trim;
}

19
2018-03-29 00:11



La risposta breve è che non puoi farlo. La lunga risposta è che puoi simularlo, ma tieni presente che dovrai creare molto codice come soluzione alternativa (in realtà, non penso che esista una soluzione elegante).

Nella discussione, una soluzione molto complessa è fornita usando l'operatore- (che è una cattiva idea, secondo me). Immagino che la soluzione fornita nel dead link fosse più o meno simile (dato che era basata sull'operatore |).

Questo si basa sulla capacità di essere in grado di fare più o meno la stessa cosa di un metodo di estensione con gli operatori. Ad esempio, se vuoi sovraccaricare l'operatore ostream << per la tua nuova classe Foo, potresti fare:

class Foo {
    friend ostream &operator<<(ostream &o, const Foo &foo);
    // more things...
};

ostream &operator<<(ostream &o, const Foo &foo)
{
  // write foo's info to o
}

Come ho detto, questo è l'unico meccanismo disponibile in C ++ per i metodi di estensione. Se puoi tradurre la tua funzione in modo naturale su un operatore sovraccarico, allora va bene. L'unica altra possibilità è quella di sovraccaricare artificialmente un operatore che non ha nulla a che fare con il tuo obiettivo, ma questo ti farà scrivere codice molto confuso.

L'approccio più simile che posso pensare vorrebbe dire creare una classe di estensione e creare lì i tuoi nuovi metodi. Sfortunatamente, questo significa che dovrai "adattare" i tuoi oggetti:

class stringext {
public:
    stringext(std::string &s) : str( &s )
        {}
    string trim()
        {  ...; return *str; }
private:
    string * str;
};

E poi, quando vuoi fare queste cose:

void fie(string &str)
{
    // ...
    cout << stringext( str ).trim() << endl;
}

Come detto, questo non è perfetto, e non penso che esista una soluzione perfetta. Scusate.


7
2018-03-28 18:22



Questa è la cosa più vicina che abbia mai visto nei metodi di estensione in C ++. Personalmente mi piace il modo in cui può essere usato, e probabilmente questo è il più vicino possibile ai metodi di estensione in questa lingua. Ma ci sono alcuni svantaggi:

  • Potrebbe essere complicato da implementare
  • La precedenza degli operatori potrebbe non essere così bella alcune volte, questo potrebbe causare sorprese

Una soluzione:

#include <iostream>

using namespace std;


class regular_class {

    public:

        void simple_method(void) const {
            cout << "simple_method called." << endl;
        }

};


class ext_method {

    private:

        // arguments of the extension method
        int x_;

    public:

        // arguments get initialized here
        ext_method(int x) : x_(x) {

        }


        // just a dummy overload to return a reference to itself
        ext_method& operator-(void) {
            return *this;
        }


        // extension method body is implemented here. The return type of this op. overload
        //    should be the return type of the extension method
        friend const regular_class& operator<(const regular_class& obj, const ext_method& mthd) {

            cout << "Extension method called with: " << mthd.x_ << " on " << &obj << endl;
            return obj;
        }
};


int main()
{ 
    regular_class obj;
    cout << "regular_class object at: " << &obj << endl;
    obj.simple_method();
    obj<-ext_method(3)<-ext_method(8);
    return 0;
}

Questa non è la mia invenzione personale, recentemente un mio amico me l'ha spedito, ha detto di averlo ricevuto da una mailing list universitaria.


5
2018-01-19 21:11