【问题标题】:Virtual template workaround without too much verbosity没有太多冗长的虚拟模板解决方法
【发布时间】:2019-04-07 22:21:04
【问题描述】:

我正在寻找 C++ 中缺少虚拟模板函数的解决方法。 我想要的理想是能够将我的派生类存储在一个向量中,遍历它们并调用正确的函数,所以在伪代码中:


template<typename T>
struct Output
{
    ...
};

struct Base
{
    template<typename T>
    virtual void doSomething(Output<T>& out) = 0;
};

struct DerivedA : public Base
{
    DerivedA(const char* filename) {...}
    template<typename T>
    void doSomething(Output<T>& out) final
    {
        ...
    }
};

struct DerivedB : public Base
{
    DerivedB(const char* filename) {...}
    template<typename T>
    void doSomething(Output<T>& out) final
    {
        ...
    }
};

int main()
{
    std::vector<Base*> vec;
    vec.push_back(new DerivedA("data1.bin"));
    vec.push_back(new DerivedB("data2.bin"));
    vec.push_back(new DerivedA("data3.bin"));
    vec.push_back(new DerivedA("data4.bin"));

    Output<float> outF;
    Output<double> outD;
    Output<int> outI;
    for (auto e : vec)
    {
        e->doSomething(outF);
        e->doSomething(outD);
        e->doSomething(outI);
    }

    return 0;
}

如果解决方法尽可能“无痛”且不冗长,我会更喜欢它(因为我使用模板来避免首先为 n 个不同类型重新定义相同的函数 n 次)。我想到的是用 std::map 让自己成为一个 vtable,并做一些 dynamic_casts。我正在寻找任何更好的想法,或者如果您认为它在这种情况下是最好的,甚至可以简洁地实现该想法。我正在寻找一种理想情况下干扰最小的解决方案,并且很容易向其中添加新类。

编辑: 我想出了一个解决方法,但它包含一些冗长(但至少避免了重要的代码重复):

struct Base
{
    virtual void doSomething(Output<int>& out) = 0;
    virtual void doSomething(Output<float>& out) = 0;
    virtual void doSomething(Output<double>& out) = 0;

private:
    template<typename T>
    void doSomething(Output<T>& out)
    {
        std::cout << "Base doSomething called with: " << typeid(T).name() << "\n";
    }
};

struct DerivedA : public Base
{
    void doSomething(Output<int>& out) final
    {
        doSomething<int>(out);
    }
    void doSomething(Output<float>& out) final
    {
        doSomething<float>(out);
    }
    void doSomething(Output<double>& out) final
    {
        doSomething<double>(out);
    }
private:
    template<typename T>
    void doSomething(Output<T>& out)
    {
        std::cout << "DerivedA doSomething called with: " << typeid(T).name() << "\n";
    }
};

struct DerivedB : public Base
{
    void doSomething(Output<int>& out) final
    {
        doSomething<int>(out);
    }
    void doSomething(Output<float>& out) final
    {
        doSomething<float>(out);
    }
    void doSomething(Output<double>& out) final
    {
        doSomething<double>(out);
    }
private:
    template<typename T>
    void doSomething(Output<T>& out)
    {
        std::cout << "DerivedB doSomething called with: " << typeid(T).name() << "\n";
    }
};

有没有人有更好的想法,我可以如何做到这一点,而不必一遍又一遍地重新定义相同的功能?理想情况下,它会在基类中定义一次,CRTP 似乎没有帮助。动态转换似乎是另一个明智的选择。

【问题讨论】:

  • @bipll 你能详细说明一下吗?我通读了解释,但它适用于不同的类,而不是函数。我的模板不在课堂上,而是在功能上。概述的用例似乎也不同意我正在做的事情。我基本上是在尝试避免代码重复,同时拥有虚拟功能。我可以很好地定义 doSomethingFloat、doSomethingInt、doSomethingDouble 并使用虚函数,但是我每次都必须在 3 个不同的地方修改代码。宏可以做,但看起来不优雅。
  • @NellieDanielyan 并没有真正解决我的问题。我不想存储数据。我希望减少代码重复和冗长,请参阅我的第二次编辑(顺便说一句,在你确定它是重复的之前就可以使用它,这让我相信你甚至没有阅读它)。

标签: c++ templates virtual-functions


【解决方案1】:

试试这样的:

struct OutputBase
{
    virtual void doSomething() = 0;
};

template<class T >
struct Output : public OutputBase
{
    virtual void doSomething()
    {
        std::cout << typeid(T).name();
    }
};


struct Base
{
    virtual void doSomething(OutputBase* out) = 0;
};

struct DerivedA : public Base
{
    virtual void doSomething(OutputBase* out)
    {
        std::cout << "DerivedA doSomething called with: ";
        out->doSomething();
        std::cout<< std::endl;
    }
};

struct DerivedB : public Base
{
    virtual void doSomething(OutputBase* out)
    {
        std::cout << "DerivedB doSomething called with: ";
        out->doSomething();
        std::cout << std::endl;
    }
};
int main()
{
    OutputBase* out_int = new Output < int > ;
    OutputBase* out_double = new Output < double >;
    Base* a = new DerivedA;
    a->doSomething(out_int);
    a->doSomething(out_double);
    Base* b = new DerivedB;
    b->doSomething(out_int);
    b->doSomething(out_double);

    return 0;
}

如果不想更改输出,可以使用包装器。

【讨论】:

  • 这不是我想要达到的目标。输出只是由相应的 doSomething 方法执行的计算的容器,您可能会注意到它在我的原始实现中没有方法,如果我愿意,我什至可以将它作为 void* 传递。执行大部分工作的是派生类中的 doSomething,它们需要知道类型 T。但是说 int8、int16、int32、int64 的所有计算都是完全相同的,而不是重复代码 4 次,我想对其进行模板化并完成它,但随后它破坏了虚拟功能。我知道这可能是..
  • ...用宏完成,但它可能是最不优雅的变体,而且也很容易出错。
  • @lightxbulb 在 doSomething 方法中使用的类型 T 到底是什么?
  • 大部分是数学计算。所以 T 可以是 double、float、int8/16/32/64、uint8/16/32/64,也可以是复数。它实际上是一个数学算法实现。在某些情况下,该函数可能需要在整数和浮点数等之间进行拆分。无论哪种方式,我都在寻找一种语言/设计结构,可以在编辑中消除一些冗长的代码。我相信这是一个很好的最小示例,实际上并不需要您了解内部结构。人们似乎想要做的是修改我的问题的最初表述。
猜你喜欢
  • 2012-10-08
  • 1970-01-01
  • 1970-01-01
  • 2011-02-25
  • 2023-04-10
  • 1970-01-01
  • 1970-01-01
  • 2016-09-28
  • 2011-12-19
相关资源
最近更新 更多