首页 > 解决方案 > 多个 CRTP 基类的对齐

问题描述

在 CRTP 中,基础对象可以通过静态转换返回对派生对象的引用

在多重继承的情况下也是如此吗?第二个碱基及以后可能位于与派生对象不同的地址。例如考虑:

#include <iostream>
#include <string_view>

template<typename Derived>
struct Base1
{
    char c1;
};

template<typename Derived>
struct Base2
{
    char c2;
    auto& get2() const
    {
        return static_cast<const Derived&>(*this); // <-- OK?
    }
};

struct X : public Base1<X>, public Base2<X>
{
    X(std::string_view d) : data{d} {}

    std::string_view data;
};


int main()
{
    auto x = X{"cheesecake"};

    std::cout << x.get2().data << std::endl;
}

gcc 的未定义行为分析器说这是未定义行为
clang 的未定义行为分析器没有检测到问题

标准是否说明其中哪一个是正确的?

更新:
gcc 中的错误现在已在主干上修复。

标签: c++language-lawyermultiple-inheritanceundefined-behaviorcrtp

解决方案


是的,这是定义的,你的代码是可以的。多重继承是转换的指针与原始指针不同的罕见情况。

如果你去源:

[expr.static.cast.11]类型为“指向 cv1 B”的纯右值,其中 B 是类类型,可以转换为类型为“指向 cv2 D”的纯右值,其中 D 是派生自的完整类B,如果 cv2 与 cv1 的 cv-qualification 相同或更高 cv-qualification。……

[expr.static.cast.2]类型“cv1 B”的左值,其中 B 是类类型,可以转换为类型“对 cv2 D 的引用”,其中 D 是从 B 派生的类,如果 cv2 是与 cv1 相同的 cv 限定或更高的 cv 限定。...

这意味着您使用的强制转换是有效的,并且只要您不丢弃任何 cv 限定符,您使用指针也同样有效。

据我所知,这是 sanitizer 中的一个错误,它在必须重定向引用时无法转换。

首先,这与 CRTP 无关。以下内容完全相同,CRTP 只是自动为我们完成。

Base2<X>* base = &x;
const X* orig = static_cast<const X*>(base);

std::cout << &x << std::endl;
std::cout << base << std::endl;
std::cout << orig << std::endl;

输出:

0x7ffc7eeab4d0
0x7ffc7eeab4d1
0x7ffc7eeab4d0

这是正确的,gcc 的消毒剂不会抱怨任何事情。

但是,如果您更改指向引用的指针:

X x{"cheesecake"};

Base2<X>& base = x;
const X& orig = static_cast<const X&>(base);//Line 36

std::cout << &x << std::endl;
std::cout << &base << std::endl;
std::cout << &orig << std::endl;

突然你得到

0x7ffdbf87cf50
0x7ffdbf87cf51
0x7ffdbf87cf50

Program stderr

example.cpp:36:14: runtime error: reference binding to misaligned address 0x7ffdbf87cf51 for type 'const struct X', which requires 8 byte alignment
0x7ffdbf87cf51: note: pointer points here
 00 00 00  60 cf 87 bf fd 7f 00 00  0a 00 00 00 00 00 00 00  66 20 40 00 00 00 00 00  6d 19 40 00 00
              ^ 

这意味着输出再次正确,但消毒剂在回退时不正确地重定向引用。


推荐阅读