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

How to store different specialized template classes in the same member variable?

本文关键字:专用 存储 成员 变量      更新时间:2023-10-16

假设我有一个类a,我想用自定义散列器和比较器将其存储在一个无序集合中。我还有一个容器类B,用来存储这个集合:

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

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

接下来,我尝试制作专门化集合的类:

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

显然,这没有帮助,因为我在类B中仍然没有用于设置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)。但明显的缺点是:每个集合专门化都有代码重复,我必须使用自定义迭代器来使这些集合与STL一起工作。在这里,我停下来问自己,是否有更好的方法来实现我真正想要的:在同一个成员var中存储不同的未定义集类,而不需要将该var的所有者也模板化。

主要思想

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

其主要思想是:创建一个标记集合的基类,并使其成为所有集合的基类。然后为您需要的每个模板参数显式实例化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.};
}

附言:感谢@Jarod42提供的使用语法的模板。

工作实施

已作出以下假设:

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

该解决方案能够使用非常量begin/ends并使用基于的新闪亮范围来迭代映射。见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;
}