programacion - template en c++
Haga coincidir una clase por tipo de parámetro en una jerarquía de clases generada por una plantilla c++ (1)
Introducción
Estoy trabajando en un asignador de memoria personalizado y necesito agregar alguna información de contabilidad al encabezado de cada parte asignada. Hay varios tipos de partes diferentes y la información de contabilidad también difiere. Por ejemplo, para los fragmentos compartidos entre subprocesos es necesario agregar un contador de referencia, para los fragmentos utilizados por un solo hilo no existe tal necesidad. Para los trozos tomados de un grupo de memoria, es necesario mantener una referencia al grupo original, para los trozos tomados de la tienda libre no existe tal necesidad.
Problema
Así que me gustaría tener una interfaz genérica para agregar y obtener ciertos tipos de datos para un diseño de fragmento determinado. Experimentando con esta idea, llegué a una solución similar a std :: tuple. Sin embargo, a diferencia de las tuplas, todos los tipos que agrego al encabezado serán únicos. Acabo de comenzar a aprender la meta-programación de plantillas y otras complejidades de c ++, sin embargo, la parte con la adición de un tipo fue sencilla para mí.
El problema que encontré es implementar un método similar a C ++ 14 std::get
by type función de plantilla para tuplas. Pensé que no hay necesidad de escribir mucho código para esto, ya que el compilador es capaz de coincidir con la clase base correcta en una llamada de método. Al principio, puse el método get
en la clase de diseño generado por la plantilla. Sin embargo, el compilador no coincide con la clase correcta en este caso. El problema se resolvió moviendo el método get
a otro nivel de jerarquía de clases, agregado manualmente.
El siguiente código muestra el problema. La definición de HAVE_GET_IN_LAYOUT en 0 produce una solución de trabajo, mientras que definirla en 1 produce una solución rota [al menos con Clang ++ 3.5 y 3.6]
La pregunta es, ¿qué se rompe en este caso?
#include <cstddef>
#include <iostream>
#ifndef HAVE_GET_IN_LAYOUT
#define HAVE_GET_IN_LAYOUT 0
#endif
constexpr std::size_t Align(std::size_t size, std::size_t offset) {
return (size < 0x8
? (offset + 0x3) & ~0x3
: (size < 0x10 ? (offset + 0x7) & ~0x7 : (offset + 0xf) & ~0xf));
}
template <std::size_t Start, typename... Ts> struct Layout {
static constexpr std::size_t Size = 0;
static constexpr std::size_t Offset = Start;
static constexpr std::size_t TotalSize = Start;
};
template <std::size_t Start, typename T, typename... Ts>
struct Layout<Start, T, Ts...>
: public Layout<Align(sizeof(T), Start) + sizeof(T), Ts...> {
using Type = T;
static constexpr std::size_t Size = sizeof(Type);
static constexpr std::size_t Offset = Align(Size, Start);
static constexpr std::size_t TotalSize = Layout<Offset + Size, Ts...>::TotalSize;
Type value = Offset - Start; // no particular meaning, just for testing.
#if HAVE_GET_IN_LAYOUT
template <typename U, std::size_t X, typename... Us>
U &helper(Layout<X, U, Us...> *c) { return c->value; }
template <typename U> U &get() { return helper<U>(this); }
#endif
};
template <typename... Ts> struct Result : public Layout<0, Ts...> {
#if !HAVE_GET_IN_LAYOUT
template <typename U, std::size_t X, typename... Us>
U &helper(Layout<X, U, Us...> *c) { return c->value; }
template <typename U> U &get() { return helper<U>(this); }
#endif
};
int main() {
std::cout << "layout size <> = " << Layout<0>::TotalSize << std::endl;
std::cout << "layout size <int> = " << Layout<0, int>::TotalSize << std::endl;
std::cout << "layout size <long> = " << Layout<0, long>::TotalSize << std::endl;
std::cout << "layout size <int,int> = " << Layout<0, int, int>::TotalSize << std::endl;
std::cout << "layout size <int,long> = " << Layout<0, int, long>::TotalSize << std::endl;
std::cout << "layout size <long,int> = " << Layout<0, long, int>::TotalSize << std::endl;
std::cout << "layout size <long,long> = " << Layout<0, long, long>::TotalSize << std::endl;
std::cout << "get: " << Result<int, long, long double>{}.get<long>() << std::endl;
return 0;
}
Así que parece que mi código era perfectamente legal, es más bien un problema de Clang ++. Alternativamente, podría estar abusando de algún comportamiento de C ++ no bien definido. Pero esto parece poco probable hasta ahora. Si cualquier abogado en lenguaje C ++ pudiera corregirme, lo apreciaría enormemente.
De todos modos, terminé usando mi solución que mejoré después de ver algunos de los ejemplos de código que se proporcionan en los comentarios de la pregunta.
Si a alguien le interesa cómo se ve el código real con el truco descrito, lo estoy pegando aquí.
// Round down to a power of two multiple.
constexpr std::size_t Align(std::size_t n, std::size_t a) {
return n & ~(a - 1);
}
// Round up to a power of two multiple.
constexpr std::size_t AlignUp(std::size_t n, std::size_t a) {
return Align(n + a - 1, a);
}
namespace memory {
namespace detail {
// Calculate a data item alignment according to its size.
constexpr std::size_t Align(std::size_t size, std::size_t offset) {
return size < 0x08 ? ::AlignUp(offset, 0x04)
: size < 0x10 ? ::AlignUp(offset, 0x08)
: ::AlignUp(offset, 0x10);
}
// Services for placement of a given type instance within a memory chunk
// at the specified offset.
template <typename T, std::size_t S> class EntryLayout {
public:
using Type = T;
using Pointer = T *;
static constexpr std::size_t Size = sizeof(Type);
static constexpr std::size_t Offset = Align(Size, S);
static constexpr std::size_t EndOffset = Offset + Size;
static Pointer Instance(char *ptr) {
return reinterpret_cast<Pointer>(RawData(ptr));
}
template <typename... Args>
static Pointer Construct(char *ptr, Args &&... args) {
return new (RawData(ptr)) Type(std::forward<Args>(args)...);
}
static void Destruct(char *ptr) { Instance(ptr)->~Type(); }
private:
static char *RawData(char *ptr) { return ptr + Offset; }
};
// Services for placement of a number of types within a memory
// chunk at the specified offset.
template <std::size_t S, typename... Tail> class ChunkLayout {
public:
static constexpr std::size_t StartOffset = S;
static constexpr std::size_t EndOffset = S;
template <typename... Args> static void Construct(char *, Args...) {}
static void Destruct(char *) {}
};
// Recursive template specialization of the above.
template <std::size_t S, typename Head, typename... Tail>
class ChunkLayout<S, Head, Tail...>
: public ChunkLayout<EntryLayout<Head, S>::EndOffset, Tail...> {
public:
using EntryType = Head;
using HeadLayout = EntryLayout<Head, S>;
using TailLayout = ChunkLayout<HeadLayout::EndOffset, Tail...>;
static constexpr std::size_t StartOffset = S;
static constexpr std::size_t EndOffset = TailLayout::EndOffset;
static typename HeadLayout::Pointer Instance(char *ptr) {
return HeadLayout::Instance(ptr);
}
template <typename... Args> void Construct(char *ptr, Args... args) {
HeadLayout::Construct(ptr, args...);
TailLayout::Construct(ptr, args...);
}
void Destruct(char *ptr) {
TailLayout::Destruct(ptr);
HeadLayout::Destruct(ptr);
}
};
} // namespace detail
// Control of memory chunk free and used space.
class ChunkSpace {
public:
ChunkSpace(std::size_t size) noexcept : free_{size}, used_(0) {}
std::size_t Used() const { return used_; }
std::size_t Free() const { return free_; }
std::size_t Size() const { return free_ + used_; }
bool Alloc(std::size_t size) {
if (size > free_)
return false;
free_ -= size;
used_ += size;
return true;
}
void Reset(std::size_t size = 0) {
assert(size <= used_);
free_ = free_ + used_ - size;
used_ = size;
}
private:
std::size_t free_;
std::size_t used_;
};
template <typename... EntryType>
class Chunk : public detail::ChunkLayout<0, ChunkSpace, EntryType...> {
using Layout = detail::ChunkLayout<0, ChunkSpace, EntryType...>;
public:
Chunk(char *data, std::size_t size) : data_{data} {
assert(size > Layout::EndOffset);
// Construct ChunkSpace instance to bootstrap allocation.
Layout::HeadLayout::Construct(data_, size);
// Allocate space required for all the chunk data.
Alloc(Layout::EndOffset);
// Construct the rest of the chunk data.
Layout::TailLayout::Construct(data_);
}
~Chunk() {
Layout::Destruct(data_);
}
template <typename T>
T* Get() {
return decltype(Upcast<T>(this))::Instance(data_);
}
template <typename T>
const T* Get() const {
return decltype(Upcast<T>(this))::Instance(data_);
}
std::size_t Used() const { return Get<ChunkSpace>()->Used(); }
std::size_t Free() const { return Get<ChunkSpace>()->Free(); }
std::size_t Size() const { return Get<ChunkSpace>()->Size(); }
void *Allocate(std::size_t size) {
std::size_t offset = Used();
std::size_t aligned_offset = detail::Align(size, offset);
std::size_t offset_padding = aligned_offset - offset;
if (!Alloc(size + offset_padding))
return nullptr;
return data_ + aligned_offset;
}
private:
bool Alloc(std::size_t size) {
return Get<ChunkSpace>()->Alloc(size);
}
// Some C++ magic to upcast to the base class that contains layout info
// for a given entry type.
template <typename Head, std::size_t S, typename... Tail>
static typename detail::ChunkLayout<S, Head, Tail...>::HeadLayout
Upcast(const detail::ChunkLayout<S, Head, Tail...> *);
char *data_;
};
} // namespace memory
Y ahora una muestra de uso de toda esta maquinaria:
#include "chunk.h"
#include "iostream"
struct A {
int value = 0xa;
};
struct B {
int value = 0xb;
};
void alloc(memory::Chunk<A, B> &chunk, std::size_t size)
{
chunk.Allocate(size);
std::cout << "Allocate " << size << " bytes:" << std::endl;
std::cout << " used: " << chunk.Used() << std::endl;
std::cout << " free: " << chunk.Free() << std::endl;
}
int main()
{
char buffer[1024];
memory::Chunk<A, B> chunk(buffer, sizeof buffer);
std::cout << "used: " << chunk.Used() << std::endl;
std::cout << "free: " << chunk.Free() << std::endl;
A *a = chunk.Get<A>();
B *b = chunk.Get<B>();
std::cout << std::hex;
std::cout << "a: " << a->value << " b: " << b->value << std::endl;
std::cout << std::dec;
alloc(chunk, 1);
alloc(chunk, 2);
alloc(chunk, 4);
alloc(chunk, 8);
alloc(chunk, 16);
return 0;
}