Как удалить указатель после приведения к void* и обратно

В рамках класса «сообщение» я пытаюсь передавать указатели разных типов, приводя их к указателям типа void* и сохраняя их в классе-оболочке («MsgData»), который запоминает исходный тип указателя.

Например, логический указатель:

bool* data = new bool;
event.wheel.y < 0 ? *data = false : *data = true;
send("all", this, MSG_MOUSE_SCROLL, MsgData(data));

Вызывается совместимый конструктор MsgData, и переменная сохраняется как член моего класса сообщений:

MsgData():                        type_(NULLPTR),    data_(nullptr)     {}  // Null
MsgData(const bool* data):        type_(BOOL),       data_((void*)data) {}  // Bool
MsgData(const std::string* data): type_(STRING_STD), data_((void*)data) {}  // std::string
// ... etc.

Я могу вернуть указатели обратно и использовать их без каких-либо ошибок, но когда я пытаюсь их удалить, программа вылетает:

~MsgData() {
    switch (type_) {
    case (BOOL):
        if ((bool*)data_)
            delete (bool*)data_;
        break;
    // ... etc.
    }
}

Указатель bool — это просто пример, и то же самое происходит со всеми другими типами и классами. Программа вылетает только при попытке удалить указатель. Вернуть им исходный тип и использовать их не проблема.

Я исследовал проблему и нашел аналогичный вопрос, например этот в StackOverflow, но пока он Кажется, считается плохим стилем указывать указатель на void* и обратно. Я не могу найти причину сбоя программы.


person Mathis    schedule 11.02.2018    source источник
comment
Я хотел бы отметить одну вещь: вы должны использовать static_cast в коде C++, чтобы сделать ваши намерения более очевидными.   -  person Arnav Borborah    schedule 11.02.2018
comment
Почему вы не используете boost::variant?   -  person Nawaz    schedule 11.02.2018
comment
Ваш подход должен работать. Мы не можем помочь без минимально воспроизводимого примера.   -  person HolyBlackCat    schedule 11.02.2018
comment
Чтобы отладить ваш код, нам нужно его увидеть.   -  person melpomene    schedule 11.02.2018
comment
@Nawaz: На самом деле никогда не использовал boost, и, если это возможно, я хотел бы решить проблему без дополнительных библиотек, чтобы программа была максимально простой. Но обязательно посмотрю.   -  person Mathis    schedule 11.02.2018
comment
Вы даже можете использовать std::any, чтобы разрешить почти любые данные без необходимости делать эту перегрузку и вводить код списка. Вы получаете производительность, гибкость и типобезопасность.   -  person nwp    schedule 11.02.2018
comment
Я бы сказал, что использование типа variant делает программу проще, чем создание вашей собственной. Если вам не нужен Boost и у вас нет доступа к стандартной, есть еще mpark::variant, которая действует точно так же, как стандартная, но может использоваться без C++17 и является отдельной библиотекой.   -  person chris    schedule 11.02.2018
comment
Если вы действительно хотите отладить это (что не нужно, потому что вместо этого вы должны просто заменить его чем-то разумным), распечатайте тип и адрес всего, что вы new и delete, а затем проверьте журнал, где они не совпадают. В качестве альтернативы, если вы используете Linux, пропустите -fsanitize=undefined,address, чтобы этот тип ошибок был проанализирован во время выполнения.   -  person nwp    schedule 11.02.2018
comment
@Mathis Пожалуйста, прочитайте минимально воспроизводимый пример.   -  person melpomene    schedule 11.02.2018
comment
Ваш код может дать сбой из-за множества причин, совершенно не связанных с тем, в чем, по вашему мнению, возникла проблема. Тот факт, что программа надежно аварийно завершает работу в delete, можно объяснить и другими причинами, например повреждением памяти.   -  person user4815162342    schedule 11.02.2018


Ответы (1)


Лучшее решение проблемы — использовать boost::variant (или std::variant). Как только вы начнете его использовать, вся головная боль по удалению и управлению типом и данными исчезнет автоматически. Вы не первый, кто сталкивается с проблемой такого рода; многие другие столкнулись с этим, и решение доступно в виде boost::variant или std::variant.

В любом случае, поскольку вы разрабатываете решение самостоятельно, вот мой совет: создайте соответствующий deleter в самом конструкторе... или всякий раз, когда вы знаете, какой тип у данные, которые будет хранить ваш класс:

MsgData()
 : type_(NULLPTR), data_(nullptr)     {} 

MsgData(const bool* data)
 : type_(BOOL), data_((void*)data), deleter_(&deleter<BOOL>) {}      

MsgData(const std::string* data)
 : type_(STRING_STD), data_((void*)data), deleter_(&deleter<std::string>) {}  

где deleter_ является членом:

std::function<void(void const*)>  deleter_;

а deleter определяется как шаблон функции:

template<typename T>
void deleter(void const * data) {
   delete static_cast<T const *>(data);
}

Как только они у вас появятся, ваш деструктор будет выглядеть так:

~MsgData() {
   if (deleter_) {
      deleter_(data_);
   }
}

Надеюсь, это поможет.

person Nawaz    schedule 11.02.2018