如何在同一成员变量中存储不同的专用模板类
How to store different specialized template classes in the same member variable?
假设我有一个类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*
,并添加一些额外的方法以方便/安全 - 我们是理智的,永远不会比较不同类型集合的迭代器。结果将是不可预测的
- 我们不需要检查
nullptr
s的指针(好吧,在我的代码示例中,它不会带来更多的值,当然在现实世界中你总是需要的)
该解决方案能够使用非常量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;
}
- .cpp和.h文件中的模板专用化声明
- 将字符串存储在c++中的稳定内存中
- std::原子加载和存储都需要吗
- C++:将控制台输出存储在宏中更好吗
- 调用专用模板时出错"no matching function for call to [...]"
- 模板专用化(按容器):value_type
- 使用QProcess执行命令,并将结果存储在QStringList中
- 访问存储在向量C++中的结构的多态成员
- 如何从存储在std::映射中的std::集中删除元素
- 存储模板类型以强制转换回派生<T>
- 类型总是使用其大小存储在内存中吗
- 当字符串存储在变量中时,如何将字符串转换为wchar_t
- 使用无符号字符数组有效存储内存
- 如何在cpp.中使用协议缓冲区存储大缓冲区/数组(char/int)
- 使用 pqxx 将 std::vector 存储在 postgresql 中,并从数据库中检索它
- 带结构的二维矢量:如何存储元素
- 如何为静态常量模板化专用整数值分配存储
- 显式模板专用化不能具有存储类 - 成员方法专用化
- 如何在同一成员变量中存储不同的专用模板类
- 使用Win32 API列出存储在资源专用库(DLL)中的消息id和符号名称