在编译时确定最大的派生类

8
假设我有一个类Base,它有N个子类Derived0,Derived1,...,DerivedN。我试图为从Base继承的对象创建池化分配器,并且实现依赖于知道Base最大子类的大小,因为每个池都必须足够大,以容纳其中一个对象。这是一个简单的示例,其中包含简单的POD类和N = 2。实际上,N可能更大,而Base的子类可能不是简单的POD类。
class Base {
public:
    virtual ~Base() = 0;
};

class Derived0 : public Base {
    int a;
};

class Derived1 : public Base {
    int a, b;
};

class Derived2 : public Base {
    int a, b, c;
};

假设我想创建一个足够大的缓冲区来存储10个孩子。我已经苦苦思索了一段时间,下面是我的解决方案:

static const size_t NUMBER_OF_POOLS = 10;

static const size_t LARGEST_CHILD_SIZE =
    sizeof(Derived0) > sizeof(Derived1) ?
        sizeof(Derived0) > sizeof(Derived2) ?
            sizeof(Derived0) :
            sizeof(Derived2) :
        sizeof(Derived1) > sizeof(Derived2) ?
            sizeof(Derived1) :
            sizeof(Derived2);

char buffer[NUMBER_OF_POOLS * LARGEST_CHILD_SIZE];

这个方法可行,但是随着 N 增加,这种嵌套的“一元树”将变得非常混乱。有没有可扩展的方法可以实现这个,而不需要手动构建一个逐渐增长成巨大混乱的嵌套“一元树”(暂时没有更好的术语)?
以下是我工作的约束/环境:
- 最大子节点的大小必须在编译时确定。 - 需要 C++03 兼容性。为了满足好奇心,我对任何建议感兴趣(欢迎 C++11!),但最终我将在一个不支持 C++11 的编译器上构建它。我知道 C++11 添加了对具有非 POD 成员的联合体的支持,所以我想您可能可以使用它来简化接口。 - 这将仅在 GCC 中构建,因此 GCC 扩展技术在技术上是可选的。
非常感谢您的帮助和建议。

1
你希望这只适用于你知道的某些类,还是“任何从基类派生的类”?我认为这与几周前关于“派生类大小”的问题类似。我会看看能否找到它。 - Mats Petersson
1
很抱歉,您目前的解决方案可能不具备可移植性:最大派生类型的大小可能导致一些元素的错误对齐(在某些实现上会导致段错误)! - Christophe
2
从一个 constexpr 函数开始,该函数返回所有参数的最大值。在 C++03 中,您可能会使用枚举类型。 - Ben Voigt
不完全相同但类似的问题: http://stackoverflow.com/questions/29356406/is-it-possible-to-know-derived-object-size-in-base-class - Mats Petersson
@MatsPetersson 无论哪种方式都可以。如果整个过程都是自动的,并且新派生类在编译时被考虑进去,那将是非常棒的,但我有一种感觉,这可能是不可能的。如果我必须手动将每个派生类添加到查找“结构”(无论它是如何实现的)中,那也没关系。 - Matt K
显示剩余3条评论
4个回答

4

