Tôi có một thành phần tôi sử dụng khi triển khai các kiểu chung cấp thấp lưu trữ một đối tượng thuộc kiểu tùy ý (có thể hoặc không phải là kiểu lớp) có thể trống để tận dụng tối ưu hóa cơ sở trống :
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...>;
Gần đây, tôi đã gặp rắc rối với các cấu trúc dữ liệu không có khóa và tôi cần các nút có tùy chọn chứa dữ liệu trực tiếp. Sau khi được cấp phát, các nút tồn tại trong suốt thời gian tồn tại của cấu trúc dữ liệu nhưng dữ liệu được chứa chỉ tồn tại trong khi nút đang hoạt động chứ không phải khi nút nằm trong danh sách miễn phí. Tôi đã triển khai các nút bằng cách sử dụng bộ nhớ thô và vị trí 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ất cả đều tốt và đẹp, nhưng lãng phí một phần bộ nhớ có kích thước bằng con trỏ cho mỗi nút khi T
trống: một byte cho raw_storage<T>::space_
và sizeof(std::atomic<list_node*>) - 1
byte đệm cho căn chỉnh. Sẽ rất tốt nếu tận dụng EBO và phân bổ biểu diễn byte đơn không sử dụng của raw_container<T>
atop list_node::next_
.
Nỗ lực tốt nhất của tôi trong việc tạo raw_ebo_storage
EBO "thủ công" có hiệu suất:
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)
));
}
};
mà có các hiệu ứng mong muốn:
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!");
nhưng cũng có một số tác dụng không mong muốn, tôi cho rằng do vi phạm bí danh nghiêm ngặt (3.10 / 10) mặc dù ý nghĩa của "truy cập giá trị được lưu trữ của một đối tượng" còn gây tranh cãi đối với kiểu trống:
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.");
Giải pháp này cũng tiềm ẩn các hành vi không xác định khi xây dựng. Tại một số thời điểm, chương trình phải xây dựng đối tượng container trong kho lưu trữ thô với vị trí 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, "");
Nhớ lại rằng mặc dù rỗng, một đối tượng hoàn chỉnh nhất thiết phải có kích thước khác không. Nói cách khác, một đối tượng hoàn chỉnh rỗng có một biểu diễn giá trị bao gồm một hoặc nhiều byte đệm. new
xây dựng các đối tượng hoàn chỉnh, vì vậy việc triển khai tuân thủ có thể đặt các byte đệm đó thành các giá trị tùy ý khi xây dựng thay vì để bộ nhớ không bị ảnh hưởng như trường hợp xây dựng một subobject cơ sở trống. Điều này tất nhiên sẽ là thảm họa nếu những byte đệm đó chồng lên các vật thể sống khác.
Vì vậy, câu hỏi đặt ra là có thể tạo một lớp vùng chứa tuân thủ tiêu chuẩn sử dụng lưu trữ thô / khởi tạo chậm cho đối tượng được chứa và tận dụng EBO để tránh lãng phí không gian bộ nhớ cho việc biểu diễn đối tượng được chứa không?