[[no_unique_address]] และสองค่าสมาชิกประเภทเดียวกัน


16

ฉันกำลังเล่นกับสิ่งต่อไปนี้[[no_unique_address]]ในc++20

ในตัวอย่างในcppreferenceเรามีประเภทEmptyและประเภทที่ว่างเปล่าZ

struct Empty {}; // empty class

struct Z {
    char c;
    [[no_unique_address]] Empty e1, e2;
};

เห็นได้ชัดว่าขนาดของZต้องมีอย่างน้อย2เพราะประเภทe1และe2เหมือนกัน

อย่างไรก็ตามฉันต้องการมีZขนาด1จริงๆ นี้มีฉันคิดสิ่งที่เกี่ยวกับการวางรูปภาพEmptyในชั้นเรียนเสื้อคลุมบางคนที่มีพารามิเตอร์แม่แบบพิเศษที่บังคับใช้ประเภทต่าง ๆและe1e2

template <typename T, int i>
struct Wrapper : public T{};

struct Z1 {
    char c;
    [[no_unique_address]] Wrapper<Empty,1> e1;
    [[no_unique_address]] Wrapper<Empty,2> e2;
};

น่าเสียดาย, sizeof(Z1)==2. มีเคล็ดลับในการทำให้ขนาดZ1เป็นที่หนึ่งหรือไม่?

ฉันกำลังทดสอบสิ่งนี้ด้วยgcc version 9.2.1และclang version 9.0.0


ในใบสมัครของฉันฉันมีแบบฟอร์มเปล่าจำนวนมาก

template <typename T, typename S>
struct Empty{
    [[no_unique_address]] T t;
    [[no_unique_address]] S s;
};

ซึ่งเป็นประเภทที่ว่างถ้าTและSยังเป็นประเภทที่ว่างเปล่าและแตกต่าง! ฉันต้องการประเภทนี้จะว่างเปล่าแม้ว่าTและSเป็นประเภทเดียวกัน


2
สิ่งที่เกี่ยวกับการเพิ่มข้อโต้แย้งแม่แบบเพื่อTตัวเอง? ที่จะสร้างประเภทที่แตกต่าง ตอนนี้ความเป็นจริงที่ว่าทั้งสองWrappers สืบทอดจากTถือคุณกลับ ...
แม็กซ์ Langhof

@MaxLanghof คุณหมายถึงอะไรโดยการเพิ่มอาร์กิวเมนต์แม่แบบT? ตอนนี้Tเป็นอาร์กิวเมนต์แม่แบบ
ทอม

Tไม่ได้รับมรดกจาก
Evg

@Evg สร้างความแตกต่างไม่ได้ที่นี่
eerorika

2
เพียงเพราะมันมีขนาดใหญ่กว่า 1 ไม่ทำให้ว่างเปล่า: coliru.stacked-crooked.com/a/51aa2be4aff4842e
Deduplicator

คำตอบ:


6

ซึ่งเป็นประเภทที่ว่างถ้าTและSยังเป็นประเภทที่ว่างเปล่าและแตกต่าง! ฉันต้องการประเภทนี้จะว่างเปล่าแม้ว่าTและSเป็นประเภทเดียวกัน

คุณไม่สามารถรับได้ เทคนิคการพูดคุณไม่สามารถรับประกันได้ว่ามันจะว่างเปล่าแม้ว่าTและSเป็นชนิดที่แตกต่างกันที่ว่างเปล่า โปรดจำไว้ว่า: no_unique_addressเป็นคุณลักษณะ ความสามารถในการซ่อนวัตถุนั้นขึ้นอยู่กับการนำไปใช้ทั้งหมด จากมุมมองมาตรฐานคุณไม่สามารถบังคับใช้ขนาดของวัตถุที่ว่างเปล่า

