मेरे पास एक घटक है जिसका उपयोग मैं निम्न-स्तरीय जेनेरिक प्रकारों को लागू करते समय करता हूं जो मनमाने प्रकार की वस्तु को स्टोर करते हैं (हो सकता है या वर्ग प्रकार न हो) जो खाली आधार अनुकूलन का लाभ लेने के लिए खाली हो सकता है :
template <typename T, unsigned Tag = 0, typename = void>
class ebo_storage {
T item;
public:
constexpr ebo_storage() = default;
template <
typename U,
typename = std::enable_if_t<
!std::is_same<ebo_storage, std::decay_t<U>>::value
>
> constexpr ebo_storage(U&& u)
noexcept(std::is_nothrow_constructible<T,U>::value) :
item(std::forward<U>(u)) {}
T& get() & noexcept { return item; }
constexpr const T& get() const& noexcept { return item; }
T&& get() && noexcept { return std::move(item); }
};
template <typename T, unsigned Tag>
class ebo_storage<
T, Tag, std::enable_if_t<std::is_class<T>::value>
> : private T {
public:
using T::T;
constexpr ebo_storage() = default;
constexpr ebo_storage(const T& t) : T(t) {}
constexpr ebo_storage(T&& t) : T(std::move(t)) {}
T& get() & noexcept { return *this; }
constexpr const T& get() const& noexcept { return *this; }
T&& get() && noexcept { return std::move(*this); }
};
template <typename T, typename U>
class compressed_pair : ebo_storage<T, 0>,
ebo_storage<U, 1> {
using first_t = ebo_storage<T, 0>;
using second_t = ebo_storage<U, 1>;
public:
T& first() { return first_t::get(); }
U& second() { return second_t::get(); }
// ...
};
template <typename, typename...> class tuple_;
template <std::size_t...Is, typename...Ts>
class tuple_<std::index_sequence<Is...>, Ts...> :
ebo_storage<Ts, Is>... {
// ...
};
template <typename...Ts>
using tuple = tuple_<std::index_sequence_for<Ts...>, Ts...>;
हाल ही में मैं लॉक-फ्री डेटा संरचनाओं के साथ खिलवाड़ कर रहा हूं और मुझे नोड्स की आवश्यकता है जिसमें वैकल्पिक रूप से एक लाइव डेटम शामिल है। एक बार आवंटित होने के बाद, नोड्स डेटा संरचना के जीवनकाल के लिए रहते हैं लेकिन निहित डेटा केवल जीवित है जबकि नोड सक्रिय है और नोड नहीं है, जबकि नोड एक मुक्त सूची में बैठता है। मैंने कच्चे भंडारण और प्लेसमेंट का उपयोग करते हुए नोड्स लागू किया है new
:
template <typename T>
class raw_container {
alignas(T) unsigned char space_[sizeof(T)];
public:
T& data() noexcept {
return reinterpret_cast<T&>(space_);
}
template <typename...Args>
void construct(Args&&...args) {
::new(space_) T(std::forward<Args>(args)...);
}
void destruct() {
data().~T();
}
};
template <typename T>
struct list_node : public raw_container<T> {
std::atomic<list_node*> next_;
};
जो सब ठीक है और बांका है, लेकिन T
खाली होने पर प्रति नोड मेमोरी के एक पॉइंटर के आकार का कचरा बर्बाद करता है: एक बाइट के लिए raw_storage<T>::space_
, और sizeof(std::atomic<list_node*>) - 1
संरेखण के लिए पैडिंग के बाइट्स। यह EBO का लाभ उठाने और के अप्रयुक्त एकल-बाइट प्रतिनिधित्व आवंटित करने के लिए अच्छा होगा raw_container<T>
के ऊपर list_node::next_
।
एक raw_ebo_storage
प्रदर्शन "मैनुअल" EBO बनाने में मेरा सबसे अच्छा प्रयास :
template <typename T, typename = void>
struct alignas(T) raw_ebo_storage_base {
unsigned char space_[sizeof(T)];
};
template <typename T>
struct alignas(T) raw_ebo_storage_base<
T, std::enable_if_t<std::is_empty<T>::value>
> {};
template <typename T>
class raw_ebo_storage : private raw_ebo_storage_base<T> {
public:
static_assert(std::is_standard_layout<raw_ebo_storage_base<T>>::value, "");
static_assert(alignof(raw_ebo_storage_base<T>) % alignof(T) == 0, "");
T& data() noexcept {
return *static_cast<T*>(static_cast<void*>(
static_cast<raw_ebo_storage_base<T>*>(this)
));
}
};
जिसके वांछित प्रभाव हैं:
template <typename T>
struct alignas(T) empty {};
static_assert(std::is_empty<raw_ebo_storage<empty<char>>>::value, "Good!");
static_assert(std::is_empty<raw_ebo_storage<empty<double>>>::value, "Good!");
template <typename T>
struct foo : raw_ebo_storage<empty<T>> { T c; };
static_assert(sizeof(foo<char>) == 1, "Good!");
static_assert(sizeof(foo<double>) == sizeof(double), "Good!");
लेकिन कुछ अवांछनीय प्रभाव भी, मैं सख्त अलियासिंग (3.10 / 10) के उल्लंघन के कारण मानता हूं, हालांकि "किसी वस्तु के संग्रहीत मूल्य तक पहुंच" का अर्थ खाली प्रकार के लिए विवादास्पद है:
struct bar : raw_ebo_storage<empty<char>> { empty<char> e; };
static_assert(sizeof(bar) == 2, "NOT good: bar::e and bar::raw_ebo_storage::data() "
"are distinct objects of the same type with the "
"same address.");
यह समाधान निर्माण पर अपरिभाषित व्यवहार के लिए भी संभावित है। कुछ बिंदु पर कार्यक्रम को प्लेसमेंट के साथ कच्चे भंडारण के भीतर आवश्यक वस्तु का निर्माण करना चाहिए new
:
struct A : raw_ebo_storage<empty<char>> { int i; };
static_assert(sizeof(A) == sizeof(int), "");
A a;
a.value = 42;
::new(&a.get()) empty<char>{};
static_assert(sizeof(empty<char>) > 0, "");
याद रखें कि खाली होने के बावजूद, एक पूर्ण वस्तु का गैर-शून्य आकार होना आवश्यक है। दूसरे शब्दों में, एक खाली पूर्ण वस्तु में एक मूल्य प्रतिनिधित्व होता है जिसमें एक या एक से अधिक पैडिंग बाइट्स होते हैं। new
निर्माण पूर्ण वस्तुएं हैं, इसलिए एक अनुरूप कार्यान्वयन उन पैडिंग बाइट्स को स्मृति से अछूता छोड़ने के बजाय निर्माण पर मनमाने मूल्यों के लिए सेट कर सकता है जैसा कि खाली बेस सबोबिज के निर्माण के मामले में होगा। यह निश्चित रूप से विनाशकारी होगा यदि उन पैडिंग बाइट्स अन्य जीवित वस्तुओं को ओवरले करते हैं।
तो सवाल यह है कि क्या एक मानक-अनुरूप कंटेनर वर्ग बनाना संभव है जो निहित वस्तु के लिए कच्चे भंडारण / विलंबित आरंभीकरण का उपयोग करता है और निहित वस्तु के प्रतिनिधित्व के लिए स्मृति स्थान को बर्बाद करने से बचने के लिए ईबीओ का लाभ उठाता है?