【问题标题】:reinterpret_cast vector of pointers to vector of pointers to base classreinterpret_cast 指向基类指针向量的指针向量
【发布时间】:2016-07-23 06:59:21
【问题描述】:

考虑下面这段代码

#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&lt;std::shared_ptr&lt;Derived&gt;&gt; 转换为std::vector&lt;std::shared_ptr&lt;const Base&gt;&gt;&amp; 是否可行并被标准接受。

我用clang-3.8和gcc-6.1用-fsanitize=undefined编译了代码,看起来没问题。但是,我似乎无法找到关于 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);
}

【问题讨论】:

  • 你的代码是一个真实的用例,还是只是一个例子?如果这是一个真实的用例,恕我直言,这是对 reinterpret_cast 的错误使用。如果是示例,请提供您想要实现的真实用例
  • @wasthishelpful 或多或少是真实的用例;在单元测试期间提供数据库假对象
  • 在您的示例中,您可以在主函数中使用 Base 的向量,或者在更新函数中使用模板。我想这在真实情况下是不可能的,但是从您发布的代码中我看不出为什么:)

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


【解决方案1】:

reinterpret_casting 像这样是未定义的行为。当从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));

【讨论】:

  • 我确实知道在需要调整指针时会中断。但在这种特殊情况下并非如此。 container_cast 的好主意,我完全错过了范围构造函数。
【解决方案2】:

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

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

  1. 您将vector 类型的对象转换为引用类型。我真的很想知道为什么会这样。
  2. 您通过不相关的不同类型的引用访问对象。我认为这违反了严格的别名规则,因此是未定义的行为。

如果您使用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

【讨论】:

  • 严格别名的好处,clang-3.8 接受代码。
  • @Rafal 这可能是,但仍然是未定义的行为。只要您不使用-fstrict-aliasing 编译,Gcc 也接受它。
  • @Rafal 如果代码未定义,也可以接受代码。它可以做任何事情。
猜你喜欢
  • 2011-08-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-19
  • 2015-06-17
  • 2020-04-13
  • 1970-01-01
  • 2019-09-10
相关资源
最近更新 更多