【问题标题】:Common base class breaks empty base class optimization for tuples公共基类打破了元组的空基类优化
【发布时间】:2013-02-25 21:37:10
【问题描述】:

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 个字节。有没有办法在不破坏类层次结构的情况下避免这种情况?还是我必须实现我自己的对实现,以其他方式针对这种情况进行优化?

【问题讨论】:

  • 也许this 可以提供帮助
  • @AndyProwl,读起来很有趣,但我看不到直接的联系。它说元组的两种常见实现都使用 EBCO,但没有说明我在这里看到的 EBCO 故障类型。
  • 该优化不适用于您的情况。在您的元组中有 两个 A 对象,每个对象必须有不同的地址。你不需要在代码中添加元组,你可以添加第四个类型:struct D : B, C {};,你会看到sizeof(D) &gt; sizeof(B)
  • 我不确定这与空基类优化有什么关系。 class C 的大小仍然必须至少为 1,无论它是否派生自 class A 以及 class A 是否在 class C 中被“优化掉”。 std::tuple&lt;&gt; 不使用继承将参数化类型打包成一个元组,因此std::tuple 不会增加 EBCO 发生的机会。所以我不确定“gcc 4.7.1 对元组进行空基类优化”是什么意思。
  • @MichaelBurr:对于 empty 参数类型,gcc will use inheritance 附带的 libstdc++ 将内容打包到元组中。一种实际应用是,例如unique_ptr won't require more space than a classical dumb pointerthe document pointed out by Andy Prowlthis one here 都会解释一些细节。

标签: c++ gcc c++11 libstdc++ stdtuple


【解决方案1】:

空对象必须占用一些空间的原因是两个不同的对象必须具有不同的地址。例外情况是派生类型的基子对象可以与派生的完整对象具有相同的地址(如果派生类型的第一个非静态成员与基 [*] 。空基优化使用它来删除任意添加到空基的额外空间,以确保sizeof x!=0 用于任何完整对象。

在您的情况下,元组包含 两个 A 子对象,一个是C 的基础,另一个是B 的基础,但它们不同,因此它们必须具有不同的地址。这两个对象都不是另一个对象的基本子对象,因此它们不能具有相同的地址。你甚至不需要使用std::tuple 就可以看到这个效果,只需创建另一个类型:

struct D : B, C {};

D 的大小将严格大于BC 的大小。要检查实际上是否有两个 A 子对象,您可以尝试向上转换为指向 A 的指针,编译器会很乐意向您的方向吐出一些歧义错误。

[*] 标准也明确禁止这种情况,原因相同:

struct A {};
struct B : A { A a; };

同样,在这种情况下,在每个B 类型的完整对象中,有两个A 对象,它们必须具有不同的地址。

【讨论】:

    【解决方案2】:

    这种优化可能很难实现,您可以通过实验轻松看到。当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&lt;B, int, C, int&gt; 只有 12 个字节,所以 C 正在被优化掉。

    【讨论】:

    • 不幸的是,在我的情况下不是一个选项,但总的来说很有用。就我而言,我放弃了来自A 的继承,因为在这种特定情况下它不是绝对必要的。其他情况可能需要其他方法,其中包括您的方法。
    • @MvG:继承是最常被滥用的结构之一。如果没有必要,就不要使用它。
    • @DavidRodríguez-dribeas:就我而言,共同祖先是一种简单的标记类型。我原本打算将其作为一种允许模板化运算符应用于我的所有类但不应用于其他类的方法。我想我会实现一个单独的谓词模板。如果 C++ 有概念,我可能不太想(ab)对这种事情使用继承。但无论如何我都会记住这一课。
    • @MvG:这就是没有将boost::noncopyable 添加到标准中的原因。虽然它在许多情况下是免费,但有些结构是有代价的,特别是在某些特定情况下存在多重继承的情况下,包括这个问题中的那个:)
    猜你喜欢
    • 1970-01-01
    • 2013-04-15
    • 1970-01-01
    • 2020-09-11
    • 2015-02-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多