Добро пожаловать в форум, Guest  >>   Войти | Регистрация | Поиск | Правила | В избранное | Подписаться
Все форумы / C++ Новый топик    Ответить
 Как вызвать деструктор не зная тип объекта?  [new]
Dima T
Member

Откуда:
Сообщений: 10360
Продолжение этой темы.
+ Упрощенный пример
struct A {
	int x = 0;

	A() {
		printf("constructor\n");
		x = 1;
	}

	~A() {
		printf("destructor\n");
	}

	void print() {
		printf("x = %d\n", x);
	}
};


template <typename T>
void* create() {
	printf("create()\n");
	void* msg = malloc(sizeof(size_t) + sizeof(T));
	void* ptr = (((size_t*)msg)+1);
	new (ptr) T(); // Конструктор
	return msg;
}

template <typename T>
T* get(void* msg) {
	printf("get()\n");
	return (T*)(((size_t*)msg) + 1);
}

void erase(void* msg) {
	void* ptr = (((size_t*)msg) + 1);
	// Тут надо как-то сделать delete ptr;
}

int main() {
	void* m = create<A>();
	A* d = get<A>(m);
	d->print();
	return 0;
}

Пытаюсь сделать сообщение полноценным объектом. Вызвать конструктор не проблема, проблема с деструктором.
Проблема в том что память под сообщением очищается библиотекой, а для нее содержимое это void*, как тут быть с деструктором?

Может как-то можно извернуться на шаблонах и адрес деструктора изначально сохранить при создании сообщения в create<T>(), а в erase() просто его вызвать?
Может еще какие-то способы есть?
17 мар 17, 16:44    [20306779]     Ответить | Цитировать Сообщить модератору
 Re: Как вызвать деструктор не зная тип объекта?  [new]
Siemargl
Member

Откуда: 010100
Сообщений: 4677
Dima T,

Или крестик снять или трусы надеть.

Либо ты делаешь полноценный объект с виртуальным деструктором либо типизированный обработчик на шаблонах (можно чтобы базовый <Т> умел удалять всё содержимое единым куском).

Есть еще подвариант - память под сообщение выделять одним цельным куском и удалится оно средствами аллокатора. Тогда никаких конструкторов.
17 мар 17, 20:13    [20307539]     Ответить | Цитировать Сообщить модератору
 Re: Как вызвать деструктор не зная тип объекта?  [new]
Barlone
Member

Откуда:
Сообщений: 812
Dima T
Может как-то можно извернуться на шаблонах и адрес деструктора изначально сохранить при создании сообщения в create<T>(), а в erase() просто его вызвать?
Можно конечно. Сделайте вместо шаблонных функций шаблонный класс со статическими методами, и в нем же функцию очистки, вызывающую деструктор.
17 мар 17, 20:48    [20307627]     Ответить | Цитировать Сообщить модератору
 Re: Как вызвать деструктор не зная тип объекта?  [new]
Dima T
Member

Откуда:
Сообщений: 10360
Siemargl
Dima T,

Или крестик снять или трусы надеть.

Я и то и другое готов сделать, только как?

Siemargl
Либо ты делаешь полноценный объект с виртуальным деструктором либо типизированный обработчик на шаблонах (можно чтобы базовый <Т> умел удалять всё содержимое единым куском).

Есть еще подвариант - память под сообщение выделять одним цельным куском и удалится оно средствами аллокатора. Тогда никаких конструкторов.

Я гоняю сообщения, точнее указатели на них, между обработчиками сообщений, причем типы мне неизвестны, я либа, но часть этой либы это удаление сообщений, т.е. освобождение памяти. Типизацию я сделал c помощью контроля typeid(T).hash_code(), который в сообщение пишется при создании.

Сделать полноценные типизированные сообщения вообще не вариант: один обработчик может получать несколько типов сообщений. Для либы без разницы какие типы, главное доставить их в порядке отправления, т.е. очередь из разных типов. Можно конечно классическое наследование, т.е. базовый класс my_msg_t и гонять указатель на него, но я тут теряю простоту написания кода, я не смогу сделать сообщение из std::string или std::vector, я буду вынужден объявить класс производный от my_msg_t где внутри будет std::string. Кроме лишних букав в коде это еще лишний тормоз в виде вызова виртуальных методов.
17 мар 17, 21:04    [20307679]     Ответить | Цитировать Сообщить модератору
 Re: Как вызвать деструктор не зная тип объекта?  [new]
