【问题标题】:Match a class by parameter type in a c++ template-generated class hierarchy在 C++ 模板生成的类层次结构中按参数类型匹配类
【发布时间】:2015-05-31 16:19:15
【问题描述】:

简介

我正在开发一个自定义内存分配器,需要在每个分配的块的标题中添加一些簿记信息。有几种不同的块类型,簿记信息也不同。例如,对于线程之间共享的块,需要添加一个引用计数器,对于单个线程使用的块,则不需要添加一个引用计数器。对于从内存池中取出的块,需要保留对原始池的引用,对于从空闲存储中取出的块,则不需要。

问题

所以我想要一个通用接口来为给定的块布局添加和获取某些数据类型。试验这个想法,我得出了一个类似于 std::tuple 的解决方案。然而,与元组不同,我添加到标题中的每种类型都是唯一的。我刚开始学习模板元编程和其他复杂的 c++,但是添加类型的部分对我来说很简单。

我遇到的问题是通过元组的类型模板函数实现类似于 C++14 std::get 的方法。我认为不需要为此编写太多代码,因为编译器能够在方法调用中匹配正确的基类。起初我将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;
}

【问题讨论】:

标签: c++ c++11 variadic-templates derived-class


【解决方案1】:

所以看起来我的代码是完全合法的,这是相当一些 clang++ 问题。或者,我可能会滥用一些定义不明确的 C++ 行为。但到目前为止,这看起来不太可能。如果任何 C++ 语言律师能纠正我,我将不胜感激。

无论如何,在查看问题 cmets 提供的一些示例代码后,我最终使用了我的解决方法。

如果有人对使用所描述技巧的真实代码的外观感兴趣,我将其粘贴在这里。

// 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

现在是所有这些机器的示例使用:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-18
    • 1970-01-01
    • 1970-01-01
    • 2023-04-02
    • 1970-01-01
    相关资源
    最近更新 更多