【问题标题】:Cast structs with certain common members具有某些公共成员的强制转换结构
【发布时间】:2021-09-07 23:26:11
【问题描述】:

假设我有 2 个structs:

typedef struct
{
    uint8_t useThis;            
    uint8_t u8Byte2;             
    uint8_t u8Byte3; 
    uint8_t u8Byte4;  
} tstr1

typedef struct
{
    uint8_t u8Byte1;            
    uint8_t u8Byte2;             
    uint8_t useThis;  
} tstr2

我将在函数中需要 useThis 成员,但在某些情况下,我需要转换一个结构或另一个:

void someFunction()
{
    someStuff();
    SOMETHING MyInstance;
    if(someVariable)
    {
        MyInstance = reinterpret_cast<tstr1*>(INFO_FROM_HARDWARE); //This line of course doesn't work
    }
    else
    {
        MyInstance = reinterpret_cast<tstr2*>(INFO_FROM_HARDWARE); //This line of course doesn't work
    }
    MyInstance->useThis; //Calling this memeber with no problem

    moreStuff();
}
  • 所以我想使用useThis,不管演员是什么。如何做到这一点?

  • 我想避免 someFunction() 成为模板(只是为了避免 this kind of things

  • 注意像this 这样的问题也有类似的问题,但结构成员的顺序相同

编辑:

在 RealLife 中,这些结构体要大得多,并且有几个“同名”成员。直接将uint8_t 转换为reinterpret_cast&lt;tstr1*&gt;(INFO_FROM_HARDWARE)-&gt;useThis 会很乏味,并且需要几个reinterpret_casts(尽管这是我在编辑之前的问题的有效解决方案)。这就是为什么我坚持MyInstance 是“完整的”。

【问题讨论】:

  • 这并没有解决问题,但在 C++ 中你不必跳 typedef struct { ... } tstr1; 舞蹈。 struct tstr1 { ... }; 工作得很好。
  • 您是否希望 MyInstance 在 if/else 范围之后保留类型信息?这将是困难。否则你可以欺骗它做几乎所有的事情exampleexample - 但我认为这可能是一个 XY 问题。
  • 似乎只是一个uint8_t,是否可以只存储useThis 而不是MyInstance 指针?还是我错过了什么?
  • INFO_FROM_HARDWARE 应该是某种联合。理想情况下是带标签的工会。更理想的是std::variant。不太理想的是std::any
  • @ben10 这是一个小例子,IRL 有几个成员,INFO_FROM_HARDWARE 很大,直接使用int8_t 会乱码

标签: c++ c++11 struct casting reinterpret-cast


【解决方案1】:

使用virtual 调度通常不是您在映射到硬件时想要的,但它一种替代方法。

例子:

// define a common interface
struct overlay_base {
    virtual ~overlay_base() = default;
    virtual uint8_t& useThis() = 0;
    virtual uint8_t& useThat() = 0;
};

template<class T>
class wrapper : public overlay_base {
public:
    template<class HW>
    wrapper(HW* hw) : instance_ptr(reinterpret_cast<T*>(hw)) {}
    uint8_t& useThis() { return instance_ptr->useThis; }
    uint8_t& useThat() { return instance_ptr->useThat; }

private:
    T* instance_ptr;
};

这样,您可以声明一个基类指针,对其进行赋值,并在if 语句之后使用:

int main(int argc, char**) {

    std::unique_ptr<overlay_base> MyInstance;

    if(argc % 2) {
        MyInstance.reset( new wrapper<tstr1>(INFO_FROM_HARDWARE) );
    } else {
        MyInstance.reset( new wrapper<tstr2>(INFO_FROM_HARDWARE) );
    }

    std::cout << MyInstance->useThis() << '\n';
    std::cout << MyInstance->useThat() << '\n';
}

Demo

关于我的评论的解释:“它可以工作,但除非编译器非常聪明并且可以优化掉你内部循环中的虚拟调度,否则它会比你真正花时间键入 cast":

virtual 调度视为在运行时使用了一个查找表(vtable)(这通常是实际发生的情况)。当调用virtual 函数时,程序必须使用该查找表来查找要调用的实际成员函数的地址。当无法优化查找时(正如我在上面的示例中通过使用仅在运行时可用的值确保的那样),与进行静态转换相比,它确实需要额外的几个 CPU 周期。

【讨论】:

  • 这看起来很酷,你能解释一下为什么“它会比你真正花时间打字要慢。” ?
  • @Ivan 我添加了一个解释,但是 - 瑞典仲夏造成了损失,我不确定我是否能很好地解释它。如果不清楚,请发表评论,我会在几天内摆脱阴霾时尝试改进。
  • 这看起来很像类型擦除,聪明!
【解决方案2】:

正如 AndyG 所建议的那样,std::variant 怎么样(没有提到您正在使用的 c++ 标准,所以也许 c++17 解决方案是可以的 - 如果没有 c++17,也值得使用 可用)。

这是example

std::variant 知道其中存储了什么类型,您可以随时使用 visit 来使用其中的任何成员(为了清楚起见,此处为 sn-p):

// stolen from @eerrorika (sorry for that :( )
struct hardware {
    uint8_t a = 'A';
    uint8_t b = 'B';
    uint8_t c = 'C';
    uint8_t d = 'D';
};

struct tstr1 {
    uint8_t useThis;            
    uint8_t u8Byte2;             
    uint8_t u8Byte3; 
    uint8_t u8Byte4;  
};

struct tstr2 {
    uint8_t u8Byte1;            
    uint8_t u8Byte2;             
    uint8_t useThis;  
};

// stuff

if(true)
{
    msg = *reinterpret_cast<tstr1*>(&hw);
} 
else
{
    msg = *reinterpret_cast<tstr2*>(&hw);
}

std::visit(overloaded {
    [](tstr1 const& arg) { std::cout << arg.useThis << ' '; }, 
    [](tstr2 const& arg) { std::cout << arg.useThis << ' '; }
}, msg);

编辑:您还可以使用指针的变体 EDIT2:忘记逃避一些东西......

【讨论】:

    【解决方案3】:

    这就是模板的用途:

    template<class tstr>
    std::uint8_t
    do_something(std::uint8_t* INFO_FROM_HARDWARE)
    {
        tstr MyInstance;
        std::memcpy(&MyInstance, INFO_FROM_HARDWARE, sizeof MyInstance);
        MyInstance.useThis; //Calling this memeber with no problem
        // access MyInstance within the template
    }
    
    // usage
    if(someVariable)
    {
        do_something<tstr1>(INFO_FROM_HARDWARE);
    }
    else
    {
        do_something<tstr2>(INFO_FROM_HARDWARE);
    }
    

    我想避免 someFunction() 成为模板(只是为了避免这种事情)

    为什么我不能将我的模板类的定义与它的声明分开并将其放在一个 .cpp 文件中?

    链接问题对您的用例来说不是问题,因为潜在的模板参数集是有限集。下一个常见问题解答entry 解释了如何:使用模板的显式实例化。

    【讨论】:

    • 也许评论一下这两个成员的类型和名称相同,但位置不同,所以对于非模板来说甚至不是一个通用的初始序列?
    • @DanielMcLaury He's not trying to do static dispatch based on the value of the shared member 我的模板也不是这样。调度基于someVariable,就像问题中一样。 he just wants a uniform way of accessing that member given either a tstr1 * or a tstr2 * 这就是我的模板所做的。它总是访问useThis,它取决于成员的类型。
    • 您好@eerorika 请注意,我需要在someFunction() 中访问MyInstance。所以你的do_something() 应该返回某种容器?也许?
    • @ben10 是的,overloaded 的技巧非常巧妙。它应该包含在标准库中。
    • @Ivan 在这种情况下你可以使用 Boost。
    【解决方案4】:

    您可能需要对结构成员的简单引用:

    uint8_t &useThis=SomeVariable
     ?reinterpret_cast<tstr1*>(INFO_FROM_HARDWARE)->useThis
     :reinterpret_cast<tstr2*>(INFO_FROM_HARDWARE)->useThis;
    

    【讨论】:

    • 这是我在编辑之前的问题的一个很好的答案,我只是添加了缺失的信息。
    猜你喜欢
    • 1970-01-01
    • 2019-08-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-25
    • 1970-01-01
    • 1970-01-01
    • 2023-03-20
    相关资源
    最近更新 更多