Dima T
Member

Откуда:
Сообщений: 10360
В общем мне надо кривой костыль, который будет быстро и надежно работать внутри либы. А снаружи все будет чопорно и по фэншую.
17 мар 17, 21:34    [20307781]     Ответить | Цитировать Сообщить модератору
 Re: Как вызвать деструктор не зная тип объекта?  [new]
Siemargl
Member

Откуда: 010100
Сообщений: 4677
Dima T,

>еще лишний тормоз в виде вызова виртуальных методов.

Это одна ассемблерная инструкция. Это не тормоз
18 мар 17, 00:15    [20308133]     Ответить | Цитировать Сообщить модератору
 Re: Как вызвать деструктор не зная тип объекта?  [new]
SashaMercury
Member

Откуда: Москва
Сообщений: 2579
Дмитрий
Сделать полноценные типизированные сообщения вообще не вариант: один обработчик может получать несколько типов сообщений. Для либы без разницы какие типы, главное доставить их в порядке отправления, т.е. очередь из разных типов. Можно конечно классическое наследование, т.е. базовый класс my_msg_t и гонять указатель на него, но я тут теряю простоту написания кода, я не смогу сделать сообщение из std::string или std::vector, я буду вынужден объявить класс производный от my_msg_t где внутри будет std::string. Кроме лишних букав в коде это еще лишний тормоз в виде вызова виртуальных методов.


Мне кажется, что нужно создать базовый класс-обертку, который и будет содержать всю необходимую информацию. И виртуальные функции здесь пригодятся. Собственно то, что ты и написал выше. Только ты почему-то рассуждаешь в таком формате, словно это будет избыточным, но по-моему это так не выглядит))
18 мар 17, 00:55    [20308168]     Ответить | Цитировать Сообщить модератору
 Re: Как вызвать деструктор не зная тип объекта?  [new]
SashaMercury
Member

Откуда: Москва
Сообщений: 2579
Barlone
Dima T
Может как-то можно извернуться на шаблонах и адрес деструктора изначально сохранить при создании сообщения в create<T>(), а в erase() просто его вызвать?
Можно конечно. Сделайте вместо шаблонных функций шаблонный класс со статическими методами, и в нем же функцию очистки, вызывающую деструктор.



Как же это правильно называется, это уже давно планируют добавить в стандарт..
18 мар 17, 00:56    [20308169]     Ответить | Цитировать Сообщить модератору
 Re: Как вызвать деструктор не зная тип объекта?  [new]
MasterZiv
Member

Откуда: Питер
Сообщений: 32501
Dima T

Может еще какие-то способы есть?


в таких случаях самое правильное -вызвать деструктор руками.
18 мар 17, 01:34    [20308192]     Ответить | Цитировать Сообщить модератору
 Re: Как вызвать деструктор не зная тип объекта?  [new]
Dima T
Member

Откуда:
Сообщений: 10360
Сделал костыль. Так работает.
typedef void(*destructor_t)(void* obj);

// Сообщение
struct msg_t {
	destructor_t free;
	char data[];
};

// Обертка вызова деструктора
template <typename T>
void free_obj(void* obj) {
	T* ptr = (T*)obj;
	ptr->~T();
}

// Создание сообщения
template <typename T>
msg_t* create() {
	msg_t* msg = (msg_t*)malloc(sizeof(msg_t) + sizeof(T));
	msg->free = free_obj<T>; // Деструктор
	void* ptr = msg->data;
	new (ptr) T(); // Вызов конструктора содержимого
	return msg;
}

// Освобождение памяти
void erase(msg_t* msg) {
	msg->free(msg->data); // Вызов деструктора содержимого
	free(msg);
}
18 мар 17, 08:19    [20308304]     Ответить | Цитировать Сообщить модератору
 Re: Как вызвать деструктор не зная тип объекта?  [new]
Dima T
Member

Откуда:
Сообщений: 10360
SashaMercury
Мне кажется, что нужно создать базовый класс-обертку, который и будет содержать всю необходимую информацию. И виртуальные функции здесь пригодятся. Собственно то, что ты и написал выше. Только ты почему-то рассуждаешь в таком формате, словно это будет избыточным, но по-моему это так не выглядит))

