重新解释转换为在C ++中具有相同内存布局的对象有多危险?

时间:2017-07-27 15:03:47

标签: c++ undefined-behavior

我已经编写了一系列功能模板,可以尽可能轻松地将任意内容转换为文本。例如, print(std::pair<int, int> {13, 1});将打印{13, 1}以及更长的内容,例如

std::vector<std::tuple<double, std::string>> vect;
for(int i=0;i<3;++i) {
    double root = sqrt(i);
    vect.push_back( {root, "sqrt " + std::to_string(i) } );
}
print(vect);

将输出:{ {0, "sqrt 0" }, {1, "sqrt 1"}, {1.41421, "sqrt 2"} }

我们说我有以下结构:

struct point { int x, y; };

编写类似下面的代码有多危险?

std::vector<point> my_points;
//Add points into my_points;
print(reinterpret_cast<const std::vector<std::pair<int, int>>&>(my_points));

它在gcc中编译并产生预期的输出,虽然我担心如果有人试图移植代码它可能会失败。

3 个答案:

答案 0 :(得分:3)

首先,您没有在此处进行任何动态投射,因为您的代码中没有一次调用dynamic_cast。你在这里做的是由reinterpet_cast制作的c-cast

其次,不,它不安全,并且在许多层面上都是未定义的行为。

答案 1 :(得分:1)

非常危险。你有未定义的行为。 std::vector<std::pair<int, int>>std::vector<Point>是完全,区域和分隔的类,彼此无关。将它们重新解释为未定义的行为。

事实上,标准中定义的案例恰好不适用。尝试将std::vector<char>投射到std::vector<bool>。即使charbool具有相同的大小,但两个向量都不兼容。

如果您想避免复制缓冲区,请考虑使用模板:

template<typename T>
void print(const std::vector<T>& vec) {
    // ...
}

更好的是,不要强迫矢量。如果您有std::array<Point, n>,则可能需要使用您的功能:

template<typename T>
void print(const T& range) {
    // ...
}

答案 2 :(得分:0)

您可以传递两个std::vector<T>,一个const T*和一个长度,或两个指针的结构,而不是传递const T*

现在你知道没有专门化(如std::vector<bool>的情况,请参阅Why is vector<bool> not a STL container?),你也知道你有T:s的连续记忆。现在提供知道

  1. Foo s和Bar s具有完全相同的布局(对齐,大小)
  2. FooBar共享使用行为(如果有一个函数f(Foo),并且您在print中使用该函数,还必须有一个语义相同的函数f(Bar))。更理论的表述如下:Foo相当于Bar相当于f
  3. 然后,它应该是安全的,但你是你自己的,因为编译器无法真正检查(2)。例如,使用两个版本的Point(让我们使用float代替int):

    struct PointXY{float x;float y;};
    
    struct PointYX{float y;float x;};
    
    float inner_product(PointXY p1,PointXY p2)
        {return p1.x*p2.x + p1.y*p2.y;}
    
    float inner_product_with_important_y(PointXY p1,PointXY p2)
        {return 0.5f*( p1.x*p2.x + 4.0f*p1.y*p2.y );}
    

    也就是说,您可以使用任意组合inner_productPointXY安全地拨打PointYX(同时满足(1)和(2)),但只要您调用各向异性版本,你会得到错误的结果((1)满足,但不是(2))。