Ale*_*kov 17 c++ derived-class variadic-templates c++11
我正在开发一个自定义内存分配器,需要在每个分配的块的头部添加一些簿记信息.有几种不同的块类型,簿记信息也不同.例如,对于线程之间共享的块,需要添加引用计数器,对于单线程使用的块,没有这样的需要.对于从内存池中获取的块,需要保留对原始池的引用,因为从免费存储中获取的块没有这样的需要.
所以我希望有一个通用接口来为给定的块布局添加和获取某些数据类型.试验这个想法,我找到了一个类似于std :: tuple的解决方案.然而,与元组不同,我添加到标题的每个类型都将是唯一的.我刚刚开始学习模板元编程和其他复杂的c ++,但添加类型的部分对我来说很简单.
我遇到的问题是std::get通过类型模板函数为元组实现类似于C++ 14的方法.我认为没有必要为此编写太多代码,因为编译器能够在方法调用中匹配正确的基类.首先,我将get方法放入模板生成的布局类中.但是,在这种情况下,编译器无法匹配正确的类.通过将get方法移动到另一个手动添加的类层次结构级别来解决该问题.
下面的代码演示了这个问题.将HAVE_GET_IN_LAYOUT定义为0会产生一个工作解决方案,同时将其定义为1会产生一个破碎的解决方案[至少使用clang ++ 3.5和3.6]
问题是,在这种情况下破坏了什么?
#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;
}
Run Code Online (Sandbox Code Playgroud)
所以看起来我的代码是完全合法的,这只是一些 clang++ 问题。或者,我可能会滥用一些未明确定义的 C++ 行为。但目前看来这不太可能。如果任何 C++ 语言律师可以纠正我,我将不胜感激。
不管怎样,我最终使用了我的解决方法,在查看了问题评论中提供的一些示例代码后,我增强了该解决方法。
如果有人对使用所描述的技巧的真实代码感兴趣,我将其粘贴在这里。
// 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
Run Code Online (Sandbox Code Playgroud)
现在是所有这些机器的使用示例:
#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;
}
Run Code Online (Sandbox Code Playgroud)