Я к С привык, в С++ слабо разбираюсь, но кое-что оттуда надо, например STD. Наверно поэтому :)

Прикинул, букав действительно не намного больше. Можно затестить с оберткой, тем более там виртуальных функций особо не надо вызывать, их там нет, msg_t это структура.

Тогда возникает следующий вопрос: если я перегружу new для базового класса (msg_t), то будет ли он вызываться при создании объекта дочернего класса? Я хочу простенький менеджер памяти добавить, т.е. не освобождать память сразу, а откладывать указатель в кэш и при следующем создании сообщения такого же размера просто выдавать из кэша.
18 мар 17, 09:03    [20308332]     Ответить | Цитировать Сообщить модератору
 Re: Как вызвать деструктор не зная тип объекта?  [new]
Anatoly Moskovsky
Member

Откуда: Odessa
Сообщений: 5876
Dima T
Можно затестить с оберткой, тем более там виртуальных функций особо не надо вызывать, их там нет, msg_t это структура.

msg->free - это и есть виртуальная функция ))

// Обертка вызова деструктора
template <typename T>
void free_obj(void* obj) {
	T* ptr = (T*)obj;
	ptr->~T();
}

Так и надо делать. Это называется type erasure.
То же самое делают всякие std::function, std::shared_ptr

Dima T
Тогда возникает следующий вопрос: если я перегружу new для базового класса (msg_t), то будет ли он вызываться при создании объекта дочернего класса? Я хочу простенький менеджер памяти добавить, т.е. не освобождать память сразу, а откладывать указатель в кэш и при следующем создании сообщения такого же размера просто выдавать из кэша.

Не помнимаю, зачем спрашивать на форуме то что можно прочесть в доке или самому проверить несколькими строками кода?
Это настолько редкая в использовании фича, что вряд ли кто-то помнит подробности.
18 мар 17, 14:06    [20308722]     Ответить | Цитировать Сообщить модератору
 Re: Как вызвать деструктор не зная тип объекта?  [new]
Dima T
Member

Откуда:
Сообщений: 10360
Anatoly Moskovsky
Так и надо делать. Это называется type erasure.

Ок. Так и сделаю.

Anatoly Moskovsky
Не помнимаю, зачем спрашивать на форуме то что можно прочесть в доке или самому проверить несколькими строками кода?
Это настолько редкая в использовании фича, что вряд ли кто-то помнит подробности.

Мысли вслух. Вдруг кто помнит или какие-то подводные камни есть. Уже не важно, остаюсь на своем варианте.
18 мар 17, 16:06    [20308963]     Ответить | Цитировать Сообщить модератору
 Re: Как вызвать деструктор не зная тип объекта?  [new]
CEMb
Member

Откуда: public T{};
Сообщений: 1502
Dima T
void* msg = malloc(sizeof(size_t) + sizeof(T));
если уж хочется такого хардкора, то почему бы в msg не завести поле, которое будет хранить тип T? И потом в зависимости от него вызывать правильный деструктор (по сути, те же виртуальные функции)
Во-вторых, а нужен ли деструктор для T? Или достаточно освободить память из-под объекта?
В-третьих, присоединяюсь к идее про шаблонный класс.
В-четвёртых, можно использовать те же "механизмы", которые используются в windows messaging
А можно ещё такой финт сделать: при создании делать make_shared<T> а указатель хранить в поле msg, после удаления которой автоматически будет вызван деструтор T. Но тут надо подумать, потому как похоже на выстрел в ногу.
20 мар 17, 05:42    [20312151]     Ответить | Цитировать Сообщить модератору
 Re: Как вызвать деструктор не зная тип объекта?  [new]
Dima T
Member

Откуда:
Сообщений: 10360
CEMb
Dima T
void* msg = malloc(sizeof(size_t) + sizeof(T));
если уж хочется такого хардкора, то почему бы в msg не завести поле, которое будет хранить тип T? И потом в зависимости от него вызывать правильный деструктор (по сути, те же виртуальные функции)
Во-вторых, а нужен ли деструктор для T? Или достаточно освободить память из-под объекта?
В-третьих, присоединяюсь к идее про шаблонный класс.
В-четвёртых, можно использовать те же "механизмы", которые используются в windows messaging
А можно ещё такой финт сделать: при создании делать make_shared<T> а указатель хранить в поле msg, после удаления которой автоматически будет вызван деструтор T. Но тут надо подумать, потому как похоже на выстрел в ногу.

