LINUX.ORG.RU

Будет ли корректно не дестроить объект в случае перемещений в массиве?

 ,


0

2

Будет ли корректным следующий вариант удаления элемента из массива:

template <class T>
class MyList {
   T *buf = nullptr;
   uint cnt = 0;
   uint capa = 0;

   ...
   
   void delByIndex(uint idx) {
       assert( idx < cnt );
       // вот такой:
       buf[idx].~T();
       for (uint ii = idx+1; ii < cnt; ++ii)
          new(&buf[ii-1]) T(std::move(buf[ii]));
       // *******
       --cnt;
   }
};
Тип параметра шаблона может быть любым.

Ответ на: комментарий от Siborgium

Если нужно на последний, то наверное нужно и на каждый элемент массива?

Я в размышлениях, если элемент списка содержит указатели на выделяемую память при создании и при удалении, то можно ли сделать такое удаление из массива, что бы без лишних таких операций. Один элемент удаляется, остальные перемещаются без перевыделения.

С другой стороны, если делать swap, то это значит через весь массив тянуть это swap.

victor79
() автор топика
Ответ на: комментарий от victor79

то наверное нужно и на каждый элемент массива?

Да, нужно.

Один элемент удаляется, остальные перемещаются без перевыделения.

С move там и так не будет перевыделения.

Деструкторы можно не вызывать вообще, если T – trivially destructible.

Siborgium ★★★★★
()
Последнее исправление: Siborgium (всего исправлений: 2)
Ответ на: комментарий от Siborgium

Тогда получается такой вариант, будет вызван много раз деструктор, и если он не тривиален, и тяжел, то будет тяжело (тривиальный сам заоптимизируется). И swap через весь массив потянется.

   void delByIndex(uint idx) {
       assert( idx < cnt );

       buf[idx].~T();

       for (uint ii = idx+1; ii < cnt; ++ii) {
          new(&buf[ii-1]) T(std::move(buf[ii]));
          buf[ii].~T();
       }

       --cnt;
   }

victor79
() автор топика
Последнее исправление: victor79 (всего исправлений: 1)
Ответ на: комментарий от victor79

std::move(buf[ii])

После этого же buf[ii] уже должен быть в неактивном состоянии в соответствии с move-семантикой и явный вызов деструктора не требуется?

thunar ★★★★★
()

Будет ли корректно не дестроить объект в случае перемещений в массиве?

Смотря что ты имеешь в виду под корректностью. Прибить объект создав поверх него другой, не вызывая перед этим деструктора старого объекта, вполне себе корректно с т.з. стандарта.

utf8nowhere ★★★
()
Ответ на: комментарий от Siborgium

Деструкторы можно не вызывать вообще, если T – trivially destructible.

Можно не вызывать для любого T.

utf8nowhere ★★★
()

Как utf8nowhere уже указал, объекты неявно убиваются, если на их месте создаются новые. А так как после перемещения утечки быть не должно, то по идее так делать можно.

http://eel.is/c++draft/basic.life#1.5

The lifetime of an object o of type T ends when:
 * [...]
 * the storage which the object occupies is released, or is reused by an object that is not nested within o ([intro.object]).

На cppreference тоже есть.

xaizek ★★★★★
()

Это же делается только для того, чтобы поиграться? Это же не для продакшена?

pathfinder ★★★★
()
Ответ на: комментарий от Siborgium

Ну если делалось что-нибудь вроде buf = (T*)operator new(capa * sizeof(T)), то не должно. Но и тогда вызов деструктора может быть не нужен.

utf8nowhere ★★★
()

А откуда указатель buf берется? Массив ведь расти будет не new T[capa]; Может и так про std::launder, а может нет:

  // Case 3: access to a new object whose storage is provided by a byte array through
  // a pointer to the array.
  alignas(Y) std::byte s[sizeof(Y)];
  Y* q = new(&s) Y{2};
  const int f = reinterpret_cast<Y*>(&s)->z; // Class member access is undefined behavior:
                                             // reinterpret_cast<Y*>(&s) has value
                                             // "pointer to s" and does not
                                             // point to a Y object 
  const int g = q->z; // OK
  const int h = std::launder(reinterpret_cast<Y*>(&s))->z; // OK
pavlick ★★
()
Последнее исправление: pavlick (всего исправлений: 1)
Ответ на: комментарий от utf8nowhere

А я и сейчас не очень понимаю как информация о динамическом типе в случае с char может что-то там сломать. Могу себе представить всякие оптимизации для сложных типов и launder меняющий динамический тип не допускающий оптимизации ломающие код.

В случае с char буфером - делаю без достаточного понимания причин. И это не моя проблема - а проблема цпп, непонятные правила без жизненных примеров - куча говнокода на выходе.

pavlick ★★
()

Всем спасибо, понял. Нужно все через конструкторы и деструкторы, и не вые(жи)ваться. Даже при перемещениях. А в некоторых случаях еще и std::launder приписывать.

Переделал часть методов на перемещения со всеми констру/дестру. Для тривиальных типов скорость чуть-чуть улучшилась, но там и некуда было. Для сложных типов, понятно, возрасла существено, но в этом списке сложные до этого и не использовались. Потому и возник вопрос.

victor79
() автор топика
Последнее исправление: victor79 (всего исправлений: 2)

Я думаю не стоит изобретать велосипед и нужно вместо самих объектов держать в контейнере unique_ptr/shared_ptr на эти объекты. Перемещение они поддерживают.

dvetutnev
()
Ответ на: комментарий от bga_

фрагментация и тормоза

Ты это уже измерил? Простое решение всегда работает лучше.

dvetutnev
()
Последнее исправление: dvetutnev (всего исправлений: 1)
Ответ на: комментарий от dvetutnev

Я думаю не стоит изобретать велосипед и нужно вместо самих объектов держать в контейнере unique_ptr/shared_ptr на эти объекты. Перемещение они поддерживают.

Не, это ты телепатируешь плохую архитектуру. Но здесь тема про реализацию утилиты с конкретно такими свойствами, а не про ее применение. Если такое будет нужно, можно сделать в обертке над первым.

victor79
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.