是否有可能知道基类中的派生对象大小?

时间:2023-01-15 20:55:44

I have requirement that some family of objects of their own derived classes have to be initialized in special memory slots with placement new operator. All of them will be derived from common base. The problem is that there is a size limit for such object. So I would like to put static assertion on derived object size but I do not want to bother with static assertion in each descendant. My question is: Is there is any way to put this assertion into the base or use any other trick in order to check the size outside the derived class declaration? It is acceptable to design base class as a template.

我要求必须在特殊的内存插槽中使用placement new运算符初始化它们自己派生类的某些对象族。所有这些都将来自共同基础。问题是此类对象存在大小限制。所以我想在派生对象大小上放置静态断言,但我不想在每个后代中使用静态断言。我的问题是:有没有办法将这个断言放入基数或使用任何其他技巧来检查派生类声明之外的大小?将基类设计为模板是可以接受的。

2 个解决方案

#1


4  

Sounds like yet another use case for CRTP:

听起来像CRTP的另一个用例:

constexpr std::size_t limit = 42;

template <typename Derived>
class Base {
  ~Base() {
    static_assert(sizeof(Derived) <= limit, "Derived class is too big.");
  }
};

class Foo : Base<Foo> {};

Of course, if you need a common base class you can inject one below the CRTP size check:

当然,如果你需要一个公共基类,你可以在CRTP大小检查下注入一个:

class Base {
  // ...
};

constexpr std::size_t limit = 42;

template <typename Derived>
class SizeCheck : public Base {
  ~SizeCheck() {
    static_assert(sizeof(Derived) <= limit, "Derived class is too big.");
  }
};

class Foo : SizeCheck<Foo> {};

and if your goal is not merely to save some keystrokes, but to defend against people who are actively trying to break the check:

如果你的目标不仅仅是保存一些击键,而且要防止那些积极试图打破支票的人:

template <typename>
class SizeCheck;

class Base {
  // ...
private:
  // Only allow SizeCheck to derive from Base
  ~Base() = default;
  template <typename>
  friend class SizeCheck;
};

constexpr std::size_t limit = 42;

template <typename Derived>
class SizeCheck : public Base {
private:
  // Only allow Derived to derive from SizeCheck<Derived>
  friend Derived;
  ~SizeCheck() {
    static_assert(sizeof(Derived) <= limit, "Derived class is too big.");

    // Ensure that Derived is actually derived from SizeCheck
    static_assert(std::is_base_of<SizeCheck, Derived>(),
                  "Parameter to SizeCheck must be derived from SizeCheck.");

    // Require Derived to be final so that no one can sidestep 
    // the size check. (Uses C++14 std::is_final)
    static_assert(std::is_final<Derived>(),
                  "Nice try; parameter to SizeCheck must be final.");
  }
};

This is all getting a bit twisted. The size of classes derived from Base isn't really the issue, it's the possibility of trying to emplace an object that is too large into your statically sized buffers. It may be simpler to approach the issue from the other end by defending the buffers instead of restricting the derived classes (DEMO):

这一切都变得有点扭曲。从Base派生的类的大小并不是真正的问题,它可能会尝试将对象太大而无法放入静态大小的缓冲区中。通过保护缓冲区而不是限制派生类(DEMO)来从另一端处理问题可能更简单:

struct placement_delete {
  template <typename T>
  void operator()(T* ptr) {
    ptr->~T();
  }
};

template <typename T>
using placement_ptr = std::unique_ptr<T, placement_delete>;

class Base {
  // ...
};

template <std::size_t N, std::size_t Align = 0>
class buffer {
public:
  template <typename T, typename...Args>
  placement_ptr<T> emplace(Args&&...args) {
    static_assert(std::is_base_of<Base, T>(),
                  "Only classes derived from Base can go in a buffer.");
    static_assert(sizeof(T) <= sizeof(space_),
                  "Type is too big for buffer.");
    static_assert(alignof(decltype(space_)) % alignof(T) == 0,
                  "Buffer alignment is insufficient for type.");
    return placement_ptr<T>{::new(&space_) T(std::forward<Args>(args)...)};
  }
private:
  typename std::conditional<!Align,
    typename std::aligned_storage<N>::type,
    typename std::aligned_storage<N, Align>::type
  >::type space_;
};

#2


2  

There is no way that the base-class can directly know the size of a derived class - to do so would require knowing the contents and datalayout of the derived class, which would require that the class is in the same compilation unit - and you probably need to play with templates or macros to make sure it happens.

基类没有办法直接知道派生类的大小 - 这样做需要知道派生类的内容和数据布局,这需要该类在同一个编译单元中 - 你可能需要使用模板或宏来确保它发生。

You could override the operator new for the baseclass - which will also work for all derived classes that don't themselves override the operator new. There are ways in which you can find out, such as using a virtual function that returns sizeof(*this) (but that requires the object is already created - you can't call it from the constructor or before construction, so if you want to use placement new, that won't work).

您可以覆盖基类的new运算符 - 这也适用于所有不会覆盖运算符new的派生类。有一些方法可以找到,例如使用返回sizeof(* this)的虚函数(但是需要已经创建了对象 - 你不能从构造函数或构造之前调用它,所以如果你想使用贴图新,这将无法正常工作)。

