我正在创建一个哈希表。哈希表的节点存储一个KEY,一个VALUE和一个标志(是否使用):
template <typename KEY, typename VALUE>
struct Node {
union { KEY key; };
union { VALUE value; };
bool used;
Node() { }
};
key
和value
是一个联合体,因为它们仅在实际使用Node
时创建(这很重要)。
现在,假设KEY或VALUE中有一些填充。明智的做法是将used
放入填充区域(这样Node
可以更小)。
是否可以(自动)执行此操作?
如果通常不可能,如果KEY或VALUE带有尾部填充,是否可以这样做?
注意,我已经知道如何利用尾部填充,但是它具有未定义的行为。基本上,这个想法是从KEY或VALUE派生的,然后将bool used
放在那里(在这种情况下,如果对象具有非标准布局,当前的编译器会将bool used
放在结尾)。但不幸的是,used
只能在实际创建KEY或VALUE(new
)之前才能使用-这是一个问题,因为创建空节点时,KEY或VALUE都不会创建(这是一个打开地址哈希表,存在空节点。
注2:仅当没有特殊的 empty KEY或VALUE值时,才使用这种方法。如果KEY或VALUE有一个特殊的 empty 值,那么,我当然不需要使用单独的bool used
。
答案 0 :(得分:1)
因此,我对此进行了进一步的思考,并意识到,尽管您无法在存储的对象中使用填充,但是您可以尝试安排Node
的成员,使其占用的空间最小。 。当然,您可以对它进行硬编码,但是我认为一种更优雅的方法是使其自动化。
要找到最小的布置,我们需要生成所有可能的布置并选择最小的布置。生成所有排列都可以通过元函数permutations
完成,该函数生成一堆类型的所有排列:
template <typename P1, typename P2>
struct merge {};
template <template <typename...> class P, typename... Ts, typename... Us>
struct merge<P<Ts...>, P<Us...>> {
using type = P<Ts..., Us...>;
};
template <typename T, typename P>
struct prepend {};
template <typename T, template <typename...> class P, typename... Packs>
struct prepend<T, P<Packs...>> {
using type = P<typename merge<P<T>, Packs>::type...>;
};
// N is the number of rotations to go
template <std::size_t N, typename Pack, typename = void>
struct permutations_impl {};
template <template <typename...> class P, typename... Ts>
struct permutations_impl<0, P<Ts...>> {
// All rotations done, break the recursion
using type = P<>;
};
template <std::size_t N, template <typename...> class P, typename T>
struct permutations_impl<N, P<T>> {
using type = P<P<T>>;
};
template <std::size_t N, template <typename...> class P, typename F, typename... Rest>
struct permutations_impl<N, P<F, Rest...>, std::enable_if_t<(sizeof...(Rest) && N != 0)>> {
using PermuteRest = typename permutations_impl<sizeof...(Rest), P<Rest...>>::type;
using NextRotation = typename permutations_impl<N-1, P<Rest..., F>>::type;
using type = typename merge<typename prepend<F, PermuteRest>::type, NextRotation>::type;
};
template <typename Pack>
struct permutations {};
template <template <typename...> class P, typename... Ts>
struct permutations<P<Ts...>> {
using type = typename permutations_impl<sizeof...(Ts), P<Ts...>>::type;
};
要选择所有排列中的最小排列,我们可以执行以下操作:
template <typename Pack>
struct min_size_element {
static constexpr std::size_t min_index(std::initializer_list<std::size_t> l) {
std::size_t min = *l.begin();
std::size_t idx = 0, result = 0;
for(auto it = l.begin(); it != l.end(); ++it, ++idx) {
if(*it < min) min = *it, result = idx;
}
return result;
}
};
template <typename... Ts>
struct min_size_element<std::tuple<Ts...>> : min_size_element<void> {
using type = std::tuple_element_t<min_index({sizeof(Ts)...}), std::tuple<Ts...>>;
};
template <typename... Ts>
struct smallest_tuple {
using tuples = typename permutations<std::tuple<Ts...>>::type;
using type = typename min_size_element<tuples>::type;
};
然后,Node
类将把键,值和已用标志存储在smallest_tuple
选择的元组中。元素需要按类型访问(因为我们不知道它们的索引),因此键和值元素需要包装在唯一的包装器类型中。 Node
类的实现,其中包含用于键和值的包装器和访问器,如下所示:
template <typename K, typename V>
class Node {
union KeyWrap {
struct{} _;
K key;
constexpr KeyWrap() : _() {}
};
union ValueWrap {
struct{} _;
V value;
constexpr ValueWrap() : _() {}
};
// Need to wrap key and value types because tuple elements are accessed by type
using Storage = typename detail::smallest_tuple<bool, KeyWrap, ValueWrap>::type;
Storage mStorage;
public:
constexpr Node() = default;
~Node() {
if(this->used()) {
this->key().~K();
this->value().~V();
}
}
Node& operator=(std::pair<K, V> entry) {
if(this->used()) {
this->key() = std::move(entry.first);
this->value() = std::move(entry.second);
} else {
new(&std::get<KeyWrap>(mStorage).key) K(std::move(entry.first));
new(&std::get<ValueWrap>(mStorage).key) V(std::move(entry.second));
std::get<bool>(mStorage) = true;
}
return *this;
}
bool used() const {
return std::get<bool>(mStorage);
}
K& key() {
assert(this->used());
return std::get<KeyWrap>(mStorage).key;
}
V& value() {
assert(this->used());
return std::get<ValueWrap>(mStorage).value;
}
};
我还尝试自动检测类型是否具有某些填充,但是不能可靠地做到这一点,并且仅针对可以派生的类型。看看我的答案here。