В реальном коде такого хардкора нет, есть msg_t переменной длины, упрощенно тут 20308304
msg_t нельзя делать шаблонным классом, т.к. например msg_t<int> и msg_t<std::string> надо ставить в одну очередь
Без деструкторов уже все работает и с освобождением памяти под сообщением проблемы порешаны.
Деструкторы нужны чтобы освобождать память которую T дополнительно в куче выделит, в первую очередь это касается объектов STD.
20 мар 17, 07:13    [20312174]     Ответить | Цитировать Сообщить модератору
 Re: Как вызвать деструктор не зная тип объекта?  [new]
CEMb
Member

Откуда: public T{};
Сообщений: 1502
Dima T
msg_t нельзя делать шаблонным классом, т.к. например msg_t<int> и msg_t<std::string> надо ставить в одну очередь
шаблон можно специализировать в зависимости от того, класс ли T: std::is_class<T>, а в одну очередь можно поставить или через наследование от интерфейса или примерно так:20172805 и 20256178, там как раз идея скорости и отсутствия виртуальных функций в классах.
20 мар 17, 10:45    [20312818]     Ответить | Цитировать Сообщить модератору
 Re: Как вызвать деструктор не зная тип объекта?  [new]
YesSql
Guest
Dima T
пропущено...
msg_t нельзя делать шаблонным классом, т.к. например msg_t<int> и msg_t<std::string> надо ставить в одну очередь
Без деструкторов уже все работает и с освобождением памяти под сообщением проблемы порешаны.
Деструкторы нужны чтобы освобождать память которую T дополнительно в куче выделит, в первую очередь это касается объектов STD.

как то так
struct msg_base_t
{
       size_t     size;
       size_t     hash_type;
      //    .....etc.....

      msg_base_t(size_t _size, size_t _hash_type)
      : size      {_size}
      , hash_type {_hash_type}
      {}

      virtual ~msg_base_t();
};

template <typename M>
struct msg_t : public msg_base_t
{
       M  payload;

       msg_t(const M &m)
       : msg_base_t(sizeof(M), typeid(M).hash_code())
       , payload(m)
       {}

};

template <typename M>
void push(const M &m)
{ 
       msg_base_t *ptr = new  msg_t<M>(m);
}

Можно ещё попробовать вариадик и/или мув симантику. Может сэкономить на конструкторах.
.
20 мар 17, 10:48    [20312833]     Ответить | Цитировать Сообщить модератору
 Re: Как вызвать деструктор не зная тип объекта?  [new]
Dima T
Member

Откуда:
Сообщений: 10360
YesSql, долго пытался понять что я выигрываю по сравнению со своим вариантом 20308304

Все что вижу - это строчка на создании сообщения
my_msg_t msg;
... заполняем msg
push(msg);

и у меня
msg_t* msg = create<my_msg_t>();
my_msg_t* obj = get<my_msg_t>();
... заполняем obj
push(msg);

Твой вариант получше, но это все плюсы. Дальше все точно также: при получении будет msg_t* и приведение его к исходному типу. Дальше отправка того же msg_t*.

В твоем варианте мне надо курить перемещения (которые я не знаю), чтобы избавиться от копирования. Потом изучать перегрузку new/delete. Это минимум неделя изучения С++, а то и больше, я дотошный. Жалко мне неделю на это тратить.
20 мар 17, 18:18    [20315134]     Ответить | Цитировать Сообщить модератору
 Re: Как вызвать деструктор не зная тип объекта?  [new]
YesSql
Guest
Dima T
YesSql, долго пытался понять что я выигрываю по сравнению со своим вариантом 20308304


Хочешь вызвать корректный деструктор неизвестного сообщения?
struct MyMessage
{
     std::string param1;
     int param2;
   
     MyMessage(const std::string _param1, int _param2)
      : param1    {_param1}
      , param2    {_param2}
     {}
};
   
         void * current_queue_ptr = malloc(sizeof(msg_t<MyMessage>));

         new(current_queue_ptr)  msg_t<MyMessage>(MyMessage("text param 1", 42));

