继承vs类模板特化。设计老兄

时间:2016-08-29 12:45:41

标签: c++ templates inheritance

我遇到了实施设计问题。我希望你能帮助我。假设我有以下课程

class A
{
public:
    vector<int> v() const { return m_v; }
    bool isValid() const { return m_v.size() > m_components; }
    int operator [] (const int index) const { return m_v[index]; }
    ...
private:
    vector<int> m_v;
    int m_components;
}

现在我希望m_v向量可以是不同类型的,所以我可以模板化这个类:

template<typename T>
class A
{
public:
    vector<T> v() const { return m_v; }
    T operator [] (const int index) const { return m_v[index]; }
    ...
private:
    vector<T> m_v;
    int m_components;
}

但是,我意识到,当T类型为例如double时,我需要扩展类A并添加更多属性,例如另一个vector<bool> m_foo;并更改几个应该使用这些新属性的方法。

这是我怀疑的地方。我想我有几个选择:

选项1:我可以使用所有常用方法实现非模板化基类A,并为每种不同类型派生出几个类(具有自己的类)特定的类属性和方法实现),即:Aint, Adouble, Afloat。此选项要求vector<...> m_v;存储在每个派生类中,因此我必须多次复制所有相同的代码才能访问每个派生类中的m_v;属性。在示例中,此类方法仅为v()operator []isValid(),但在实际问题中还有更多。

选项2:模板专精。我可以为每种类型专门化类模板,因此只提供根据T类型更改的特定方法的实现。但是,这会强制在模板类中存储大量内容,仅在T属于特定类型时使用,即m_foo向量仅在T类型时使用double(在提议的示例中)。因此,我在浪费记忆力。此外,实现模板类并为几乎大多数模板类型提供模板类专门化并存储仅用于特定类型的特定属性似乎不是很优雅甚至是连贯的。

我不知道我是否能够很好地解释我的问题。希望如此。

提前谢谢你。 哈维尔。

3 个答案:

答案 0 :(得分:0)

这种取决于。

一般的经验法则是问自己“ADouble是a-a A”。当那些有一个is-a关系时,你应该使用继承。

但是你也有类型依赖,这实际上并不是一个“is-a”的关系。

您还可以使用这两个选项:拥有一个具有通用功能的基类,该基类具有模板参数并且具有需要添加的子类。所以你不需要重新实现所有类型的dependend函数

所以:

template<typename T>
class A
{
public:
    vector<T> v() const { return m_v; }
    T operator [] (const int index) const { return m_v[index]; }
    ...
private:
    vector<T> m_v;
    ...
};

class ADouble : public A<double>
{
    ...
};
Btw:为什么你认为模板会占用更多内存?

答案 1 :(得分:0)

使用子操作部分专门化您的类中的特定操作(不专门化整个类)。

#include <vector>

namespace detail
{
  // general concept of indexing into something
  template<class T> struct index_operation;

  // indexing into most vectors
  template <class T> struct index_operation<std::vector<T>>
  {
    T& operator()(std::vector<T>& v, std::size_t i) const
    {
      return v[i];
    }
    T const& operator()(std::vector<T> const& v, std::size_t i) const
    {
      return v[i];
    }
  };

  // indexing into a vector<bool>
  template <> struct index_operation<std::vector<bool>>
  {
    std::vector<bool>::reference operator()(std::vector<bool>& v, std::size_t i) const
    {
      return v[i];
    }
    std::vector<bool>::const_reference operator()(std::vector<bool> const& v, std::size_t i) const
    {
      return v[i];
    }
  };


}

template<typename T>
class A
{
    using vector_type = std::vector<T>;

public:
    std::vector<T> v() const { return m_v; }
    decltype(auto) operator [] (const int index) const
    {
      auto op = detail::index_operation<vector_type>();
      return op(m_v, index);
    }
private:
    std::vector<T> m_v;
};

答案 2 :(得分:0)

我会尝试用一个与我的真实问题密切相关的例子来表达这个问题,尽管它会变得更长。

选项1:根据模板和模板专业化考虑以下类实现。