You can of course, if you are in control of the source code, do static_assert(sizeof(someDerivedClass) > some_value) - but that's not guaraneteed to not be broken by someone who wishes to do so.

当然,如果您控制源代码,可以执行static_assert(sizeof(someDerivedClass)> some_value) - 但这并不是因为没有被希望这样做的人打破。

#1


4  

Sounds like yet another use case for CRTP:

听起来像CRTP的另一个用例:

constexpr std::size_t limit = 42;

template <typename Derived>
class Base {
  ~Base() {
    static_assert(sizeof(Derived) <= limit, "Derived class is too big.");
  }
};

class Foo : Base<Foo> {};

Of course, if you need a common base class you can inject one below the CRTP size check:

当然,如果你需要一个公共基类,你可以在CRTP大小检查下注入一个:

class Base {
  // ...
};

constexpr std::size_t limit = 42;

template <typename Derived>
class SizeCheck : public Base {
  ~SizeCheck() {
    static_assert(sizeof(Derived) <= limit, "Derived class is too big.");
  }
};

class Foo : SizeCheck<Foo> {};

and if your goal is not merely to save some keystrokes, but to defend against people who are actively trying to break the check:

如果你的目标不仅仅是保存一些击键,而且要防止那些积极试图打破支票的人:

template <typename>
class SizeCheck;

class Base {
  // ...
private:
  // Only allow SizeCheck to derive from Base
  ~Base() = default;
  template <typename>
  friend class SizeCheck;
};

constexpr std::size_t limit = 42;

template <typename Derived>
class SizeCheck : public Base {
private:
  // Only allow Derived to derive from SizeCheck<Derived>
  friend Derived;
  ~SizeCheck() {
    static_assert(sizeof(Derived) <= limit, "Derived class is too big.");

    // Ensure that Derived is actually derived from SizeCheck
    static_assert(std::is_base_of<SizeCheck, Derived>(),
                  "Parameter to SizeCheck must be derived from SizeCheck.");

    // Require Derived to be final so that no one can sidestep 
    // the size check. (Uses C++14 std::is_final)
    static_assert(std::is_final<Derived>(),
                  "Nice try; parameter to SizeCheck must be final.");
  }
};

This is all getting a bit twisted. The size of classes derived from Base isn't really the issue, it's the possibility of trying to emplace an object that is too large into your statically sized buffers. It may be simpler to approach the issue from the other end by defending the buffers instead of restricting the derived classes (DEMO):

这一切都变得有点扭曲。从Base派生的类的大小并不是真正的问题,它可能会尝试将对象太大而无法放入静态大小的缓冲区中。通过保护缓冲区而不是限制派生类(DEMO)来从另一端处理问题可能更简单:

struct placement_delete {
  template <typename T>
  void operator()(T* ptr) {
    ptr->~T();
  }
};

template <typename T>
using placement_ptr = std::unique_ptr<T, placement_delete>;

class Base {
  // ...
};

template <std::size_t N, std::size_t Align = 0>
class buffer {
public:
  template <typename T, typename...Args>
  placement_ptr<T> emplace(Args&&...args) {
    static_assert(std::is_base_of<Base, T>(),
                  "Only classes derived from Base can go in a buffer.");
    static_assert(sizeof(T) <= sizeof(space_),
                  "Type is too big for buffer.");
    static_assert(alignof(decltype(space_)) % alignof(T) == 0,
                  "Buffer alignment is insufficient for type.");
    return placement_ptr<T>{::new(&space_) T(std::forward<Args>(args)...)};
  }
private:
  typename std::conditional<!Align,
    typename std::aligned_storage<N>::type,
    typename std::aligned_storage<N, Align>::type
  >::type space_;
};

#2


2  

There is no way that the base-class can directly know the size of a derived class - to do so would require knowing the contents and datalayout of the derived class, which would require that the class is in the same compilation unit - and you probably need to play with templates or macros to make sure it happens.

基类没有办法直接知道派生类的大小 - 这样做需要知道派生类的内容和数据布局,这需要该类在同一个编译单元中 - 你可能需要使用模板或宏来确保它发生。

You could override the operator new for the baseclass - which will also work for all derived classes that don't themselves override the operator new. There are ways in which you can find out, such as using a virtual function that returns sizeof(*this) (but that requires the object is already created - you can't call it from the constructor or before construction, so if you want to use placement new, that won't work).

您可以覆盖基类的new运算符 - 这也适用于所有不会覆盖运算符new的派生类。有一些方法可以找到,例如使用返回sizeof(* this)的虚函数(但是需要已经创建了对象 - 你不能从构造函数或构造之前调用它,所以如果你想使用贴图新,这将无法正常工作)。

You can of course, if you are in control of the source code, do static_assert(sizeof(someDerivedClass) > some_value) - but that's not guaraneteed to not be broken by someone who wishes to do so.

当然,如果您控制源代码,可以执行static_assert(sizeof(someDerivedClass)> some_value) - 但这并不是因为没有被希望这样做的人打破。