【问题标题】:Is it safe to cast to a class that has the same data member layout, but a different implementation?转换为具有相同数据成员布局但实现不同的类是否安全?
【发布时间】:2016-09-08 02:31:51
【问题描述】:

第一个类将用于私有继承,以确保完全相同的布局。这应该可以确保投射安全。

#include <iostream>
#include <string>

struct data_base
{
    data_base( int i, std::string&& s ) noexcept
        : i_{ i }
        , s_{ std::move( s ) }
    {}

    int i_;
    std::string s_;
};

在这个简单的示例中,我首先打印int 数据成员,然后是std::string 数据成员,用于data&lt;true&gt; 的实例。

template<bool = true>
struct data : private data_base // inherits
{
    data( int i, std::string&& s ) noexcept
        : data_base( i, std::move( s ) )
    {}

    void print()
    {
        std::cout << "data<true> - " << i_ << s_ << '\n';
    }
};

但是,data&lt;false&gt; 首先打印 std::string 数据成员,然后是 int 数据成员。

template<>
struct data<false> : private data_base
{
    void print()
    {
        std::cout << "data<false> - " << s_ << i_ << '\n';
    }
};

例子:

int main()
{
    data<true> d{ 5, "abc" };
    d.print();
    ( ( data<false>& )d ).print();
}

演示:http://coliru.stacked-crooked.com/a/8b1262afe23dc0a2

如演示所示,即使打开-fstrict-aliasing 标志,也不会出现警告。

现在,由于它们具有相同的布局,我认为我可以在这两种类型之间进行转换以获得不同类型的静态多态性;无需虚函数调用。

这种用法安全还是我触发了未定义的行为?

【问题讨论】:

  • 不确定std::string 是否保证是标准布局类
  • 我认为出于严格别名规则的目的,print 调用算作“使用存储的值”,但不是 100% 确定
  • 严格来说,无关类型之间的任何强制转换都会导致未定义的行为。此外,在 C++ 中,建议使用 C++ 类型转换操作符而不是 C 风格类型转换。
  • @antred 在指针或对不相关类型的引用之间进行转换肯定不是 UB。问题出现在引用对象的某些用途中。参见 C++14 [expr.reinterpret.cast]/7。即使是未对齐的强制转换也是未指定(从 C++14 开始),而不是像在 C 中那样未定义。

标签: c++ performance casting type-conversion language-lawyer


【解决方案1】:

这或多或少是here所描述的,即所谓的增强突变成语

据说(强调我的):

Boost 突变惯用语使用 reinterpret_cast,并在很大程度上依赖于假设具有相同数据成员(类型和顺序)的两个不同结构的内存布局是可互换的。 虽然 C++ 标准不保证这个属性,但几乎所有的编译器都满足它。此外,如果只使用 POD 类型,则突变成语是标准的。


注意:该页面已经过时了,我不知道最近的修订是否改变了上面提到的保证

【讨论】:

  • 问题不在于这是否能正常工作。这几乎是给定的。任何合理理智的编译器都会处理这个问题而不会出现令人讨厌的意外。如果不是这种情况,大量现有代码将会中断。问题是这样的演员表(以及使用演员表的结果)是否有未定义行为的风险,答案是肯定的。
  • 好的,既然我已经重读了你的答案,它实际上确实提供了对 OP 问题的答案,所以请忽略我的评论。
  • @antred 没问题,随时欢迎批评。 ;-) ...请注意,您可以根据需要删除您的 cmets。
  • 所以它应该可以正常工作,但仅适用于 POD 类型。因此我的用法是错误的,因为std::string 不是 POD?
  • @user2296177 它说如果只使用 POD 类型,突变成语是标准的,所以它是有道理的。无论如何,我的知识还不够深入,所以我不能说你的用法是否真的错误。对不起。
【解决方案2】:

根据语言规范中的 [expr.reinterpret.cast]/11,您可以将引用从一种类型转换为另一种类型(如果您可以将指向一种类型的指针转​​换为另一种类型)。

使用您的类布局,两种类型都有一个通用的基类来保存所有数据。这两个派生类型不添加任何数据成员,也不添加任何虚函数,因此两个类的对象布局将是相同的。

所以使用reinterpret_cast是安全的。

在这种情况下,这类似于将基类转换为引用,然后将该引用转换为另一个派生类。

【讨论】:

  • 在我看来,[expr.reinterpret.cast]/11 只是说可以执行转换,但不清楚使用转换结果是否会导致定义的行为。看到 data 不是 data,我的期望是它是未定义的。
  • @antred 通常使用转换的结果是未定义的。但是在 OP 询问的这种特定情况下,由于 data&lt;true&gt;data&lt;false&gt; 共享一个没有虚函数的公共基类,并且两个类都没有添加任何数据或虚函数,因此两个类的内存布局将是相同的。
猜你喜欢
  • 2020-12-09
  • 2013-11-17
  • 2020-07-24
  • 1970-01-01
  • 2011-08-15
  • 1970-01-01
  • 2013-05-05
  • 1970-01-01
  • 2017-06-10
相关资源
最近更新 更多