通过引入一个帮助宏,修改您的代码,可以得到更好的代码格式。当添加一个新的派生类时,只需插入一行STATIC_MAX(sizeof(...,然后加上一个)。这在C++03中有效。

#define STATIC_MAX(a, b)  ((a) > (b) ? (a) : (b))

static const size_t LARGEST_CHILD_SIZE =
    STATIC_MAX(sizeof(Derived0),
    STATIC_MAX(sizeof(Derived1),
    STATIC_MAX(sizeof(Derived2),
               0)));

如果您有C++1x编译器选项,可以使用“可变模板”和“constexpr函数”来获得更简单的解决方案。当添加新的派生类时,只需将类名作为模板参数插入到最后一行即可。

template <typename T>
static constexpr T static_max(T a, T b) {
    return a < b ? b : a;
}

template <typename T, typename... Ts>
static constexpr T static_max(T a, Ts... bs) {
    return static_max(a, static_max(bs...));
}

template <typename... Ts>
constexpr size_t max_sizeof() {
    return static_max(sizeof(Ts)...);
};

static constexpr size_t LARGEST_CHILD_SIZE =
    max_sizeof<Derived0, Derived1, Derived2>();

3

仅适用于POD类:

union AllDerived {
    Derived0 _0;
    Derived1 _1;
    Derived2 _2;
};

static const size_t LARGEST_CHILD_SIZE = sizeof(AllDerived);
static const size_t NUMBER_OF_POOLS = 10;
char buffer[NUMBER_OF_POOLS * LARGEST_CHILD_SIZE];

而且这个解决方案不仅适用于POD:

template <int Value1, int Value2>
struct static_max {
    static const int value = (Value1 < Value2) ? Value2 : Value1 ;
};

template<typename T, typename U>
struct TypeList {
    typedef T Head;
    typedef U Tail;
};

class NullType {};

template
<
    typename T1  = NullType, typename T2  = NullType, typename T3  = NullType,
    typename T4  = NullType, typename T5  = NullType, typename T6  = NullType,
    typename T7  = NullType, typename T8  = NullType, typename T9  = NullType,
    typename T10 = NullType, typename T11 = NullType, typename T12 = NullType,
    typename T13 = NullType, typename T14 = NullType, typename T15 = NullType,
    typename T16 = NullType, typename T17 = NullType, typename T18 = NullType
>
struct MakeTypelist
{
private:
    typedef typename MakeTypelist
    <
        T2 , T3 , T4 ,
        T5 , T6 , T7 ,
        T8 , T9 , T10,
        T11, T12, T13,
        T14, T15, T16,
        T17, T18
    >
    ::Result TailResult;

public:
    typedef TypeList<T1, TailResult> Result;
};

template<>
struct MakeTypelist<>
{
    typedef NullType Result;
};

template<typename TList>
struct MaxTypeSize;

template <>
struct MaxTypeSize<NullType> {
    enum { value=0 };
};

template<typename T, typename U>
struct MaxTypeSize<TypeList<T,U>> {
    enum { value = static_max<sizeof(T), MaxTypeSize<U>::value>::value };
};

typedef MakeTypelist<Derived0, Derived1, Derived2>::Result AllTypes;
static const size_t LARGEST_CHILD_SIZE = MaxTypeSize<AllTypes>::value;
static const size_t NUMBER_OF_POOLS = 10;
char buffer[NUMBER_OF_POOLS * LARGEST_CHILD_SIZE];

在这里,我们使用类型列表和编译时 max 函数。您可以在 Loki 库中找到类型列表的实现。编译时函数 MaxTypeSize 可以计算列表中的最大类型大小。

1
这将编译/工作,但仅因为派生类是POD类型。如果您给任何派生类一个非默认构造函数,则会在没有启用C++11支持的情况下收到编译器错误。我在提问时就知道这一点,所以我应该使用那些在这个联合方法中失败的示例。谢谢! - Matt K
@gomons 这将使用C++11编译。对于C++03,只要将字符串作为成员放入其中,它就无法编译,原因如Matt所解释的那样。请参见此代码片段以进行演示:http://ideone.com/0CjYW2(您可以使用C++11选项复制粘贴它,然后它将正常工作)。 - Christophe
@gomons +1 ;-) 现在变得有趣了!如果有超过3个派生类,你能解释一下如何扩展这种方法吗? - Christophe
@Christophe,我从loki复制了一些代码,现在你可以创建具有18个元素的TypeList。 - gomons
其实对我来说,这有点像魔法,我虽然不太理解,但我可以使用它。 - gomons

3
您可以考虑使用boost::variant并获取其大小:
    sizeof (boost::variant< Derived0, Derived1, Derived2 > )  

这将返回最大元素的大小。

顺便说一句,这还可以简化您的缓冲区管理,使其成为此变体的缓冲区,并直接索引正确的元素。好消息是boost会处理对齐要求:

    typedef boost::variant< Derived0, Derived1, Derived2 > DerivedVar; 
    DerivedVar buffer[NUMBER_OF_POOLS];  

@AndyG,它在GCC 4.9.1下工作正常,但我不知道它是否有保证。 - gomons
1
@gomons:无论是cppreference还是cplusplus.com都明确指出,联合体的大小等于联合体中最大类型的大小。 - AndyG
2
我在回复@gomons时提到过这一点,但如果类不是POD类型,则联合体在C++03中无法工作。我为在我的示例中使用POD类型而感到愚蠢,但我需要这种方法来支持非POD类。这段话来自cppreference:“联合体不能包含具有非平凡特殊成员函数(复制构造函数、复制赋值运算符或析构函数)的非静态数据成员。”这只提到了那些“特殊”的函数,但任何带有非默认构造函数的联合成员在针对C++03标准编译时都无法在GCC 4.9.2中编译。 - Matt K
我进行了测试,得到了错误的答案。std::cout << "variant的大小: " << sizeof (boost::variant<Derived0,Derived1,Derived2>) << std::endl; std::cout << "Derived2的大小: " << sizeof(Derived2) << std::endl; 返回给我的结果是 variant的大小: 20 Derived2的大小: 16 - gomons
@Christophe,你是正确的,+1(= - gomons
显示剩余2条评论

0

我知道 OP 说了 C++03,但我正在寻找 C++1x 的解决方案,并想把它放在这里供任何搜索的人使用。

#include <algorithm>

static const size_t LARGEST_CHILD_SIZE = std::max({sizeof(Derived0), 
                                                   sizeof(Derived1), 
                                                   sizeof(Derived2)}); 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接