模板类对象的数组

时间:2020-10-08 19:20:45

标签: c++ templates c++17 derived-class

问题

我想要一个指向模板类实例的指针数组。如果C ++允许在基类中使用模板化的派生类进行模板化的虚拟方法,则将解决我的问题。

因此,如何实现模板化虚拟方法?

下面我有一个可行的解决方案,但是我对自己的实现方式感兴趣。

约束

template参数是无限可变的,例如,我无法枚举此模板类的每个特殊化。模板类T可以是任何POD,POD数组或POD的结构。

T的完整集合在编译时是已知的。基本上,我有一个文件,该文件定义了用于实例化对象的所有不同T,并使用Xmacros(https://en.wikipedia.org/wiki/X_Macro)创建对象数组。

我知道这不是一个好主意。让我们暂时忽略一下。最终变得更加好奇。

可能的解决方案

这些是我研究过的东西。

创建基类和派生类

class Base {
  virtual void SomeMethod() = 0;
}

template <class T>
class Derived : Base {
  void SomeMethod() {...}
}

问题是我无法声明Base中要重载的所有虚拟方法,因为无法对虚拟方法进行模板化。否则,这将是一个完美的解决方案。

std :: any / std :: variant

我正在使用C ++ 17,因此可以使用std::any定义虚拟基本方法。但是它不能保存数组,因此不能在这里使用。

CRTP

看来这无助于我创建这些不同对象的数组。我需要做类似的事情

template <typename D, typename T>
class Base
{
    ...
};

template <typename T>
class Derived : public Base<Derived, T>
{
    ...
};

所以我仍然最终尝试创建Derived<T>对象的数组。

访客模式

再次看来,我需要枚举Visitable类需要服务的每种可能的类型,尽管并非不可能(再次,我有一个文件定义了所有不同的T似乎更像Xmacros,这只会使问题变得更加复杂。

我的解决方案

这是我想出的。它将在https://www.onlinegdb.com/online_c++_compiler

中运行
#include <iostream>
#include <array>
#include <typeinfo>

// Base class which declares "overloaded" methods without implementation
class Base {
 public:
  template <class T>
  void Set(T inval);
  template <class T>
  void Get(T* retval);
  virtual void Print() = 0;
};

// Template class which implements the overloaded methods
template <class T>
class Derived : public Base {
 public:
  void Set(T inval) {
    storage = inval;
  }
  void Get(T* retval) {
    *retval = storage;
  }
  void Print() {
    std::cout << "This variable is type " << typeid(T).name() <<
      ", value: " << storage << std::endl;
  }
 private:
  T storage = {};
};

// Manually pointing base overloads to template methods
template <class T> void Base::Set(T inval) {
  static_cast<Derived<T>*>(this)->Set(inval);
}
template <class T> void Base::Get(T* retval) {
  std::cout << "CALLED THROUGH BASE!" << std::endl;
  static_cast<Derived<T>*>(this)->Get(retval);
}

int main()
{
  // Two new objects
  Derived<int>* ptr_int = new Derived<int>();
  Derived<double>* ptr_dbl = new Derived<double>();
  
  // Base pointer array
  std::array<Base*, 2> ptr_arr;
  ptr_arr[0] = ptr_int;
  ptr_arr[1] = ptr_dbl;

  // Load values into objects through calls to Base methods
  ptr_arr[0]->Set(3);
  ptr_arr[1]->Set(3.14);

  // Call true virtual Print() method
  for (auto& ptr : ptr_arr) ptr->Print();

  // Read out the values
  int var_int;
  double var_dbl;
  std::cout << "First calling Get() method through true pointer." << std::endl;
  ptr_int->Get(&var_int);
  ptr_dbl->Get(&var_dbl);
  std::cout << "Direct values: " << var_int << ", " << var_dbl << std::endl;
  std::cout << "Now calling Get() method through base pointer." << std::endl;
  ptr_arr[0]->Get(&var_int);
  ptr_arr[1]->Get(&var_dbl);
  std::cout << "Base values: " << var_int << ", " << var_dbl << std::endl;

  return 0;
}

运行此命令时,它表明在Base上调用方法正确指向Derived实现。

This variable is type i, value: 3                                                                                                    
This variable is type d, value: 3.14                                                                                                 
First calling Get() method through true pointer.                                                                                     
Direct values: 3, 3.14                                                                                                               
Now calling Get() method through base pointer.                                                                                       
CALLED THROUGH BASE!                                                                                                                 
CALLED THROUGH BASE!                                                                                                                 
Base values: 3, 3.14  

基本上,我是在手动创建虚拟方法指针。但是,由于我明确地这样做,因此允许我使用Base中的模板方法,该模板方法指向Derived中的方法。更容易出错,例如,对于每个模板方法,我需要两次键入方法名称,即,我可能会弄乱:

template <class T> void Base::BLAH_SOMETHING(T inval) {
  static_cast<Derived<T>*>(this)->WHOOPS_WRONG_CALL(inval);
}

因此,这毕竟是一个可怕的主意吗?对我来说,这似乎可以达到我避免模板化虚拟方法局限性的目标。这真的有什么问题吗?我知道,可能有一些方法可以使所有不必要的代码结构化,我只是专注于这种特定的结构。

2 个答案:

答案 0 :(得分:1)

它更容易出错,例如,对于每个模板方法,我需要两次键入方法名称

哦,这是您最不用担心的。想象一下,如果您将类型转换为错误的类型。

至少可以避免头痛,并使用dynamic_cast

class Base {
  public:
    virtual ~Base() = default;

    template <class T>
    void Set(T inval) {
        dynamic_cast<Derived<T>&>(*this).Set(inval);
    }

    template <class T>
    T Get() {
        return dynamic_cast<Derived<T>&>(*this).Get();
    }
};


template <class T>
class Derived : public Base {
  public:
    void Set(T inval) {
      storage = inval;
    }

    T Get() {
      return storage;
    }

  private:
    T storage{};
};

除此之外,我同意这些评论,这可能不是解决您问题的正确方法。

答案 1 :(得分:0)

处理包含未知类型的子类的常规方法是将整个对象移至虚拟函数。因此,代替

superclass->get_value(&variable_of_unknown_type);
print(variable_of_unknown_type);

您写

superclass->print_value();

现在,您无需了解子类可能包含的任何类型。

但这并不总是适当的,因为可能会有很多操作。如果您一直都在增加新的操作,那么使每个操作成为虚拟函数会很麻烦。另一方面,可能的子类的集合通常受到限制。在这种情况下,最好的选择是Visitor。可以这么说,访客将继承层次结构旋转90°。您无需修复操作集并自由添加新的子类,而是可以修复子集集并自由添加新的操作。所以代替

superclass->print_value();

您写

class PrinterVisitor : public MyVisitor
{
   virtual void processSubclass1(Subclass1* s) { print(s->double_value); }
   virtual void processSubclass2(Subclass2* s) { print(s->int_value); }
};

superclass->accept(PrinterVisitor());

现在accept是基类中唯一的virtual函数。请注意,没有强制转换可能在代码中的任何地方失败。