เมื่อการใช้งาน C ++ 20 เป็นผู้ใหญ่คุณควรถือว่า[[no_unique_address]]โดยทั่วไปแล้วจะเป็นไปตามกฎของการเพิ่มประสิทธิภาพฐานที่ว่างเปล่า กล่าวคือตราบใดที่วัตถุสองประเภทเดียวกันนั้นไม่ใช่วัตถุย่อยคุณอาจคาดว่าจะซ่อนตัวได้ แต่ ณ จุดนี้มันเป็นโชคดี

สำหรับกรณีเฉพาะTและSเป็นประเภทเดียวกันนั้นเป็นไปไม่ได้ แม้จะมีความหมายของชื่อ "no_unique_address" แต่ความเป็นจริงก็คือ C ++ ต้องการให้กำหนดพอยน์เตอร์สองตัวให้กับวัตถุประเภทเดียวกันพอยน์เตอร์เหล่านั้นชี้ไปที่วัตถุเดียวกันหรือมีที่อยู่ต่างกัน ฉันเรียกสิ่งนี้ว่า "กฎเอกลักษณ์เฉพาะ" และno_unique_addressไม่ส่งผลกระทบต่อสิ่งนั้น จาก[intro.object] / 9 :

วัตถุสองชิ้นที่มีอายุการใช้งานที่ทับซ้อนกันซึ่งไม่ใช่บิตฟิลด์อาจมีที่อยู่เดียวกันหากวัตถุหนึ่งซ้อนกันภายในอีกวัตถุหนึ่งหรืออย่างน้อยหนึ่งวัตถุย่อยที่มีขนาดเป็นศูนย์ และพวกเขาจะแตกต่างกัน ; มิฉะนั้นจะมีที่อยู่ที่แตกต่างกันและใช้พื้นที่จัดเก็บข้อมูลร่วมกันเป็นไบต์

สมาชิกของประเภทที่ว่างเปล่าประกาศเป็น[[no_unique_address]]ศูนย์ขนาด แต่มีประเภทเดียวกันทำให้เป็นไปไม่ได้

ที่จริงแล้วคิดเกี่ยวกับมันพยายามที่จะซ่อนประเภทที่ว่างเปล่าผ่านการทำรังยังคงละเมิดกฎเอกลักษณ์ที่ไม่ซ้ำกัน พิจารณาWrapperและZ1กรณีของคุณ ให้z1ซึ่งเป็นตัวอย่างของZ1มันเป็นที่ชัดเจนz1.e1และz1.e2เป็นวัตถุที่แตกต่างกับประเภทที่แตกต่างกัน อย่างไรก็ตามz1.e1จะไม่ซ้อนกันภายในz1.e2หรือในทางกลับกัน และในขณะที่พวกเขามีประเภทที่แตกต่างกัน(Empty&)z1.e1และไม่ได้(Empty&)z1.e2เป็นประเภทที่แตกต่างกัน แต่พวกมันชี้ไปที่วัตถุต่าง ๆ

และด้วยกฎเอกลักษณ์ที่ไม่ซ้ำกันพวกเขาจะต้องมีที่อยู่ที่แตกต่างกัน ดังนั้นถึงแม้ว่าe1และe2จะมีความแตกต่างกันในนาม แต่ internals ของพวกเขาจะต้องเชื่อฟังเอกลักษณ์ที่ไม่ซ้ำกับ subobjects อื่น ๆ ในวัตถุที่มีวัตถุเดียวกัน ซ้ำ

สิ่งที่คุณต้องการนั้นเป็นไปไม่ได้ใน C ++ ที่เป็นอยู่ในปัจจุบันไม่ว่าคุณจะพยายามทำอะไร


คำอธิบายที่ดีขอบคุณมาก!
ทอม

2

เท่าที่ฉันสามารถบอกได้ว่าเป็นไปไม่ได้หากคุณต้องการมีสมาชิกทั้งคู่ แต่คุณสามารถชำนาญและมีสมาชิกได้เพียงคนเดียวเมื่อประเภทนั้นเหมือนกันและว่างเปล่า:

template <typename T, typename S, typename = void>
struct Empty{
    [[no_unique_address]] T t;
    [[no_unique_address]] S s;

