缩小派生类中指针成员的子类

时间:2019-01-25 19:32:15

标签: c++ polymorphism dynamic-cast

我有一个分为两个类的类层次结构-高级对象以及低级和可交换的连接接口。连接对象形成一个类层次结构,其中每个对象都为连接添加了更多功能。同样,高级类层次结构也需要逐步改善连接。

连接对象如下所示:

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 :(得分:0)

在我看来,您的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进行类型检查,则可能意味着这些接口并不是为多态而设计的。我会重新考虑对象之间的接口,并尝试查看是否有一种方法可以不必上载就获得所需的内容。


编辑:使用特征类型

根据您的评论,似乎您可能需要比我提供的原始答案更多的自定义高级对象。本质上,我认为您要尝试的是处理您要管理的基础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回显Connection类的嵌套typedef

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的{​​{1}}调用connection_traits方法之一。

由于所有类型在编译时都是已知的,因此Connection类知道在编译期间要调用的基本方法。无需Dispatch即可确定要保留哪种类型。

尽管我只使用一个模板类来实现高阶功能,但是您可以可行地使用多个非模板类来完成相同的事情,在每个模板中都使用dynamic_cast和函数参数重载来启用/禁用功能。