如何在不增加sizeof的情况下将布尔值添加到结构中(如果结构中存在填充)?

时间:2018-11-05 18:20:20

标签: c++ hashtable

我正在创建一个哈希表。哈希表的节点存储一个KEY,一个VALUE和一个标志(是否使用):

template <typename KEY, typename VALUE>
struct Node {
    union { KEY key; };
    union { VALUE value; };
    bool used;

    Node() { }
};

keyvalue是一个联合体,因为它们仅在实际使用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

1 个答案:

答案 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;
    }
};

Live demo


我还尝试自动检测类型是否具有某些填充,但是不能可靠地做到这一点,并且仅针对可以派生的类型。看看我的答案here