Сужение подклассов члена-указателя в производном классе - PullRequest
0 голосов
/ 25 января 2019

У меня есть иерархия классов, которая разбита на два - высокоуровневые объекты и низкоуровневые и переставляемые интерфейсы соединений.Объекты соединения образуют иерархию классов, где каждый добавляет дополнительные функции к соединению.Точно так же иерархия классов высокого уровня нуждается в прогрессивно улучшенных соединениях.

Объекты соединений выглядят следующим образом:

class BaseConnection {
    virtual void a() = 0;
};
class BetterConnection : public BaseConnection {
    virtual void b() = 0;
}
class BestConnection : public BetterConnection {
    virtual void c() = 0;
}

Вот моя попытка написания объектов высокого уровня

struct Base {
protected:
    // This type is correct for `Base`, but `Better` and `Best` need access to a more derived type.
    unique_ptr<BaseConnection> const m_conn;
public:
    Base(unique_ptr<BaseConnection> conn) : m_conn(std::move(conn));
    void do_a_twice() {
        auto& conn = *m_conn;
        conn.a(); conn.a();
    }
};
struct Better : public Base {
    Better(unique_ptr<BetterConnection> conn) : Base(std::move(conn));
    void do_b_twice() {
        auto& conn = dynamic_cast<BetterConnection&>(*m_conn);
        conn.b(); conn.b();
    }
};
struct Best : public Better {
    unique_ptr<BetterConnection> conn;
    Better(unique_ptr<BetterConnection> conn) : Better(std::move(conn));
    void do_c_twice() {
        auto& conn = dynamic_cast<BestConnection&>(*m_conn);
        conn.b(); conn.b();
    }
};

Итак, мои вопросы:

  1. Есть ли способ достичь этого без dynamic_cast?
  2. Правильно ли я считаю, что это приводит к накладным расходам на использование времени выполнения -информация о типе?
  3. Безопасно ли использовать reinterpret_cast здесь?

1 Ответ

0 голосов
/ 26 января 2019

Мне кажется, что абстракции вокруг ваших типов Connection усложняют ситуацию (тогда как абстракция должна упростить вещи).

Почему типы Connectionесть разные участники?Если производные классы Connection вместо этого переопределяют BaseConnection, вы можете положиться на диспетчеризацию виртуальных функций, чтобы делать правильные вещи во время выполнения.Например,

struct BaseConnection {                             
    virtual void connect() {                        
        cout << "BaseConnection::connect" << endl;  
    }                                               
};                                                  

struct BetterConnection : public BaseConnection {   
    void connect() override {                       
        cout << "BetterConnection::connect" << endl;
    }                                               
};                                                  

struct BestConnection : public BetterConnection {   
    void connect() override {                       
        cout << "BestConnection::connect" << endl;  
    }                                               
};                                                  

class X {                                           
public:                                             
    X(std::unique_ptr<BaseConnection> connection)   
        : connection_(std::move(connection))        
    {                                               
        connection_->connect();                     
    }                                               

private:                                            
    std::unique_ptr<BaseConnection> connection_;    
};                                                  

int main() {                                        
    X(std::make_unique<BaseConnection>());          
    X(std::make_unique<BetterConnection>());        
    X(std::make_unique<BestConnection>());          
}                                                   

Если типы Connection имеют разные методы, потому что они действительно выполняют разные действия, то возникает вопрос, является ли наследование подходящей абстракцией.

Возможно, выМожно добавить виртуальный метод, который вы переопределите, чтобы «делать правильные вещи» для каждого производного Connection.Тогда классу более высокого уровня нужно вызвать только этот метод, и это можно сделать без приведения.

В общем, если вам нужно использовать dynamic_cast для проверки типов во время выполнения, возможно, этоозначает, что интерфейсы не предназначены для полиморфизма.Я бы переосмыслил интерфейсы между вашими объектами и попытался бы выяснить, есть ли способ получить то, что вы хотите, без необходимости использовать upcast.


Редактировать: Использование черт типа

На основе вашихкомментарии, кажется, что вам может потребоваться больше настройки объектов более высокого уровня, чем мой первоначальный ответ.По сути, я думаю, что вы пытаетесь сделать это на основе того, каким базовым типом Connection вы управляете, и предоставить различные реализации функций более высокого уровня.

Распространенный способ сделать это (и какSTL делает это) через перегрузку оператора чертами типа.Чтобы проиллюстрировать это, начните с нескольких типов, которые описывают черты базовых объектов соединения.

struct base_connection_tag {};                               
struct better_connection_tag : public base_connection_tag {};
struct best_connection_tag : public better_connection_tag {};

И затем мы можем добавить их к классам Connection.

struct BaseConnection {                               
    virtual void a() {                                
        cout << "BaseConnection::a()" << endl;        
    }                                                 

    using connection_category = base_connection_tag;  
};                                                    

struct BetterConnection : public BaseConnection {     
    virtual void b() {                                
        cout << "BetterConnection::b()" << endl;      
    }                                                 

    using connection_category = better_connection_tag;
};                                                    

struct BestConnection : public BetterConnection {     
    virtual void c() {                                
        cout << "BestConnection::c" << endl;          
    }                                                 

    using connection_category = best_connection_tag;  
};                                                    

По соглашению connection_traits повторяет вложенный typedef класса Connection

template <typename ConnectionT>                                           
struct connection_traits {                                                
    using connection_category = typename ConnectionT::connection_category;
};                                                                        

Наконец, мы можем использовать перегрузку операторов, чтобы решить, какую реализацию вызывать в некотором классе (или классах) более высокого уровня, используяшаблон, подобный следующему:

template <typename T>                                                 
class Dispatch                                                        
{                                                                     
public:                                                               
    Dispatch(std::unique_ptr<T> connection)                           
        : connection_(std::move(connection))                          
    {}                                                                

    void operator()() {                                               
        connect(typename connection_traits<T>::connection_category());
    }                                                                 

private:                                                              

    void connect(base_connection_tag) {                               
        connection_->a();                                             
    }                                                                 

    void connect(better_connection_tag) {                             
        connection_->b();                                             
    }                                                                 

    void connect(best_connection_tag) {                               
        connection_->c();                                             
    }                                                                 

    std::unique_ptr<T> connection_;                                   
};                                                                    

Когда вызывается оператор (), класс Dispatch вызывает один из методов connect, используя connection_traits базового Connection.

Поскольку все типы известны во время компиляции, класс Dispatch знает, какой базовый метод вызывать во время компиляции.dynamic_cast не требуется для определения того, какой тип удерживается.

Хотя я использовал только один шаблонный класс для реализации функциональности более высокого порядка, вы могли бы реально использовать несколько не шаблонных классов для того же, используя connection_traits и перегрузка аргументов функции в каждом для включения / выключения функции.

...