कच्चे भंडारण का उपयोग करते समय ईबीओ का अनुकरण कैसे करें?


79

मेरे पास एक घटक है जिसका उपयोग मैं निम्न-स्तरीय जेनेरिक प्रकारों को लागू करते समय करता हूं जो मनमाने प्रकार की वस्तु को स्टोर करते हैं (हो सकता है या वर्ग प्रकार न हो) जो खाली आधार अनुकूलन का लाभ लेने के लिए खाली हो सकता है :

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निर्माण पूर्ण वस्तुएं हैं, इसलिए एक अनुरूप कार्यान्वयन उन पैडिंग बाइट्स को स्मृति से अछूता छोड़ने के बजाय निर्माण पर मनमाने मूल्यों के लिए सेट कर सकता है जैसा कि खाली बेस सबोबिज के निर्माण के मामले में होगा। यह निश्चित रूप से विनाशकारी होगा यदि उन पैडिंग बाइट्स अन्य जीवित वस्तुओं को ओवरले करते हैं।

तो सवाल यह है कि क्या एक मानक-अनुरूप कंटेनर वर्ग बनाना संभव है जो निहित वस्तु के लिए कच्चे भंडारण / विलंबित आरंभीकरण का उपयोग करता है और निहित वस्तु के प्रतिनिधित्व के लिए स्मृति स्थान को बर्बाद करने से बचने के लिए ईबीओ का लाभ उठाता है?


@ कोलमबो यदि कंटेनर प्रकार निहित प्रकार से प्राप्त होता है, तो कंटेनर ऑब्जेक्ट को बनाना / नष्ट करना आवश्यक रूप से निहित उप-विषय का निर्माण / विनाश करता है। निर्माण के लिए, इसका मतलब है कि आप या तो कंटेनर ऑब्जेक्ट को पूर्व-आवंटित करने की क्षमता खो देते हैं या जब तक आप एक कंटेस्टेंट का निर्माण करने के लिए तैयार नहीं हो जाते तब तक उनके निर्माण में देरी होनी चाहिए। कोई बड़ी बात नहीं है, यह सिर्फ एक और चीज को ट्रैक करने के लिए जोड़ता है - आवंटित-लेकिन-नहीं-अभी तक निर्मित कंटेनर ऑब्जेक्ट। मृत कंटेस्टेंट सबोबिज के साथ कंटेनर ऑब्जेक्ट को नष्ट करना एक कठिन समस्या है, हालांकि - आप बेस क्लास डिस्ट्रक्टर से कैसे बचते हैं?
केसी

आह, वहाँ मुझे माफ करना। यह भूल गए कि देरी से निर्माण / विनाश इस तरह संभव नहीं है और अंतर्निहित विध्वंसक कॉल।
कोलंबो

`टेम्प्लेट <टाइपनेम टी> स्ट्रक्चर एलायस (टी) रॉ_एबो_स्टोरेज_बेस <टी, std :: enable_if_t <std :: is_empty <T> :: value>>: T {}; ? With maybe more tests on T` यह सुनिश्चित करने के लिए कि यह निर्माण किया गया है ... या यह सुनिश्चित करने के लिए कि आप निर्माण Tकिए बिना निर्माण कर सकते हैं T, किसी तरह से T::T()साइड इफेक्ट्स हैं। हो सकता है कि गैर-खाली निर्माण / नष्ट करने के लिए एक लक्षण वर्ग Tकहता है कि कैसे एक निर्माण करने के लिए T?
यक्क - एडम नेवरामॉन्ट

एक और विचार: क्या ईबो स्टोरेज क्लास को उन प्रकारों की सूची में ले लिया गया है जिन्हें आपको खाली मानने की अनुमति नहीं है, क्योंकि ईबो स्टोरेज क्लास का पता इसके साथ ओवरलैप हो जाएगा यदि ऐसा होता है?
यक्क - एडम नेवरामॉन्ट

1
लाने पर आप किसी वस्तु को स्वतंत्र सूची से खींच लेंगे, उसका निर्माण कर सकते हैं, और उसे परमाणु सूची में डाल सकते हैं। आंसू पर आप एक ट्रैकिंग सूची से परमाणु को हटा रहे होंगे, एक विध्वंसक को बुलाएंगे, और फिर परमाणु को स्वतंत्र सूची में डाल देंगे। तो निर्माता और विध्वंसक पर परमाणु सूचक का उपयोग नहीं होता है और स्वतंत्र रूप से संशोधित किया जा सकता है, सही है? यदि ऐसा है, तो सवाल यह होगा: क्या आप परमाणु सूचक को space_सरणी में रख सकते हैं और सुरक्षित रूप से इसका उपयोग कर सकते हैं, जबकि यह मुफ्त सूची में शामिल नहीं है? तब space_T नहीं होगा, लेकिन T और परमाणु सूचक के आसपास कुछ आवरण होगा।
स्पीड 8 नं

जवाबों:


2

मुझे लगता है कि आपने खुद को अपनी विभिन्न टिप्पणियों में जवाब दिया:

  1. आप कच्ची मेमोरी और प्लेसमेंट नया चाहते हैं। इसके लिए कम से कम एक बाइट उपलब्ध होना आवश्यक है, भले ही आप प्लेसमेंट नई के माध्यम से एक खाली वस्तु का निर्माण करना चाहते हों।
  2. आप किसी भी खाली वस्तुओं को संग्रहीत करने के लिए शून्य बाइट्स ओवरहेड चाहते हैं।

ये आवश्यकताएं स्वयं विरोधाभासी हैं। इसलिए उत्तर नहीं है , यह संभव नहीं है।

आप अपनी आवश्यकताओं को थोड़ा और बदल सकते हैं, हालांकि, केवल खाली, तुच्छ प्रकार के लिए शून्य बाइट ओवरहेड की आवश्यकता होती है।

आप एक नए वर्ग गुण को परिभाषित कर सकते हैं, जैसे

template <typename T>
struct constructor_and_destructor_are_empty : std::false_type
{
};

फिर आप विशेषज्ञ

template <typename T, typename = void>
class raw_container;

template <typename T>
class raw_container<
    T,
    std::enable_if_t<
        std::is_empty<T>::value and
        std::is_trivial<T>::value>>
{
public:
  T& data() noexcept
  {
    return reinterpret_cast<T&>(*this);
  }
  void construct()
  {
    // do nothing
  }
  void destruct()
  {
    // do nothing
  }
};

template <typename T>
struct list_node : public raw_container<T>
{
  std::atomic<list_node*> next_;
};

फिर इसे इस तरह उपयोग करें:

using node = list_node<empty<char>>;
static_assert(sizeof(node) == sizeof(std::atomic<node*>), "Good");

बेशक, आपके पास अभी भी है

struct bar : raw_container<empty<char>> { empty<char> e; };
static_assert(sizeof(bar) == 1, "Yes, two objects sharing an address");

लेकिन ईबीओ के लिए यह सामान्य है:

struct ebo1 : empty<char>, empty<usigned char> {};
static_assert(sizeof(ebo1) == 1, "Two object in one place");
struct ebo2 : empty<char> { char c; };
static_assert(sizeof(ebo2) == 1, "Two object in one place");

लेकिन जब तक आप हमेशा का उपयोग के रूप में constructऔर destructनए पर और कोई नियुक्ति &data()कर रहे हैं स्वर्ण, आप।


मुझे std::is_trivial:-) शक्ति के बारे में अवगत कराने के लिए @Deduplicator का धन्यवाद
Rumburak
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.