如何在同一个成员变量中存储不同的专用模板类?

时间:2016-03-25 12:42:01

标签: c++

假设我有一个A类,我希望将其存储在带有自定义hasher和comparer的unordered_set中。我还有一个容器类B来存储这个集合:

class B {
private:
  std::unordered_set<A, Hasher, Comparer> set;
};

要进行此编译,我必须使B成为模板类,我想避免,因为这会导致一些重大的重构,并且实际上将此问题移动到一层,然后我必须处理模板参数乙

接下来我尝试创建专门化集合的类:

class MySet1 : public std::unordered_set<A, MyHasher1, MyComparer1> {
};
class MySet2 : public std::unordered_set<A, MyHasher2, MyComparer2> {
};

显然这没有用,因为我仍然没有为B类中的set var提供公共基类。

为了解决这个问题,我将无序设置向下移动了一级:

class MySet {
public:
  // Some abstract functions...
};

class MySet1 : public MySet {
public:
  // Implementation of the abstract functions.
private:
  std::unordered_set<A, MyHasher1, MyComparer1> set;
};

class MySet2 : public MySet {
public:
  // Implementation of the abstract functions.
private:
  std::unordered_set<A, MyHasher2, MyComparer2> set;
};

现在我有一个B类的公共基类(MySet)。但明显的缺点是:每个集合特化的代码重复,我必须使用imlement自定义迭代器来使集合与STL一起工作。以下是我停下来问自己是否有更好的方法来实现我真正想要的东西:在同一个成员var中存储不同的unordered_set类,而不需要使该var的所有者也被模板化。

1 个答案:

答案 0 :(得分:2)

主要想法

您可以在这里愉快地使用多重继承。

主要思想是:创建一个标记集合的基类,并使其成为所有集合的基类。然后为您需要的每个模板参数显式实例化set类,创建一个从set容器和标记接口公开继承的空类。那么你将无需添加任何内容,似乎不需要重复代码。

无论如何,您需要创建一些适用于所有模板专业化的(可能是虚拟的)功能。我们需要能够在同一个上下文中使用单个变量,而不管它拥有什么。但是,您可以尝试使用更多using声明来减少一些代码,因为继承并使用隐式类型转换(例如,如果您的集合仅包含数字)。

#include <set>

class setInterface {
    /* Code common for all set template specializations
       (you have to have some common interface anyway) */
};

template <typename T> class TSet: public setInterface, std::set<T> {
    using std::set<T>::set;
    /* more using-declarations or templated versions of some functions
       You can use SFINAE here to achieve more magical results,
       or use template specializations for specific types. */
};

using intSet = TSet<int>;
using doubleSet = TSet<double>;

class B {
public:
  setInterface values;
};

int main () {
    B b;
    b.values = intSet {1, 2, 3} ;
    b.values = doubleSet {1., 2., 3.};
}

PS:感谢使用语法转到@ Jarod42的模板。

工作实施

已做出以下假设:

  • 我们将仅使用包含可转换为long long的项目的集合。我们可以在一般情况下使用void*并添加一些其他方法以方便/安全。
  • 我们是理智的,永远不会比较不同类型的集合的迭代器。结果将无法预测。
  • 我们不需要检查nullptr的指针(好吧,它不会给我的代码示例带来更多价值,确实在您需要的现实世界中)。

该解决方案能够使用非const开始/结束并使用新的基于闪亮范围的for来迭代地图。见main;编译并运行它(-std=c++14)以查看结果。

#include <set>
#include <memory>
#include <iostream>

using common_denominator_type = long long;

class setInterface {
protected:
    class iterator_impl;
public:
    class iterator {
    public:
        iterator (iterator_impl* impl) : impl (impl) {}

        iterator& operator++ () { ++*impl; return *this; };
        bool operator != (const iterator& rhs) const { return *impl != *rhs.impl; };
        common_denominator_type operator* () const { return **impl; };
    private:
        std::shared_ptr <iterator_impl> impl;
    };
    virtual iterator begin() = 0;
    virtual iterator end() = 0;
    virtual size_t size() const = 0;
protected:
    class iterator_impl {
    public:
        virtual iterator_impl& operator++ () = 0;
        virtual bool operator != (const iterator_impl&) const = 0;
        virtual common_denominator_type operator* () const = 0;
        virtual void* as_std_set_iterator () = 0;
        virtual const void* as_std_set_iterator () const = 0;
    };
};

template <typename T> class TSet: public setInterface, std::set<T> {
public:
    using std::set<T>::set;
    size_t size () const override { return std::set<T>::size(); }
    iterator begin () override { return iterator (new TSet<T>::iterator_impl (std::set<T>::begin())); }
    iterator end   () override { return iterator (new TSet<T>::iterator_impl (std::set<T>::end  ())); }
protected:
    class iterator_impl: public setInterface::iterator_impl {
    public:
        using std_it = typename std::set<T>::iterator;
        iterator_impl (std_it&& _) : m_real_iterator(std::move (_)) {}
        iterator_impl& operator++ () override { ++m_real_iterator; return *this; }
        bool operator != (const setInterface::iterator_impl& rhs) const override {
            return *reinterpret_cast <const std_it*>(as_std_set_iterator())
                    !=
                   *reinterpret_cast <const std_it*>(rhs.as_std_set_iterator());
        }
        common_denominator_type operator* () const override { return *m_real_iterator; }
        void* as_std_set_iterator () override { return &m_real_iterator; }
        const void* as_std_set_iterator () const override { return &m_real_iterator; }
    private:
        std_it m_real_iterator;
    };
};

using intSet = TSet<int>;
using longSet = TSet<long>;

class B {
public:
    std::shared_ptr <setInterface> values;
};

std::ostream& operator<< (std::ostream& str, B& b) {
    str << "[" << b.values->size() << "] [";
    for (auto i = b.values->begin(); i != b.values->end(); ++i)
        str << *i << " ";
    str << "][";
    for (auto i : *b.values)
        str << i << " ";
    return str << "]";
}

int main () {
    B b;
    b.values.reset (new intSet {1, 2, 3});
    std::cout << b << std::endl;
    b.values.reset (new longSet {10l, 20l, 30l});
    std::cout << b << std::endl;
}