//.................

         static_cast<msg_base_t *>(current_queue_ptr)->~msg_base_t();
20 мар 17, 19:33    [20315379]     Ответить | Цитировать Сообщить модератору
 Re: Как вызвать деструктор не зная тип объекта?  [new]
Dima T
Member

Откуда:
Сообщений: 10360
YesSql
Хочешь вызвать корректный деструктор неизвестного сообщения?

Я его и вызову 20308304, msg->free это указатель на функцию, которая вызовет деструктор именно для того типа, который передан в конструктор. Как выше написали это стандартный велосипед вызова деструктора.
20 мар 17, 19:39    [20315392]     Ответить | Цитировать Сообщить модератору
 Re: Как вызвать деструктор не зная тип объекта?  [new]
д0kХ
Guest
YesSql
         static_cast<msg_base_t *>(current_queue_ptr)->~msg_base_t();


ИМХО нельзя так делать ->~
Это пистолет в кармане со снятым предохранителейм ,
прострел ноги вопрос времени , он рано или поздно произойдет
в самый неожиданный момент.
20 мар 17, 19:55    [20315439]     Ответить | Цитировать Сообщить модератору
 Re: Как вызвать деструктор не зная тип объекта?  [new]
YesSql
Guest
д0kХ
YesSql
         static_cast<msg_base_t *>(current_queue_ptr)->~msg_base_t();


ИМХО нельзя так делать ->~
Это пистолет в кармане со снятым предохранителейм ,
прострел ноги вопрос времени , он рано или поздно произойдет
в самый неожиданный момент.

Это C++ карл, сдесь каждый угол заминирован.
20 мар 17, 21:19    [20315633]     Ответить | Цитировать Сообщить модератору
 Re: Как вызвать деструктор не зная тип объекта?  [new]
YesSql
Guest
Dima T
YesSql
Хочешь вызвать корректный деструктор неизвестного сообщения?

Я его и вызову 20308304, msg->free это указатель на функцию, которая вызовет деструктор именно для того типа, который передан в конструктор. Как выше написали это стандартный велосипед вызова деструктора.

то же самое только без костыля.
20 мар 17, 21:27    [20315649]     Ответить | Цитировать Сообщить модератору
 Re: Как вызвать деструктор не зная тип объекта?  [new]
ermak.nn
Member

Откуда: Нижний Новгород
Сообщений: 37
Люблю я и C, и кресты, но как-то вместе они выглядят жутковато :)

class Message
{
public:
    Message();
    virtual ~Message();
    virtual void process() = 0;
};

template <class T>
class MessageTemplate : public Message
{
private:
    T* m_obj;
    
public:
    MessageTemplate();
    ~MessageTemplate();
    void process() override
    {
        // process message
    }
};


Я бы, наверно, сделал как-то так, но вы говорите, что элегантность теряется. Может быть, спорить не буду. Кишки библиотеки просто выглядят, хм... странно для человека, который пишет C++.
21 мар 17, 09:40    [20316254]     Ответить | Цитировать Сообщить модератору
 Re: Как вызвать деструктор не зная тип объекта?  [new]
Dima T
Member

Откуда:
Сообщений: 10360
ermak.nn
Кишки библиотеки просто выглядят, хм... странно для человека, который пишет C++.

Сначала я хотел ограничится только С с классами, т.е. было
class lite_msg_t {
	size_t _type;		// Тип сообщения
	size_t _size;		// Размер data, байт
public:
	char data[];		// Данные

	static lite_msg_t* create(size_t size, size_t type = 0) noexcept {
		lite_msg_t* msg = (lite_msg_t*)malloc(size + sizeof(lite_msg_t));
...

Красиво и по Сишному. Оно так и осталось.
Все что касается типов вложений хотел чтобы осталось поверх этого. Но как-то не очень код выглядит с постоянными <my_msg_t>
Пока нет времени этим заниматься, подумаю, может вообще выкину все шаблоны и оставлю только явное наследование. Тесты показали что на скорость это никак не влияет.
21 мар 17, 10:28    [20316419]     Ответить | Цитировать Сообщить модератору
Все форумы / C++ Ответить