C++虚拟模板方法

C++ Virtual template method

我有一个抽象类(我知道它不会以这种方式编译,但它是为了理解我想做什么):

1
2
3
4
5
6
7
8
9
10
11
class AbstractComputation {
    public:
        template <class T> virtual void setData(std::string id, T data);
        template <class T> virtual T getData(std::string id);
};

class Computation : public AbstractComputation {
    public:
        template <class T> void setData(std::string id, T data);
        template <class T> T getData(std::string id, T data);
};

所以当我调用setData("foodouble", data)时,我希望由foodouble标识的double(内部机制,这里不主要关注)设置为double数据。

那么怎么做呢?

我认为输入类似于virtual void setData(std::string id, double data)的东西可能有一种手段,但我不知道该怎么做。


问题是,不能轻易地将静态时间多态性(模板)与运行时多态性混合在一起。该语言不允许在示例中使用特定构造的原因是可能存在无限多的不同类型,这些类型可以实例化模板成员函数,而这又意味着编译器必须生成代码来动态地调度这些类型,这是不可行的。

这里可以做不同的事情来绕过这个限制,基本上要么去掉静态多态性,要么去掉动态多态性。从方程中删除动态多态性可以通过提供一个不是从中派生的类型来完成,以存储映射,然后提供一个模板来解决这一问题,该模板仅在基本级别:

1
2
3
4
5
6
7
8
9
10
11
12
13
class AbstractComputation {
public:
   template <typename T>
   void setData( std::string const & id, T value ) {
      m_store.setData( id, value );
   }
   template <typename T>
   T getData( std::string const & id ) const {
      return m_store.getData<T>( id );
   }
protected:
   ValueStore m_store;
};

现在派生类可以从基础访问ValueStore,不需要多态性。(这也可以通过直接在AbstractComputation中实现功能来实现,但分离关注点可能是有意义的)

另一种选择是维护运行时多态性,但删除静态多态性。这可以通过在基类上执行类型擦除,然后分派到接受类型擦除参数的相应(非模板化)函数来完成。最简单的版本是使用boost::any

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class AbstractComputation {
public:
   template <typename T>
   void setData( std::string const & id, T value ) {
      setDataImpl( id, boost::any( value ) );
   }
   template <typename T>
   T getData( std::string const & id ) const {
      boost::any res = getDataImpl( id );
      return boost::any_cast<T>( res );
   }
protected:
   virtual void setDataImpl( std::string const & id, boost::any const & value ) = 0;
   virtual boost::any getDataImpl( std::string const & id ) const = 0;
};

类型擦除是如何在引擎盖下实现的是有趣的,但在这里,重要的部分是boost::any是一种具体的(非模板化的)类型,可以通过在参数上使用类型擦除在内部存储任何类型,同时允许对数据进行类型安全的检索。


在某些情况下,将模板从方法级别移动到类级别可能就足够了,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>

template<typename T>
class AbstractComputation {
public:
    virtual void setData(std::string id, T data)
    {
        std::cout <<"base" << std::endl;
    }
};

template<typename T>
class Computation : public AbstractComputation<T> {
public:
    virtual void setData(std::string id, T data)
    {
        std::cout <<"derived" << std::endl;
    }
};

int main()
{
    AbstractComputation<int> *x = new Computation<int>();

    x->setData("1", -1);

    delete x;
    return 0;
}


首先,您不能使用virtual模板函数。由于在编译时解决了模板问题,因此virtual将无法工作,因为编译器不知道要选择哪个模板。请参阅此处,了解更多有关此的信息。


如果预先知道可能的类型列表,预处理器可能会帮助:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#define MY_CLASSES MYTYPE(int) MYTYPE(float) MYTYPE(double)

class AbstractComputation {
    public:
#     define MYTYPE(T) virtual void setData(std::string id, T data)=0;\
                       virtual void getData(std::string id, T& dst_data)=0;

      MY_CLASSES
#     undef MYTYPE
};

class Computation : public AbstractComputation {
    public:
#     define MYTYPE(T) virtual void setData(std::string id, T data){std::cout<<"writing:"<<data<<std::endl;}\
                       virtual void getData(std::string id, T& dst_data){dst_data=0;/*put your actual implementation here*/}

      MY_CLASSES
#     undef MYTYPE
};

如果您不知道可能的类型的完整列表,那么您的问题可能无法解决。如其他人提到的,类型擦除也可能有帮助。但并非在所有情况下。


您可以在您的案例中使用boost::any

1
virtual void setData(std::string id, boost::any data);

它是一种可以封装几乎任何东西的包装器。

有关此答案中类似主题的详细信息。


使用boost::any接受数据,然后在实际设置时,从中获取正确的类型。