[[no_unique_address]] và hai giá trị thành viên cùng loại


16

Tôi đang chơi xung quanh với [[no_unique_address]]trong c++20.

Trong ví dụ về cppreference chúng ta có một kiểu Emptyvà kiểu trốngZ

struct Empty {}; // empty class

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

Rõ ràng, kích thước của Zphải có ít nhất là 2vì các loại e1e2giống nhau.

Tuy nhiên, tôi thực sự muốn có Zkích thước 1. Điều này khiến tôi suy nghĩ, những gì về việc gói Emptytrong một số lớp bao bọc với tham số mẫu bổ sung thực thi các loại khác nhau 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;
};

Thật không may , sizeof(Z1)==2. Có một mẹo để làm cho kích thước của Z1một?

Tôi đang thử nghiệm điều này với gcc version 9.2.1clang version 9.0.0


Trong ứng dụng của tôi, tôi có rất nhiều loại biểu mẫu trống

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

Đó là một loại trống nếu TScũng là loại trống và khác biệt! Tôi muốn loại này trống ngay cả khi TScùng loại.


2
Điều gì về việc thêm các đối số mẫu cho Tchính nó? Điều đó sẽ tạo ra các loại khác nhau. Ngay bây giờ thực tế là cả hai Wrapperthừa kế từ Tđang giữ bạn lại ...
Max Langhof

@MaxLanghof Ý bạn là gì khi thêm một đối số mẫu vào T? Ngay bây giờ, Tlà một đối số mẫu.
tom

Đừng thừa kế từ T.
Evg

@Evg làm cho không có sự khác biệt ở đây.
eerorika

2
Chỉ vì nó lớn hơn 1 nhưng điều đó không làm cho nó không rỗng: coliru.stacked-crooked.com/a/51aa2be4aff4842e
Deduplicator

Câu trả lời:


6

Đó là một loại trống nếu TScũng là loại trống và khác biệt! Tôi muốn loại này trống ngay cả khi TScùng loại.

Bạn không thể có được điều đó. Về mặt kỹ thuật, bạn thậm chí không thể đảm bảo rằng nó sẽ trống ngay cả khi TSlà các loại trống khác nhau. Ghi nhớ: no_unique_addresslà một thuộc tính; khả năng của nó để ẩn các đối tượng là hoàn toàn phụ thuộc vào việc thực hiện. Từ góc độ tiêu chuẩn, bạn không thể thực thi kích thước của các đối tượng trống.

Khi triển khai C ++ 20, bạn nên cho rằng [[no_unique_address]]nhìn chung sẽ tuân theo các quy tắc tối ưu hóa cơ sở trống. Cụ thể, miễn là hai đối tượng cùng loại không phải là tiểu dự án, bạn có thể mong đợi được ẩn. Nhưng tại thời điểm này, đó là loại may mắn.

Đối với trường hợp cụ thể TSlà cùng loại, điều đó đơn giản là không thể. Mặc dù có ý nghĩa của tên "no_unique_address", nhưng thực tế là C ++ yêu cầu rằng, đưa ra hai con trỏ cho các đối tượng cùng loại, những con trỏ đó trỏ đến cùng một đối tượng hoặc có địa chỉ khác nhau. Tôi gọi đây là "quy tắc nhận dạng duy nhất" và no_unique_addresskhông ảnh hưởng đến điều đó. Từ [intro.object] / 9 :

Hai đối tượng có tuổi thọ chồng chéo không phải là trường bit có thể có cùng một địa chỉ nếu một đối tượng được lồng vào nhau hoặc nếu ít nhất một đối tượng là một tiểu thể có kích thước bằng 0 và chúng có các loại khác nhau ; mặt khác, chúng có địa chỉ riêng biệt và chiếm các byte lưu trữ khác nhau.

Các thành viên của các loại trống được khai báo là [[no_unique_address]]có kích thước bằng 0, nhưng có cùng loại làm cho điều này là không thể.

Thật vậy, suy nghĩ về nó, cố gắng che giấu kiểu trống thông qua việc lồng nhau vẫn vi phạm quy tắc nhận dạng duy nhất. Xem xét của bạn WrapperZ1trường hợp. Cho một z1ví dụ là Z1, rõ ràng rằng z1.e1z1.e2là các đối tượng khác nhau với các loại khác nhau. Tuy nhiên, z1.e1không được lồng trong z1.e2và ngược lại. Và trong khi chúng có các loại khác nhau, (Empty&)z1.e1không phải(Empty&)z1.e2 là các loại khác nhau. Nhưng họ chỉ vào các đối tượng khác nhau.

Và theo quy tắc nhận dạng duy nhất, họ phải có địa chỉ khác nhau. Vì vậy, mặc dù e1e2là các loại khác nhau trên danh nghĩa, các bộ phận bên trong của chúng cũng phải tuân theo danh tính duy nhất chống lại các tiểu dự án khác trong cùng một đối tượng có chứa. Đệ quy.

Những gì bạn muốn chỉ đơn giản là không thể có trong C ++ vì nó hiện đang tồn tại, bất kể bạn cố gắng như thế nào.


Giải thích tuyệt vời, cảm ơn rất nhiều!
tom

2

Theo như tôi có thể nói, điều đó là không thể nếu bạn muốn có cả hai thành viên. Nhưng bạn có thể chuyên môn hóa và chỉ có một trong số các thành viên khi loại giống nhau và trống:

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

Tất nhiên, phần còn lại của chương trình sử dụng các thành viên sẽ cần phải được thay đổi để giải quyết trường hợp chỉ có một thành viên. Không quan trọng thành viên nào được sử dụng trong trường hợp này - xét cho cùng, đó là một đối tượng không quốc tịch không có địa chỉ duy nhất. Các chức năng thành viên được hiển thị sẽ làm cho đơn giản.

Thật không may sizeof(Empty<Empty<A,A>,A>{})==2, nơi A là một cấu trúc hoàn toàn trống rỗng.

Bạn có thể giới thiệu thêm các chuyên ngành để hỗ trợ nén đệ quy các cặp trống:

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

Thậm chí nhiều hơn, để nén một cái gì đó như 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; };
};

Đây là tốt đẹp, nhưng vẫn không may sizeof(Empty<Empty<A,A>,A>{})==2Alà một cấu trúc hoàn toàn trống rỗng.
tom

Tôi muốn thêm một get_empty<T>chức năng. Sau đó, bạn có thể sử dụng lại get_empty<T>bên trái hoặc bên phải nếu nó đã hoạt động ở đó.
Yakk - Adam Nevraumont
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.