Функция C ++ в родительском возврате потомка - PullRequest
5 голосов
/ 20 декабря 2011

Если честно, я не знаю, как задать этот вопрос, поэтому, пожалуйста, не злись:)

В любом случае, я хочу, чтобы в моем классе были мутаторы (сеттеры), чтобывернуть this, чтобы учесть jQuery-подобный a.name("something").address("somethingelse"); У меня есть родительский класс (Entity) и несколько дочерних классов (Client, Agent etc.).Мутаторы для большинства вещей наследуются от класса Entity (например, имя или адрес), но они возвращают объект Entity, поэтому я не могу вызывать клиентские мутаторы для них.

Другими словами:

// 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;   
}

тогда, когда я его вызываю:

Client a; a.name("Dorota Adamczyk").budgetRange(50);

Компилятор (логически) говорит, что объект Entity не имеет элемента budgetRange (потому что name возвращает Entity, а не Client).

Теперь у меня вопрос: как я могу реализовать что-то подобное?Я думал о перегрузке всех функций Entity в дочерних классах, но это было бы нехорошо и было бы против идеи наследования:)

Заранее спасибо за ваши идеи: D

Ответы [ 3 ]

7 голосов
/ 20 декабря 2011

Вы должны использовать 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;   
    }
};

Если вы хотите использовать виртуальные функции, вы также можете добавить абстрактный базовый класс, например:

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

     virtual ~AbstractEntity();
};

template<class Derived>
class Entity : AbstractEntity
{...};
3 голосов
/ 20 декабря 2011

Здесь может помочь шаблон "любопытно рекурсивный шаблон"; сделать базовый класс шаблоном, параметризованным производным классом, следующим образом:

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

Это будет работать, только если все ваши типы наследуются напрямую от Entity. Если вам нужно, чтобы типы были полиморфными (то есть все они имели общий базовый класс), вам нужно добавить еще один базовый класс, не относящийся к шаблону, и Entity наследовать от него.

2 голосов
/ 20 декабря 2011

Теперь, когда почти все уже сказано, я хочу добавить часть ответа, которая позволяет использовать CRTP для нескольких уровней наследования:

Приведенные выше реализации CRTP обрываются, когда кто-то хочет наследовать от Client, поскольку Derived будет ссылаться на Client. Если вы хотите иметь возможность переносить именованный параметр параметра на несколько уровней наследования с использованием шаблона CRTP, вам необходимо кодировать свои классы следующим образом:

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();   
    }
};

Чтобы предоставить пользователю бесплатную версию Client_T, добавьте

class Client : public Client_T<Client> {};

Стоит ли это того, стоит ли расширенная база кода, решать только вам. Обратите внимание, что я не скомпилировал код выше.

...