multi从struct和template struct继承结构,访问基本非模板struct数据时的排序很重要

时间:2018-12-06 19:52:10

标签: c++ inheritance struct

#include <stdlib.h>
#include <string>
#include <atomic>

struct base_C_event {
    const char* ev;
    const char* da;
};

template <class T>
struct ref_counter {

    private:
        std::atomic<std::ptrdiff_t> _counter;
};

struct derived_event : ref_counter<derived_event>, base_C_event {

    derived_event() : event_type(), event_data() {
        ev = event_type.c_str();
        da = event_data.c_str();
    }

    std::string event_type;
    std::string event_data;
};

struct derived_event2 : base_C_event, ref_counter<derived_event2> {
    derived_event2() : event_type(), event_data() {
        ev = event_type.c_str();
        da = event_data.c_str();
    }

    std::string event_type;
    std::string event_data;
};

struct some_cool_event {
    int type;
    void* payload;
};

void OnEvent(const some_cool_event* event) {
    auto e = static_cast<base_C_event*>(event->payload); //...and then shows itself here
    printf("%s - %s\n", e->ev, e->da);
}

int main() {
    derived_event evt;
    evt.event_type = "type";
    evt.event_data = "Hello World";

    derived_event2 evt2;
    evt2.event_type = "hi";
    evt2.event_data = "there";

    some_cool_event my_event;
    my_event.type = 1;
    my_event.payload = &evt; //Problem starts here...
    OnEvent(&my_event);

    my_event.type = 2;
    my_event.payload = &evt2;
    OnEvent(&my_event);

    return 0;
}

输出:(与g ++编译)

(空)-输入

类型-Hello World

现在,在我的真实环境(XCode)中,derived_event的继承顺序导致BADACCESS异常;使用g ++时,它只会产生(null),如输出所示。

但是,derived_event2的排序工作正常。

我理解标准的方式,多重继承的顺序会影响构造函数和析构函数的顺序以及内存的布局。谁能解释这里发生了什么?

编辑: 我实际上已经弄清楚了。将事件对象设置为void *有效负载,然后将随后的static_cast <>返回基本类型的行似乎使第一个指针(ev)无效,因为该结构仅成为内存布局在这一点上,因此将指针设置为前两个指针大小块……在这种情况下为std::atomic<std::ptrdiff_t>,然后设置为base_C_event。因此,强制转换会获取std :: atomic的数据,并将其用作ev的指针地址,而派生对象中最初的ev现在就是da指向的内容

很遗憾,在我的实际情况下,我无法在base_C_event中使用derived_event的合成来发送。这就是为什么存在refcounting的原因,所以我必须发送派生对象,以便以后在回调中可以减少refcount。

是否有防止这种情况发生的方法?

2 个答案:

答案 0 :(得分:0)

如果将指针强制转换为void *,则在强制转换回实际类型时始终执行完全相反的强制转换。

所以,如果您有:

D *d = new D;
void *v = d;  // Here D* is casted to void *

当您找回指针时,请使用反向强制转换。以下示例是正确的:

D *d2 = static_cast<D *>(v);
A *a2 = static_cast<D *>(v);
B<D> *b2 = static_cast<D *>(v);

更好的是,如果可以的话,请尝试避免使用void *。它很容易导致难以发现的错误,使用多重继承时甚至更糟。

如果必须使用void *,请尝试在代码中尽可能在本地进行操作,以便在理想情况下针对每个方向在代码中的某个位置准确地完成转换。

class VoidMember
{
public:
    void set(D *d) { v = d; }
    D *get() { return static_cast<D *>(v);

private:
    // In reality, you would not store a void like that but assume this is stored in 
    // a library / API that use `void *`
    void *v;
};

尽管有时强制转换为其他类型,但应避免这种情况,因为如果在某些时候对代码进行重构(例如对基类进行重新排序),它会使代码更加脆弱。

答案 1 :(得分:0)

嗯,我想我知道问题出在哪里了

struct D : B<D>, A { };

通过这种方式,您可以同时继承B<D>a A实例。实际上,它看起来像这样:

struct D
{
    B<D> implicitly_inherited_B_D;
    A    implicitly_inherited_A;
};

您现在执行以下操作:

D* d    = new D();
void* v = d;
A* a    = static_cast<A*>(v);

问题是:v现在指向D实例,该实例与继承的B<D>实例共享其地址。但是您将指针投射回了A*,但是D的{​​{1}}有一个偏移量。因此,您所做的对应于:

A

这注定会失败...

如果要回退到D* d = new D(); void* v = &d->implicitly_inherited_B_D; A* a = static_cast<A*>(v); // or equivalent: A* aa = reinterpret_cast<A*>(&d->implicitly_inherited_B_D); ,则需要确保指针实际上指向A*中继承的A-这很简单:

D

为进行比较:

D* d    = new D();
void* v = static_cast<A*>(d);
// now this will work fine (v points to D's A part):
A* a    = static_cast<A*>(v);
D* dd   = static_cast<D*>(a); // even this one, original object was constructed as D

假设D* d = new D(); A* a = d; D* ds = static_cast<D*>(a); D* dr = reinterpret_cast<D*>(a); // actually undefined behaviour!!! std::cout << d << std::endl << a << std::endl << ds << std::endl << dr << std::endl; 的地址为0x10001000,并且d中的A的偏移量为8(D +可能会填充字节以进行对齐),您将看到一个输出像这样:

sizeof(B<D>

请注意,最后一行源自通过reinterpret_cast收到的10001000 10001008 10001000 10001008 指针!

最后说明:请注意,可以重新排列成员-在这些部分之间 内,同一可访问性类(公共/受保护/私有)中的成员只能保证先声明的成员之后才声明的成员,允许编译器重新安排。因此,总的来说,只有从D*返回的方式和到达那里的方式一样,您才可以安全:

void*

其他任何事情都是未定义的行为(请注意,一旦涉及到虚拟类,问题就会变得更糟,因为此外还有vtable指针...)。