Элемент производного от указателя на базу, static_cast, crtp, удаление шаблонов - PullRequest
0 голосов
/ 12 сентября 2018

Ищу: доступ к члену производного класса из указателя на базу.

Reductio ad absurdum:

class Base
{
public:
    int member_of_base;
};

class Derived : public Base
{
public:
    int member_of_derived;
};

Я сейчас использую шаблоны:

template <class T>
class Client
{
    T* data; // T is Base or Derived
};

Существует несколько уровней композиции в иерархии классов, поэтому мне нужно провести параметр типа шаблона через всю иерархию. Каков наилучший подход для преодоления этого? Очевидно, я не могу получить доступ к члену Derived через указатель на Base, т. Е.

Base* foo = new Derived();
foo->member_of_derived; // no go

Таким образом, я использую:

Client<Base>
Client<Derived>

Я пытаюсь найти решение, которое работает без шаблонов. Опции, которые я знаю, будут работать:

  • void * // простой старый C и приведение по необходимости, все они указатели (как в адресах памяти) в машине
  • static_cast<Derived*>(pointer_to_base); // введите safe во время компиляции.
  • завершение приведения в шаблонном методе Клиента (не путать с шаблоном дизайна здесь)

Последний вариант кажется наиболее «элегантным», то есть:

template <class T>
T* get_data() const { return static_cast<T*>(data); }

Однако, глядя здесь и там, можно сказать, что может существовать неизвестный мне путь. Я видел CRTP, но это возвращает меня к шаблонам, что является оригинальной вещью, без которой я хочу обойтись. Каковы пути или популярные подходы для достижения такой цели?

Реальный код использует shared_ptr, weak_ptr и enable_shared_from_this со слабым_from_this. Я ищу безопасный тип доступа "полиморфный член".

РЕДАКТИРОВАТЬ: они не просто "целые". Они могут быть совершенно разных типов, как в protobuf в base и Json :: Value в производном. И я пытаюсь использовать указатели на Base / Derived, что, в свою очередь, даст мне доступ к их соответствующим членам.

1 Ответ

0 голосов
/ 13 сентября 2018

Виртуальный добытчик может решить эту проблему для вас; поскольку типы данных различаются, вы можете упаковать их в std::variant.

class Base
{
    // having private members probably is more appropriate
    int member_of_base;

public:
    using Data = std::variant<int, double>;

    virtual ~Base() { } // virtual functions -> have a virtual destructor!

    virtual Data getMember() // or just "member", if you prefer without prefix
    {
        return member_of_base;
    }
};

class Derived : public Base
{
    double member_of_derived;

public:   
    Data getMember() override
    {
        return member_of_derived;
    }
};

std::unique_ptr<Base> foo = new Base();
foo->getMember(); // member_of_base;
std::unique_ptr<Base> bar = new Derived();
bar->getMember(); // member_of_derived;

Допустим, еще не полностью без шаблонов, std::variant - это одно, но я полагаю, что в такой форме это приемлемо ...

Однако есть некоторые проблемы:

  1. Доступ к значению не самый простой, вы можете рассмотреть функцию visit для.
  2. Более серьезный: базовый класс (или где бы вы ни определяли используемый вариант) должен знать обо всех типах, которые могут использоваться, добавление нового типа заставит перекомпилировать все другие классы.
  3. Имеет запах плохого дизайна. Почему производному классу необходимо возвращать что-то отличное от базового, хотя и для той же цели ???

Если вы можете поручить выполнение работы самим классам, вы справитесь со всеми этими проблемами:

class Base
{
    int member_of_base;

public:
    virtual ~Base() { }

    virtual void doSomething()
    {
        /* use member_of_base */
    }
};

class Derived : public Base
{
    double member_of_derived;

public:
    void doSomething() override
    {
        /* use member_of_derived */   
    }
};

Последнее было бы истинным полиморфным подходом и обычно подходом; в то время как в приведенном выше примере возвращается void, вы можете просто выполнить все необходимые вычисления в базовых и производных классах, пока, наконец, не доберетесь до некоторого общего типа данных и не вернете этот. Пример: * * тысяча двадцать-шесть

class Base
{
    int64_t m_balance; // in 100th of currency in use

public:
    virtual ~Base() { }

    virtual int64_t balance()
    {
        return m_balance;
    }
};

class Derived : public Base
{
    long double m_balance; // arbitrary values in whole currency entities

public:
    int64_t balance() override
    {
        // calculate 100th of currency, correctly rounded:
        return std::llround(m_balance * 100);
    }
};

Признаюсь, я сомневаюсь, что представление баланса в двойном размере (даже если длинное) - хорошая идея (проблемы с округлением, точностью и т. Д.) ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...