首页 > 解决方案 > 模板类对象数组

问题描述

问题

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

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

下面我有一个似乎可行的解决方案,但我对有关我的实现的评论感兴趣。

约束

模板参数是无限可变的,例如,我不能枚举这个模板类的每一个特化。模板类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);
}

那么,在这一切之后,这是一个可怕的想法吗?对我来说,它似乎实现了我规避模板化虚拟方法限制的目标。这真的有什么问题吗?我知道可能有一些方法来构造代码,这一切都变得不必要了,我只关注这个特定的构造。

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

解决方案


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

哦,这是你最不关心的问题。想象一下,如果您选择了错误的类型。

至少让自己头疼并使用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{};
};

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


推荐阅读