首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >如何将模板派生类的方法提取到非模板基类中

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

Stack Overflow用户
提问于 2019-05-25 23:24:44
回答 2查看 296关注 0票数 0

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

例如:

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

代码语言:javascript
复制
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;
};

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

代码语言:javascript
复制
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不能是模板类。

我有没有办法做到这一点?

提前谢谢。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2019-05-25 23:35:56

如果派生成员的签名依赖于模板参数(就像getObject对类型所做的那样),则不能将该成员提取到非模板库中。至少在不删除成员签名根据模板参数而变化的能力的情况下是这样的。

票数 2
EN

Stack Overflow用户

发布于 2019-05-28 05:19:21

也许不是一个典型的访客但是..。

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

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

代码语言:javascript
复制
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类型对象的纯虚方法,该方法的一个具体实现应该向Consumer公开其特定实现者(这是Base的一个子类型)的内部状态的相关部分。换句话说,我们采用了“虚拟模板”的习惯用法,并将其隐藏在Consumer中。好吧,那会是什么呢?

第一种选择,如果你在编译时(更准确地说,在源代码时)预先知道它可能做什么,即每个对象类型只有一个消耗算法,并且类型集是固定的,那么它就非常简单:

代码语言:javascript
复制
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";
    }
};

等。

更复杂的实现将允许运行时构造模板化的实体。这怎么可能呢?

Alexandrescu在他的“现代C++设计”中使用typeid在单个数据结构中存储特定类型的处理程序。简而言之,这可能是这样的:

代码语言:javascript
复制
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;
};

我还没有真正测试过它,因为我不太喜欢typeids和其他RTTI。我很快测试了另一个解决方案,它既不需要map也不需要typeinfo来以模板化的方式存储处理程序。尽管如此,它仍然使用了一个小技巧,比如我们如何通过相同的调用传递、保存和检索任意类型的信息。

代码语言:javascript
复制
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,上面的类层次结构的工作方式如下:

代码语言:javascript
复制
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);
}

输出:

代码语言:javascript
复制
42 is an int!
1.57

在第一行中,我们看到一条由自定义int处理程序打印的消息;在第二行中,打印了一条double类型的默认消息,因为没有安装double的自定义处理程序。

这种实现的一个明显缺点是处理程序存储在static变量中,因此所有的Consumer对于所有类型共享相同的处理程序,所以这里的Consumer实际上是单状态的。至少,您可以在运行时更改类型的实现,这与修复第一种方法的Consumers不同。上面的maps of-typeids方法不应该有这个缺点,但要付出一些性能代价。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/56306180

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档