【问题标题】:C++ Casting back from member pointer to holding class pointerC ++从成员指针转换回持有类指针
【发布时间】:2015-02-15 16:59:30
【问题描述】:

我有以下课程:

class SSVec
{
public:
    float values[8];
}

我有一个对象SSVec obj,我将float* 指针obj.values 传递给另一个函数。在代码的其他地方,我得到了这个 float* 指针,然后将它转换回 SSVec* 指针。

这可能以 C++ 标准定义的行为方式吗?大多数情况下,这将适用于静态转换,但我的猜测是它实际上是未定义的行为。

原因是float* 指针被传入和传出一个DLL,它对SSVec 一无所知。我保证传递的指针总是指向一个 SSVec::value[8] 对象成员。

该类可能更复杂,但它不派生自任何东西,没有虚函数,并且仅包含 POD 类型。 values是第一个成员

问题可以重新表述:通过static_cast,类地址和第一个成员地址是否保证相同?

【问题讨论】:

  • 可能没有未定义的行为,但仅限于一些非常有限的情况。其中一种受限情况是,如果 SSVec 是 POD 类型,那么您可以从指针中减去 values 成员的偏移量。
  • 这是std::array<float,8>的重新实现吗?
  • @leemes : 大致但带有 SSE/AVX 内在优化。
  • @leemes :另外,它必须通过混合编译器的 DLL 边界

标签: c++ class casting member


【解决方案1】:

如果SSVec 是 POD 类型,则定义为行为。你可以在为你的类型实现一个特殊的转换函数时静态地断言这个:

SSVec* SSVec_cast(float* ptr) {
    // Break if someone changes SSVec to be no POD anymore:
    static_assert(std::is_pod<SSVec>::value, "SSVec is no longer a POD!");

    // Break if someone changes SSVec to contain more than the array:
    // [ NOTE: This is optional. Behavior is still defined if the structure
    //   changes(*), but then only if the pointer really points into an SSVec.
    //   With these assertions included, you can even cast from a different
    //   float array of size 8, even if it hasn't been declared as a SSVec. ]
    static_assert(sizeof(SSVec) == 8 * sizeof(float), "SSVec has wrong size!");
    static_assert(sizeof(SSVec::values) == sizeof(SSVec), "SSVec has wrong structure!");
    static_assert(offsetof(SSVec, values) == 0, "SSVec has wrong structure!");

    // Now it is safe to reinterpret cast the pointer:
    // [ (*) NOTE: If above assertions are removed, please change (ptr)
    //   to (reinterpret_cast<char*>(ptr) - offsetof(SSVec, values)). ]
    return reinterpret_cast<SSVec*>(ptr);
}

通过重载 const 指针也可以做到这一点;当然,您可以将这些断言移动到某个通用函数或全局范围内(最好)。


PS:请查看std::array。它完全符合您的要求:

typedef std::array<float,8> SSVec;

【讨论】:

  • 感谢您的详细回答。这门课是关于 SSE/AVX 内在优化,同时在非 SSE/AVX 平台上保持可移植性,这就是我不使用普通 std::array 的原因
  • 我可以将float* 转换为std::array&lt;float,8&gt;* 吗?我不能通过指针/引用传递std::array,因为它跨越了 DLL 边界
【解决方案2】:

您可以使用offsetof 来获取指向该类的指针。一些例子:

#include <cstddef>
#include <iostream>

class SSVec
{
public:
    int someOtherValueThatMakeEverythingMoreComplicated;
    float values[8];
};

SSVec* getSSVeciFromValuesPointer(float* floatPointer)
{
    char* rawPointer = reinterpret_cast<char*>(floatPointer);
    char* movedPointer = rawPointer - offsetof(class SSVec, values);

    return reinterpret_cast<SSVec*>(movedPointer);
}

void callBack(float* p)
{
    std::cout << "Callback: float*: " << p << std::endl; 

    SSVec* vec = getSSVeciFromValuesPointer(p);

    std::cout << "Callback: vec*: " << vec << std::endl;
}

typedef void DummyCallback(float*);

void functionThatCanNotBeChanged(float* parameter, DummyCallback callback)
{
    std::cout << "FunctionThatCanNotBeChanged: " << parameter << std::endl;
    callback(parameter);
}

int main()
{
    SSVec vec;

    std::cout << "Vec pointer: " << &vec << std::endl;
    functionThatCanNotBeChanged(vec.values, &callBack);

    return 0;
}

我得到的结果:

Vec pointer: 0x7fff85655f90
FunctionThatCanNotBeChanged: 0x7fff85655f94
Callback: float*: 0x7fff85655f94
Callback: vec*: 0x7fff85655f90

【讨论】:

  • 直到今天我才知道offsetof。这是富有成效的一天
【解决方案3】:

我刚刚遇到了这个问题和前两个答案。虽然chosen answer 非常简洁、正确且特定于问题的细节(给定的类是 POD),但我想为到达这里寻找有关“C++ 从成员指针转换为持有”的信息的人们澄清一些事情类指针”,如标题所示。

简而言之,C++11 标准将 POD 的技术定义分为两个独立的概念:普通类标准布局类。由于 C++11 成为标准,调用类型“POD”(或更正式地称为“POD 结构”)意味着它既是琐碎 标准-布局

但是,为了将指向对象的指针转换为指向第一个成员的指针或从指向第一个成员的指针转换,重要的是对象的类型是标准布局类。特别是,对象的类型是否也是一个普通类并不重要。

这意味着,例如,原始问题中的 SSVec 类可能具有用户定义(非平凡):默认构造函数、复制/移动构造函数/赋值运算符和/或析构函数。

我认为注意这一点很重要,因为这意味着这种类型的转换以及其他一些有趣的转换是为更多类型的类定义的行为,而不是仅限于 POD。出于这个原因,标准布局的 C++11 概念最初有时被非正式地称为“宽松”POD。

因此,chosen answer 中示例代码的第一部分可以安全地更改为:

SSVec* SSVec_cast(float* ptr) {
    // Break if someone changes SSVec to be a non-standard-layout class:
    static_assert(std::is_standard_layout<SSVec>::value, "SSVec is no longer a standard-layout class!");
    ...

注意事项:

  • C++14 标准没有改变 POD 结构、标准布局类或普通类的定义。

  • 我没有检查过 C++17 标准,但我也没有读到任何地方显着改变了这些定义。

  • 在编写依赖于标准布局类的代码时,始终使用static_assert 作为选择的答案显示,这一点再怎么强调也不为过;只需在测试中使用std::is_standard_layout 而不是std::is_pod

  • 在 C++17 下,您可以使用 std::is_standard_layout_v&lt;SSVec&gt; 代替 std::is_standard_layout&lt;SSVec&gt;::value

另见

cppreference 中的 C++ 标准类型特征:

  • std::is_pod:普通布局和标准布局。

  • std::is_standard_layout:标量类型、标准布局类或此类类型/类的数组,可能是 cv 限定的

  • std::is_trivial:标量类型,具有普通默认构造函数的普通可复制类,或此类类型/类的数组,可能是 cv 限定的。

cppreference 中的伪概念:

有关高级信息,这些 stackoverflow 文章:

【讨论】:

    猜你喜欢
    • 2021-09-20
    • 2016-10-10
    • 1970-01-01
    • 2018-03-24
    • 1970-01-01
    • 2021-12-22
    • 2016-08-24
    • 1970-01-01
    相关资源
    最近更新 更多