公共基类破坏元组的空基类优化
Common base class breaks empty base class optimization for tuples
gcc 4.7.1为元组执行空基类优化,我认为这是一个非常有用的特性。然而,这似乎有一个意想不到的限制:
#include <tuple>
#include <cstdint>
#include <type_traits>
class A { };
class B : public A { std::uint32_t v_; };
class C : public A { };
static_assert(sizeof(B) == 4, "A has 32 bits.");
static_assert(std::is_empty<C>::value, "B is empty.");
static_assert(sizeof(std::tuple<B, C>) == 4, "C should be 32 bits.");
在这种情况下,最后一个断言失败,因为元组实际上大于4个字节。有没有办法在不破坏类层次结构的情况下避免这种情况?或者我必须实现我自己的对实现,它以其他方式优化了这种情况?
空对象必须占用一些空间的原因是两个不同的对象必须具有不同的地址。例外情况是,派生类型的基子对象可以具有与派生完整对象相同的地址(如果派生类型的第一个非静态成员与基[*]不是同一类型。空基优化使用该地址来移除任意添加到空基的附加空间,以确保任何完整对象的sizeof x!=0
。
在您的情况下,元组包含两个A
子对象,一个是C
的基,另一个是B
的基,但它们是不同的,因此它们必须具有不同的地址。这两个对象都不是另一个的基子对象,因此它们不能具有相同的地址。你甚至不需要使用std::tuple
来看到这种效果,只需创建另一种类型:
struct D : B, C {};
CCD_ 6的大小将严格大于CCD_ 7和CCD_。为了检查实际上是否有两个A
子对象,您可以尝试向上转换到指向A
的指针,编译器会很乐意向您的方向抛出一些模糊性错误。
[*]标准也明确禁止这种情况,原因相同:
struct A {};
struct B : A { A a; };
同样,在这种情况下,在类型为B
的每个完整对象中,都有两个A
对象,并且它们必须具有不同的地址。
正如您在实验中很容易看到的那样,这种优化可能很难实现。当tuple
是另一个类的数据成员时(尤其是当我预期将该类放入容器中时(,我发现这种优化最有用。您是否可以在此tuple
中捆绑其他数据成员而不暴露该事实?例如:
class D
{
std::tuple<B, int, C, int> data_;
public:
B& get_B() {return std::get<0>(data_);}
C& get_C() {return std::get<2>(data_);}
int& get_size() {return std::get<1>(data_);}
int& get_age() {return std::get<3>(data_);}
};
对我来说,这并不能保证,std::tuple<B, int, C, int>
只有12个字节,因此C
正在被优化。
- 继承:构造函数,初始化C++11中基类的类C数组成员
- 继承和友元函数,从基类访问受保护的成员
- 将子类方法声明为基类的友元
- 类内部和外部静态 constexpr 元组之间的差异
- 类似元组的类模板的反向内存布局
- 基类中受保护的纯虚函数如何被基类的友元类使用?
- 如何使用数据成员填充派生类的对象到基类的指针数组中
- 即使基类和派生类只使用基元数据类型,我是否需要定义虚拟析构函数
- 使用每种类型的可变参数模板上的类模板初始化元组
- 从基类数组调用派生函数
- 带有元组声明的类模板
- 获取继承基类类型的元组
- 获取所有基类 [元编程]
- 通过drived类模板值通过基类构造器初始化基类数组成员变量
- 使用variadic模板创建模板类元组
- 指向基类数组的指针,填充派生类
- 基类数组中重写函数的使用
- 派生类是否可以在类声明中定义基类数组的大小
- 类元组类型(tuple,pair)的模板重载
- 公共基类破坏元组的空基类优化