首页 > 解决方案 > 缩小派生类中指针成员的子类

问题描述

我有一个分为两个高级对象的类层次结构,以及低级和可交换的连接接口。连接对象形成一个类层次结构,其中每个对象都向连接添加更多功能。类似地,高级类层次结构需要逐渐更好的连接。

连接对象如下所示:

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吗?

标签: c++polymorphismdynamic-cast

解决方案


在我看来,围绕您的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方法。connection_traitsConnection

由于所有类型在编译时都是已知的,因此Dispatch类知道在编译期间要调用哪个基方法。不需要dynamic_cast确定持有哪种类型。

虽然我只使用一个模板类来实现更高阶的功能,但您可以使用多个非模板类来做同样的事情,connection_traits在每个类中使用函数参数重载来启用/禁用功能。


推荐阅读