如何将模板派生类的方法提取到非模板基类中

时间:2019-05-25 15:24:44

标签: c++

我想在C ++中使用多态性,我尝试将所有派生类中的方法显示提取到基类中。

例如:

我有两个类HouseAHouseB,它们是模板类。 它们是从基类BaseHouse派生的。

class BaseHouse
{
public:
    //other thing
private:
};

template <typename Type>
class HouseA : public BaseHouse
{
public:
    HouseA(Type object_input) : object(object_input)
    {
    }
    // other thing about HouseA
    Type &getObject()
    {
        std::cout << "this is House A" << std::endl;
        return object;
    }

private:
    Type object;
};

template <typename Type>
class HouseB : public BaseHouse
{
public:
    HouseB(Type object_input) : object(object_input)
    {
    }
    // other thing about HouseB
    Type &getObject()
    {
        std::cout << "this is House B" << std::endl;
        return object;
    }

private:
    Type object;
};

由于多态性,我们使用基类的指针来访问派生类对象。当我需要调用派生类中定义的方法时,我总是将基类指针转换为派生类指针:

int main()
{
    HouseA<int> house_a(5);
    int x = house_a.getObject();

    BaseHouse *base_ptr = &house_a;

    // suppose after some complicate calculate calculation
    // we only have the base class pointer can access derivated class object

    HouseA<int> *ptr_a = (HouseA<int> *)base_ptr; //transfer base class pointer into derivated class pointer
    ptr_a->getObject();
    return 0;
}

但是派生类HouseAHouseB都具有方法getObject

所以我想将模板派生类的方法提取到非模板基类中。

由于某些原因,我们假设基类BaseHouse不能是模板类。

有什么办法可以做到吗?

谢谢。

2 个答案:

答案 0 :(得分:2)

如果派生成员的签名取决于模板参数(如getObject在Type上所做的那样),则不能将成员提取到非模板库中。至少没有消除成员签名根据模板参数而变化的能力。

答案 1 :(得分:0)

也许不完全是经典的访客,但是...

好的,基本思想是我们必须以某种方式捕获模板化的处理并将其封装到一个可在运行时多态构造中使用的单个实体中。

让我们从一个简单的类层次结构开始:

struct Consumer;

struct Base {
    virtual void giveObject(Consumer const &) const = 0;
    virtual ~Base() = default;
};

struct Derived1: Base {
    Derived1(int x): x(x) {}
    void giveObject(Consumer const &c) const override {
        c(x);
    }
private:
    int x;
};

struct Derived2: Base {
    Derived2(double y): y(y) {}
    void giveObject(Consumer const &c) const override {
        c(y);
    }
private:
    double y;
};

到目前为止,这非常简单:Base类具有一个纯虚拟方法,该方法接受类型为Consumer的对象,并且预计该方法的具体实现将公开给{{1} }其特定实现者内部状态的相关部分(它是Consumer的子类型)。换句话说,我们采用了“虚拟模板”惯用法并将其隐藏在Base中。好吧,那可能是什么?

第一种选择,如果您提前在编译时(在源代码时,更确切地知道)知道它可能会做什么,即每种对象类型只有一个消耗算法,并且类型集是固定的,这很简单:

Consumer

更精细的实现将允许在运行时构建模板实体。那怎么可能?

Alexandrescu在其“现代C ++设计”中使用struct Consumer { void consume(int x) const { std::cout << x << " is an int.\n"; } void consume(double y) const { std::cout << y << " is a double.\n"; } template<typename T> void consume(T t) const { std::cout << "Default implementation called for an unknown type.\n"; } }; 将特定的类型处理程序存储在单个数据结构中。简而言之,可能类似于:

typeid

实际上没有测试过,因为我不太喜欢typeid和其他RTTI。我快速测试过的另一种解决方案既不需要映射也不需要typeinfo以模板方式存储处理程序。仍然使用了一个小技巧,例如我们如何通过相同的调用传递,保留和检索任意类型的信息。

struct Handler {
    virtual ~Handler() = default; // now it's an empty polymorphic base
};

template<typename T> struct RealHandler: Handler {
    RealHandler(std::function<void(T)> f): f(std::move(f)) {}
    void handle(T x) {
        f(x);
    }
private:
    std::function<void(T)> f;
};

#include <map>
#include <type_info>
#include <functional>

struct Consumer {
    template<typename T> void consume(T t) const {
        auto f{knownHandlers.find(typeid(t))};
        if(f != knownHandlers.end()) {
            RealHandler<T> const &rh{
                dynamic_cast<RealHandler<T> const &>(*f->second)};
            rh.handle(t);
        }
        else {
            // default implementation for unregistered types here
        }
    }
    template<typename T> Consumer &register(std::function<void(T)> f) {
        knownHandlers[typeid(T)] = std::make_unique<RealHandler<T>>(std::move(f));
    }
private:
    std::map<std::type_info, std::unique_ptr<Handler>> knownHandlers;
};

这里,struct Consumer { Consumer() {} template<typename T> void consume(T t) const { auto f{setSlot<T>()}; if(f) f(t); else { // default implementation for an unset slot std::cout << t / 2 << '\n'; } } template<typename T> std::function<void(T)> &setSlot( std::function<void(T)> f = std::function<void(T)>{}) const { static std::function<void(T)> slot; if(f) { // setter slot = std::move(f); } return slot; } }; 用于存储特定类型的处理程序:当使用非空参数调用时,它将存储该参数;然后返回其当前保留的值。如此定义setSlot()后,以上的类层次结构将用作:

Consumer

输出:

int main() {
    Consumer c;
    c.setSlot<int>([](int x){ std::cout << x << " is an int!\n"; });
    Base const &b1{Derived1{42}};
    Base const &b2{Derived2{3.14}};
    b1.giveObject(c);
    b2.giveObject(c);
}

在第一行中,我们看到了由自定义42 is an int! 1.57 处理程序打印的消息;在第二行中,由于未安装int的自定义处理程序,因此会为double类型打印一条默认消息。

此实现的一个明显的缺点是处理程序存储在double变量中,因此所有static的所有类型都共享相同的处理程序,因此Consumer实际上是单态的。至少,您可以在运行时更改类型的实现,这与您固定了第一种方法的Consumer不同。上面提到的Type-of-typeids方法不应具有此缺点,以换取一些性能成本。