    constexpr T& get_t() noexcept { return t; };
    constexpr S& get_s() noexcept { return s; };
};

template<typename TS>
struct Empty<TS, TS, typename std::enable_if_t<std::is_empty_v<TS>>>{
    [[no_unique_address]] TS ts;

    constexpr TS& get_t() noexcept { return ts; };
    constexpr TS& get_s() noexcept { return ts; };
};

แน่นอนส่วนที่เหลือของโปรแกรมที่ใช้สมาชิกจะต้องมีการเปลี่ยนแปลงเพื่อจัดการกับกรณีที่มีสมาชิกเพียงคนเดียว ไม่สำคัญว่าสมาชิกรายใดจะใช้ในกรณีนี้เพราะเป็นวัตถุไร้สัญชาติที่ไม่มีที่อยู่เฉพาะ ฟังก์ชั่นสมาชิกที่แสดงควรทำให้ง่าย

น่าเสียดาย sizeof(Empty<Empty<A,A>,A>{})==2ที่ A คือโครงสร้างที่ว่างเปล่าโดยสมบูรณ์

คุณสามารถแนะนำความเชี่ยวชาญพิเศษเพิ่มเติมเพื่อรองรับการบีบอัดแบบเรียกซ้ำของคู่ที่ว่างเปล่า:

template<class TS>
struct Empty<Empty<TS, TS>, TS, typename std::enable_if_t<std::is_empty_v<TS>>>{
    [[no_unique_address]] Empty<TS, TS> ts;

    constexpr Empty<TS, TS>& get_t() noexcept { return ts; };
    constexpr TS&            get_s() noexcept { return ts.get_s(); };
};

template<class TS>
struct Empty<TS, Empty<TS, TS>, typename std::enable_if_t<std::is_empty_v<TS>>>{
    [[no_unique_address]] Empty<TS, TS> ts;

    constexpr TS&            get_t() noexcept { return ts.get_t(); };
    constexpr Empty<TS, TS>& get_s() noexcept { return ts; };
};

Empty<Empty<A, char>, A>มากยิ่งขึ้นในการบีบอัดบางอย่างเช่น

template <typename T, typename S>
struct Empty<Empty<T, S>, S, typename std::enable_if_t<std::is_empty_v<S>>>{
     [[no_unique_address]] Empty<T, S> ts;

    constexpr Empty<T, S>& get_t() noexcept { return ts; };
    constexpr S&           get_s() noexcept { return ts.get_s(); };
};

template <typename T, typename S>
struct Empty<Empty<S, T>, S, typename std::enable_if_t<std::is_empty_v<S>>>{
     [[no_unique_address]] Empty<S, T> st;

    constexpr Empty<S, T>& get_t() noexcept { return st; };
    constexpr S&           get_s() noexcept { return st.get_t(); };
};


template <typename T, typename S>
struct Empty<T, Empty<T, S>, typename std::enable_if_t<std::is_empty_v<T>>>{
     [[no_unique_address]] Empty<T, S> ts;

    constexpr T&           get_t() noexcept { return ts.get_t(); };
    constexpr Empty<T, S>  get_s() noexcept { return ts; };
};

template <typename T, typename S>
struct Empty<T, Empty<S, T>, typename std::enable_if_t<std::is_empty_v<T>>>{
     [[no_unique_address]] Empty<S, T> st;

    constexpr T&           get_t() noexcept { return st.get_s(); };
    constexpr Empty<S, T>  get_s() noexcept { return st; };
};

นี่เป็นสิ่งที่ดี แต่ก็น่าเสียดายsizeof(Empty<Empty<A,A>,A>{})==2ที่Aโครงสร้างที่ว่างเปล่าโดยสมบูรณ์
ทอม

ฉันจะเพิ่มget_empty<T>ฟังก์ชั่น จากนั้นคุณสามารถนำกลับมาใช้ใหม่get_empty<T>ทางซ้ายหรือขวาหากมันใช้งานได้แล้ว
Yakk - Adam Nevraumont
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.