派生 类 中的 sizeof(*this) 和 decltype(*this)

sizeof(*this) and decltype(*this) in derived classes

假设有class个:

struct A {
  int a;

  virtual size_t GetMemoryUsage() const {
    return sizeof(*this);
  }
};

struct B : public A {
  int b;
};

而且可能还有更深层次的传承。

我想要的是有一个方法可以 return 对象在内存中占用的字节数,在本例中为 GetMemoryUsage()。通常使用sizeof(*this)即可实现。问题是(至少 AFAIU)我必须重写每个派生 class 中的方法并实际复制粘贴其主体。我不喜欢重复的代码:)

我说的对吗?我怎样才能使 sizeof(*this)decltype(*this) return 在 subclasses 中成为我想要的,只从基础 class 的方法中调用它们?有没有更优雅的解决方案?

您不必为每个派生的 class 手动实现 GetMemoryUsage,只需将其保留为纯虚拟即可。例如:

struct A
{
    virtual ~A() = default;
    virtual size_t GetMemoryUsage() const noexcept = 0;
};

struct B : A
{
    int b;
};

但是,在创建对象时,必须实现该功能。您可以使用 "decorates" class 的工厂函数以及纯虚拟的通用实现来做到这一点:

// Can alternatively be defined inside function template create.
template<class T>
struct GetMemoryUsageImpl : T
{
    using T::T;
    size_t GetMemoryUsage() const noexcept final {
        return sizeof(T);
    }
};

template<class T, class... Args>
std::unique_ptr<T> create(Args&&... args) {
    return std::unique_ptr<T>(new GetMemoryUsageImpl<T>(std::forward<Args>(args)...));
}

用法:

void f(A& a) {
    auto object_size = a.GetMemoryUsage();
}

int main() {
    auto b = create<B>();
    f(*b);
}

You can also implement a hierarchy of interfaces incrementally easily using this idiom.

这是@Maxim 解决方案的疯狂通用版本。

template<class B0, template<class...>class... Z>
struct TemplateFold {
  using type=B0;
};
template<class B0, template<class...>class... Z>
using TemplateFold_t = typename TemplateFold<B0, Z...>::type;

template<class B0, template<class...>class Z0, template<class...>class... Z>
struct TemplateFold<B0, Z0, Z...>
{
  using type=Z0< TemplateFold_t<B0, Z...> >;
};

struct ExposeTrivial {
protected:
    ~ExposeTrivial() {}
};
template<class D, class B0=ExposeTrivial, class...Bases>
struct Expose:B0, Bases... {
  // is a template because D isn't a real type when this class is instantiated:
  template<class T>
  using MakeConcreteType = TemplateFold_t< T, std::conditional_t< std::is_same<B0,ExposeTrivial>{}, T, B0 >::template Implement, Bases::template Implement... >;
  template<class...Args>
  static std::unique_ptr<D> create( Args&&... args ) {
    using ConcreteType = MakeConcreteType<D>;
    return std::unique_ptr<D>( new ConcreteType( std::forward<Args>(args)... ) );
  }
protected:
  ~Expose() {}
};

// expose one thing:
struct ExposeMemoryUsage:Expose<ExposeMemoryUsage> {
  virtual std::size_t GetMemoryUsage() const noexcept = 0;

  template<class B>
  struct Implement:B {
    using B::B;
    std::size_t GetMemoryUsage() const noexcept override final {
      return sizeof(*this);
    }
  };
protected:
  ~ExposeMemoryUsage() {}
};
// expose a different thing:
struct ExposeAlignment:Expose<ExposeAlignment>{
  virtual std::size_t GetAlignment() const noexcept = 0;
  template<class B>
  struct Implement:B {
    using B::B;
    std::size_t GetAlignment() const noexcept final override {
      return alignof(decltype(*this));
    }
  };
};

// Expose two things:
struct Bob : Expose<Bob, ExposeMemoryUsage, ExposeAlignment> {
  int x;

  Bob( int v ): x(v) {}
  virtual ~Bob() {}
};

int main() {
  std::unique_ptr<Bob> ptr = Bob::create(7);
  std::cout << ptr->x << " size:" << ptr->GetMemoryUsage() << " align:" << ptr->GetAlignment() << "\n";
 // Bob b; // does not compile
}

只需在 Exposer 中添加更多 "knows the derived type" 个静态助手以增加功能。

Live example.


使用方法:

创建一个 Expose 类型。它应该有一个纯虚拟成员和一个实现该纯虚拟成员的模板实现 class(给定一个从 Expose 类型派生的 class)。

应该继承Expose<OwnType>(CRTP)给你写静态::create方法

如果您想继承其他 Expose 类型(即组合两个需要知道具体类型的独立 Expose 接口),而不是继承 Expose< YourType, OtherExposeType, AnotherExposeType >。不要独立继承 OtherExposeTypeAnotherExposeType.

如果您这样做,您的 Implement 模板将不会被选取。

我可以对此进行改进,以便我们在您和您的基地中检测到 Implement 模板,但这比我现在准备的要多得多。