C++策略模式设计:继承 vs 组合

7

在Meeting C++ 2019上,Jon Kalb讲述了关于模板技术的内容,并提到了策略类。可以在此处查看来源:https://youtu.be/MLV4IVc4SwI?t=1815

有趣的代码片段如下:

template<class T, class CheckingPolicy>
struct MyContainer : private CheckingPolicy
{
    ...
}

我经常看到这种设计,不知道通过继承是否真的比组合有更大优势。从我的个人经验来看,我听说过很多关于“优先使用组合而非继承”的范式。所以我写代码的方式更像是这样:

template<class T, class CheckingPolicy>
struct MyContainer
{
    CheckingPolicy policy;
    ...
}

这里不涉及任何虚拟函数。但是,如果你能分享一些见解,并让内容更加通俗易懂,我会非常感激。我尤其关注内存布局的差异及其影响。如果CheckingPolicy没有数据成员,只有一个check方法或重载的调用运算符,是否会有所不同?

1个回答

4
可能的原因之一:当您从CheckingPolicy继承时,您可以受益于空基类优化
如果CheckingPolicy空的(即它没有非静态数据成员,除了大小为0的位域、没有虚函数、没有虚基类和没有非空基类),它将不会对MyContainer的大小产生贡献。
相反,当它是MyContainer的数据成员时,即使CheckingPolicy为空,MyContainer的大小也会增加至少一个字节。至少,由于对齐要求,您可能会有额外的填充字节。
这就是为什么在实现std::vector时,你会发现使用了分配器继承。例如,libstdc++的实现
template<typename _Tp, typename _Alloc>
struct _Vector_base {
    typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template
        rebind<_Tp>::other _Tp_alloc_type;

    struct _Vector_impl : public _Tp_alloc_type, public _Vector_impl_data { 
        // ...
    };

    // ...
};

无状态分配器(例如没有非静态数据成员的CheckingPolicy)不会对std::vector的大小产生贡献。

在C++20中,我们将有[[no_unique_address]]来潜在地解决这个问题:虽然空基类优化是标准布局类型所必需的,但[[no_unique_address]]只是一种许可,而不是要求。(感谢Nicol Bolas指出这一点。)


2
为了解决这个问题。为了可能地解决这个问题。与标准布局的 EBO 不同,no_unique_address 没有任何保证。 - Nicol Bolas

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