мульти наследование структуры от структуры и шаблонной структуры, упорядочение имеет значение при доступе к базовым не шаблонным структурным данным - PullRequest
0 голосов
/ 06 декабря 2018
#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;
}

output: (скомпилировано с g ++)

(null) - введите

type - 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 и отправить это.вот почему существует пересчет, поэтому я должен отправить производный объект, чтобы позже в обратном вызове я мог уменьшить счет.

Есть ли способ предотвратить это?

Ответы [ 2 ]

0 голосов
/ 07 декабря 2018

Хм, я думаю, что вижу, в чем проблема:

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 s 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;

Предполагая, что адрес d равен 0x10001000 и Aв пределах D смещение равно 8 (sizeof(B<D> + возможно заполнение байтов для выравнивания), вы увидите такой вывод:

10001000
10001008
10001000
10001008

Обратите внимание, что последняя строка начинается с D*указатель получен через reinterpret_cast!

Последнее замечание: Помните, что элементы могут быть переставлены - элементы, объявленные первыми предыдущими элементами, объявленными только после, гарантированы для членов в пределах одного и того же класса доступности (публичный / защищенный / частный), между этими разделами компилятору разрешено переупорядочивать.Таким образом, в целом вы можете быть в безопасности, только если вернетесь с void* так же, как вы привыкли к нему:

void* v = d; // -> need to go back via static_cast<D*>!

A* a    = static_cast<A*>(v);    // requires v = static_cast<A*>(d);
B<D>* d = static_cast<B<D>*>(v); // requires v = static_cast<B<D>*>(d);

Все остальное - неопределенное поведение (имейте в виду, что ситуация становится еще хуже, как толькопоскольку задействованы виртуальные классы, то, кроме того, существуют указатели vtable ...).

0 голосов
/ 07 декабря 2018

Если вы приведете указатель к 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;
};

Хотя приведение к другим типам может когда-нибудь сработать, его следует избегать, поскольку это делает код более хрупким, если код подвергается рефакторингу вкакой-то момент, как переупорядочение базовых классов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...