7 Stimmen

C++ Funktion in parent return child

Ich möchte, dass die Mutatoren (Setter) in meiner Klasse Folgendes zurückgeben this um jQuery-ähnliches zu ermöglichen a.name("something").address("somethingelse"); Ich habe eine übergeordnete Klasse ( Entity ) und mehrere Unterklassen ( Client, Agent etc. ). Die Mutatoren für die meisten Dinge werden von der Entity Klasse (wie Name oder Adresse), aber sie geben eine Entity Objekt, so dass ich keine Client-Mutatoren für sie aufrufen kann.

Mit anderen Worten:

// name mutator
Entity& Entity::name( const string& name ) {
    // [...] checks
    _name = name;
    return *this;
}

// budgetRange mutator
Client& Client::budgetRange( const long int& range ) {
    // [...] checks
    _budgetRange = range;
    return *this;   
}

dann, wenn ich sie anrufe:

Client a; a.name("UserName123").budgetRange(50);

Der Compiler sagt (logischerweise), dass das Entity-Objekt kein budgetRange-Mitglied hat (weil name ein Entity und keinen Client zurückgibt).

Meine Frage ist nun: Wie könnte ich so etwas umsetzen? Ich dachte daran, alle Entity-Funktionen in den Kindklassen zu überladen, aber das wäre nicht schön und würde der Idee der Vererbung widersprechen :)

Ich danke Ihnen im Voraus für Ihre Ideen :D

8voto

Abyx Punkte 11461

Sie sollten die CRTP .

template<class Derived>
class Entity
{
    Derived* This() { return static_cast<Derived*>(this); }

public:
    Derived& name(const string& name)
    {
        ...
        return *This();
    }
};

class Client : public Entity<Client>
{
public:
    Client& budgetRange(const long& range)
    {
        ...    
        return *this;   
    }
};

Wenn Sie virtuelle Funktionen verwenden möchten, können Sie auch eine abstrakte Basisklasse hinzufügen, etwa so:

class AbstractEntity
{
public:
     virtual void foo() = 0;

     virtual ~AbstractEntity();
};

template<class Derived>
class Entity : AbstractEntity
{...};

3voto

Mike Seymour Punkte 242473

Das "curiously recursive template"-Muster könnte hier helfen; machen Sie die Basisklasse zu einer Vorlage, die von der abgeleiteten Klasse parametrisiert wird, etwa nach dem Muster:

template <typename Derived>
struct Entity {
    Derived & name(std::string const & name) {
        // stuff
        return static_cast<Derived&>(*this);
    }
};

struct Client : Entity<Client> {
    Client & budget(long range) {
        // stuff
        return *this;
    }
};

Client().name("Mike").budget(50); // should compile

Dies funktioniert nur, wenn alle Ihre Typen direkt von Entity . Wenn die Typen polymorph sein sollen (d.h. alle teilen sich eine gemeinsame Basisklasse), dann müssen Sie eine weitere Nicht-Template-Basisklasse hinzufügen, und die Entity davon erben.

3voto

chrish. Punkte 705

Nachdem nun schon fast alles gesagt wurde, möchte ich noch eine Antwort hinzufügen, die es ermöglicht, das CRTP über mehrere Vererbungsebenen hinweg zu verwenden:

Die obigen CRTP-Implementierungen brechen zusammen, wenn man von Client da Derived wird sich beziehen auf Client . Wenn Sie das Idiom der benannten Parameter mit Hilfe des CRTP-Musters über mehrere Vererbungsebenen hinweg verwenden möchten, müssen Sie Ihre Klassen wie folgt kodieren

template<class Derived>
class Entity_T
{
protected:
    Derived* This() { return static_cast<Derived*>(this); }
public:
    Derived& name(const string& name)
    {
        ...
        return *This();
    }
};

template<class Derived>
class Client_T : public Entity_T<Derived>
{
    Derived& budgetRange(const long& range)
    {
        ...    
        return *This();   
    }
};

Um dem Nutzer eine vorlagenfreie Version von Client_T hinzufügen.

class Client : public Client_T<Client> {};

Ob dies die vergrößerte Codebasis wert ist oder nicht, liegt ganz bei Ihnen. Beachten Sie, dass ich den obigen Code nicht kompiliert habe.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X