reinterpret_cast指向基类

时间:2016-07-23 06:59:21

标签: c++ reinterpret-cast class-hierarchy

考虑以下代码

#include <algorithm>
#include <iostream>
#include <memory>
#include <vector>

struct Base {
    int x;

    Base(int x) : x(x) {}
};

struct Derived : public Base {
    int y, z;

    Derived(int x) : Base(x), y(x + 1), z(x + 2) {}
};

void update(const std::vector<std::shared_ptr<const Base>>& elements) {
    for (const auto elem : elements) {
        std::cout << elem->x << "\n";
    }
}

int main(int, char**) {
    std::vector<std::shared_ptr<Derived>> elements(4);

    {
        int ctr = 0;
        std::generate(begin(elements), end(elements), [&ctr]() { return std::make_shared<Derived>(++ctr); });
    }

//    update(elements); // note: candidate function not viable: no known conversion from 'vector<shared_ptr<Derived>>' to 'const vector<shared_ptr<const Base>>' for 1st argument
    update(reinterpret_cast<std::vector<std::shared_ptr<const Base>>&>(elements));  // ok

    return 0;
}

我的问题是,如果使用reinterpret_caststd::vector<std::shared_ptr<Derived>>投射到std::vector<std::shared_ptr<const Base>>&是可行的并且被标准接受。

我用clang-3.8编译了代码,用-fsanitize=undefined编译了gcc-6.1,看起来没问题。但是,我似乎无法找到关于cppreference的正确解释。

当然,我可以很容易地创建一个适当的函数,但它比单行reinterpret_cast长,并且需要一个临时向量。

void update(const std::vector<std::shared_ptr<Derived>>& elements) {
    std::vector<std::shared_ptr<const Base>> casted(elements.size());
    std::copy(begin(elements), end(elements), begin(casted));
    update(casted);
}

2 个答案:

答案 0 :(得分:2)

像这样的{p> reinterpret_cast是未定义的行为。从Derived*转换为Base*时,代码将中断,需要指针调整。当Derived使用多重继承并且Base不是它的第一个基类时,很可能会发生这种情况。

struct Derived : public X, public Base { ... };

Derived* d = new Derived;

Base* b = d; // this is no longer a "no-op", since the Base sub-object
             // of Derived is not at offset 0:
             //
             // d  b
             // |  |
             // v  v
             // [Derived]
             // [X][Base]

如果您的目标是让它以最简洁的方式运行而不是通过临时向量来避免转换,那么在这种特殊情况下,您可以使用this answer中提出的container_cast实用程序。

update(container_cast(elements));

答案 1 :(得分:2)

一般模板和容器(我将shared_ptr视为一种特殊形式的容器)在C ++中不协变。这意味着,如果您有两种类型BaseDerived < Base以及template<typename T> class X {};,则X<Base>X<Derived>是两个完全不同的东西,而不是任何形式的关系。

在您的情况下,您有一个std::vector<std::shared_ptr<Derived>>类型的对象,然后创建一个std::vector<std::shared_ptr<const Base>>&,然后用它来访问它。我认为这有两个问题:

  1. 您将类型为vector的对象强制转换为引用类型。我真的很想知道为什么会这样。
  2. 您可以通过不相关的不同类型的引用来访问该对象。我认为这违反了严格的别名规则,因此是未定义的行为。
  3. 如果使用gcc -fstrict-aliasing编译代码,编译器将假定您的程序符合规则并对其进行优化。它会产生一个警告:

    > Start prog.cc: In function 'int main(int, char**)': prog.cc:33:80:
    > warning: dereferencing type-punned pointer will break strict-aliasing
    > rules [-Wstrict-aliasing]
    >      update(reinterpret_cast<std::vector<std::shared_ptr<const Base>>&>(elements));  // ok