template<typename T>
class A
{
public:
   A() {}
   vector<T> v() const { return m_v; }
   bool isValid() const  { return m_v.size() >= m_components; }
   T  operator [] (const int i) const { return m_v[i]; }
   T& operator [] (const int i)       { return m_v[i]; }
   int components() const { return m_components; }
   double value() const { return m_value; }
   void method1();
private:
   vector<T> m_v;
   int m_components;
   double m_value;
   vector<bool> m_indices;    // this is only used when T is int
   map<int, char> m_map;      // this is only used when T is double
   queue<int> m_queue;        // this is only used when T is bool
};

template<>
void A<int>::method1()
{
   for (int i = 0; i < m_components; ++i) m_v.push_back(i);
   // stuff only for int case
   for (int i = 0; i < m_components; ++i) { i % 2 == 0 ? m_indices[i] = true : m_indices[i] = false; }
}
template<>
void A<double>::method1()
{
   for (int i = 0; i < m_components; ++i) m_v.push_back(i);
   // stuff only for double case
   for (int i = 0; i < m_components; ++i) { i % 2 == 0 ? m_map[i] = 'e' : m_map[i] = 'o'; }
}
template<>
void A<bool>::method1()
{
   for (int i = 0; i < m_components; ++i) m_v.push_back(i);
   // stuff only for bool case
   for (int i = 0; i < m_components; ++i) { i % 2 == 0 ? m_queue.push(1) : m_queue.push(0); }
}

正如您所看到的,无论T类型如何,都有几种常见的方法,并涉及使用m_v向量(方法:v()isValid()operator[])。但是,还有其他方法(method1())具有取决于T类型的特定实现,并且还需要根据此类型使用特定数据结构(queues,{{ 1}},maps)。我看到类中队列,地图,向量等的定义非常非常难看,尽管它们仅用于具体情况,具体取决于vectors类型。

选项2:另一种选择:

T

正如您所看到的,在这种情况下,特定于类型的数据结构(class A { public: A() {} int components() const { return m_components; } double value() const { return m_value; } virtual void method1() == 0; protected: int m_components; double m_value; }; /***** Derived A for int case ****/ class Aint : public A { public: Aint() {} vector<int> v() const { return m_v; } bool isValid() const { return m_v.size() >= m_components; } int operator [] (const int i) const { return m_v[i]; } int& operator [] (const int i) { return m_v[i]; } void method1(); private: vector<int> m_v; vector<bool> m_indices; }; void Aint::method1() { for (int i = 0; i < m_components; ++i) m_v.push_back(i); for (int i = 0; i < m_components; ++i) { i % 2 == 0 ? m_indices[i] = true : m_indices[i] = false; } } /***** Derived A for double case ****/ class Adouble : public A { public: Adouble() {} vector<double> v() const { return m_v; } bool isValid() const { return m_v.size() >= m_components; } double operator [] (const int i) const { return m_v[i]; } double& operator [] (const int i) { return m_v[i]; } void method1(); private: vector<double> m_v; map<int, char> m_map; }; void Adouble::method1() { for (int i = 0; i < m_components; ++i) m_v.push_back(i); for (int i = 0; i < m_components; ++i) { i % 2 == 0 ? m_map[i] = 'e' : m_map[i] = 'o'; } } /***** Derived A for bool case ****/ class Abool : public A { public: Abool() {} vector<bool> v() const { return m_v; } bool isValid() const { return m_v.size() >= m_components; } bool operator [] (const int i) const { return m_v[i]; } bool& operator [] (const int i) { return m_v[i]; } void method1(); private: vector<bool> m_v; queue<int> m_map; }; void Abool::method1() { for (int i = 0; i < m_components; ++i) m_v.push_back(i); for (int i = 0; i < m_components; ++i) { i % 2 == 0 ? m_queue.push(1) : m_queue.push(0); } } queues等)仅针对其所需案例进行定义(不在的常规类模板中)选项1 )。但是,现在应该在每个派生类中定义maps向量,因为它的特定类型。因此,访问和操作向量的东西应该总是在所有派生类中复制,尽管它们总是相同的(方法m_vv()isValid()等)。它似乎也没有很好的设计。

为此目的最好的设计是什么? 谢谢