c++ - tarjeta - ¿Cómo emular EBO cuando se usa almacenamiento sin procesar?
emular tarjeta nfc android (1)
Creo que usted mismo dio la respuesta en sus diversas observaciones:
- Desea memoria cruda y ubicación nueva. Esto requiere tener al menos un byte disponible, incluso si desea construir un objeto vacío mediante la colocación de nuevo.
- Desea una sobrecarga de cero bytes para almacenar objetos vacíos.
Estos requisitos son contradictorios. La respuesta, por lo tanto, es No , eso no es posible.
Sin embargo, podría cambiar sus requisitos un poco más, al requerir la sobrecarga de cero bytes solo para tipos vacíos y triviales.
Podría definir un nuevo rasgo de clase, p. Ej.
template <typename T>
struct constructor_and_destructor_are_empty : std::false_type
{
};
Entonces te especializas
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_;
};
Entonces úsalo así:
using node = list_node<empty<char>>;
static_assert(sizeof(node) == sizeof(std::atomic<node*>), "Good");
Por supuesto, todavía tienes
struct bar : raw_container<empty<char>> { empty<char> e; };
static_assert(sizeof(bar) == 1, "Yes, two objects sharing an address");
Pero eso es normal para EBO:
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");
Pero siempre que use
construct
y
destruct
y no haya una nueva ubicación en
&data()
, es dorado.
Tengo un componente que uso al implementar tipos genéricos de bajo nivel que almacenan un objeto de tipo arbitrario (puede o no ser un tipo de clase) que puede estar vacío para aprovechar la optimización de la base vacía :
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...>;
Últimamente he estado jugando con estructuras de datos sin bloqueo y necesito nodos que opcionalmente contengan un dato en vivo.
Una vez asignados, los nodos viven durante la vida útil de la estructura de datos, pero el dato contenido solo está vivo mientras el nodo está activo y no mientras el nodo se encuentra en una lista libre.
Implementé los nodos usando almacenamiento en bruto y ubicación
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_;
};
que está muy bien, pero desperdicia una porción de memoria del tamaño de un puntero por nodo cuando
T
está vacío: un byte para
raw_storage<T>::space_
, y
sizeof(std::atomic<list_node*>) - 1
bytes de acolchado para alineación.
Sería bueno aprovechar EBO y asignar la representación de un solo byte no utilizada de
raw_container<T>
atop
list_node::next_
.
Mi mejor intento de crear un
raw_ebo_storage
realiza un
raw_ebo_storage
"manual":
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)
));
}
};
que tiene los efectos deseados:
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!");
pero también algunos efectos indeseables, supongo debido a la violación del alias estricto (3.10 / 10) aunque el significado de "acceder al valor almacenado de un objeto" es discutible para un tipo vacío:
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.");
Esta solución también tiene potencial para un comportamiento indefinido en la construcción.
En algún momento, el programa debe construir el objeto contenido dentro del almacenamiento sin procesar con una ubicación
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, "");
Recuerde que a pesar de estar vacío, un objeto completo necesariamente tiene un tamaño distinto de cero.
En otras palabras, un objeto completo vacío tiene una representación de valor que consta de uno o más bytes de relleno.
new
construcciones completan objetos, por lo que una implementación conforme podría establecer esos bytes de relleno en valores arbitrarios en la construcción en lugar de dejar la memoria intacta, como sería el caso para construir un subobjeto base vacío.
Por supuesto, esto sería catastrófico si esos bytes de relleno se superponen a otros objetos vivos.
Entonces, la pregunta es, ¿es posible crear una clase de contenedor que cumpla con el estándar que use almacenamiento en bruto / inicialización retrasada para el objeto contenido y aproveche EBO para evitar desperdiciar espacio de memoria para la representación del objeto contenido?