基于 C++ 策略的设计:继承与组合
C++ policy based design: Inheritance vs composition
在 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
方法或一个重载的调用运算符,会有什么不同吗?
一个可能的原因:当你从CheckingPolicy
继承时,你可以从empty base class optimization中受益。
如果 CheckingPolicy
是 empty(即除了大小为 0
的位域外,它没有非静态数据成员,没有虚函数,没有虚基 类,并且没有非空基 类),它不会影响 MyContainer
.
的大小
相反,当它是MyContainer
的数据成员时,即使CheckingPolicy
为空,MyContainer
的大小也会增加至少一个字节。至少,由于对齐要求,您可能会有额外的填充字节。
这就是为什么,例如,在 std::vector
的实现中,您可以从分配器中找到 ihnehritance。例如libstdc++的implementation:
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 指出这一点。)
在 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
方法或一个重载的调用运算符,会有什么不同吗?
一个可能的原因:当你从CheckingPolicy
继承时,你可以从empty base class optimization中受益。
如果 CheckingPolicy
是 empty(即除了大小为 0
的位域外,它没有非静态数据成员,没有虚函数,没有虚基 类,并且没有非空基 类),它不会影响 MyContainer
.
相反,当它是MyContainer
的数据成员时,即使CheckingPolicy
为空,MyContainer
的大小也会增加至少一个字节。至少,由于对齐要求,您可能会有额外的填充字节。
这就是为什么,例如,在 std::vector
的实现中,您可以从分配器中找到 ihnehritance。例如libstdc++的implementation:
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 指出这一点。)