Размещающий new и явный вызов деструктора

343
14 июня 2017, 02:48

Создал класс:

template <typename T> class Vector {
   static const size_t def_capacity = 10;
public:
   Vector():capacity{ 0 }, size{ 0 }, mas{ nullptr } {}     
   Vector(size_t siz) :capacity{ siz }, size{ siz } {
        T* tmp = (T*)malloc(sizeof(T) * siz);
        mas = new(tmp) T[siz];
   }
    ~Vector() {
        for (size_t i = 0; i < size; ++i)
            mas[i].~T();
         free(mas);
    }

    T operator[](int i) const {
        return mas[i];
    }
    T& operator[](int i) {
        return mas[i];
    }
}
private:
    size_t capacity;
    size_t size;
    T* mas;
};
class V {
public:
    V():p{new char[10]}{}
    ~V() {
    delete[] p;
}
private:
    char * p;
};

Хотел проверить таким кодом:

#include "new.h"
void foo() {
    Vector<V> vec(10);
}
int main()
{
    foo();
}

Но получаю это:

Expression: _CrtIsValidHeapPointer(block)

Через отладчик просмотрел, что все деструкторы вызываются правильно, но ошибка появляется перед free(mas) в деструкторе Vector, если закоментить, ошибки не будет. Возможно что-то напартачил с явным вызовом деструктора, ибо на int ошибок не было. Использую Visual Studio.

UPD: Ещё раз пересмотрел все через отладчик и выяснил что на строчке

 T* tmp = (T*)malloc(sizeof(T) * siz);

Возвращается указатель, который на 4 байта (предположительно размер V) меньше, чем позже возвращает new

  mas = new(tmp) T[siz];

который я и записываю в поле mas. Позже, пытаясь очистить память free(mas) выскакивает ошибка, ибо я адрес то уже не тот, который вернул malloc.

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

Answer 1

Placement-new, примененный в синтаксисе массива, в общем случае возвращает не то же самое значение указателя, которое было передано в него, ибо он может использовать часть памяти для хранения служебной информации (например, размер массива). Это означает, что, во-первых, блок памяти, выделенный как malloc(sizeof(T) * siz) в общем случае не достаточен для хранения массива T[siz] при условии инициализации через placement-new[]. И, во-вторых, передавать полученный из placement-new[] указатель обратно во free нельзя - он в общем случае не совпадает по значению с тем указателем, который был получен из malloc.

Именно по этим причинам ваш код будет падать в случаях, когда тип T имеет нетривиальный деструктор (как у вас в примере).

Если я не упускаю каких-то нововведений, не существует корректного/портабельного способа использования "массивной" версии placement-new, ибо нет портабельного способа узнать заранее, сколько памяти ему понадобится. И даже если вы каким-то образом выкрутитесь с объемом памяти, все равно вам придется либо запоминать, либо восстанавливать правильное значение указателя, полученное из malloc - для передачи во free.

Наилучший выход в данном случае - прекратите пользоваться "массивным" placement-new вообще, а лучше сделайте обычный placement-new отдельно для каждого элемента массива. Вы уже фактически сделали аналогичный шаг на этапе деструкции, т.е. вызываете деструктор для каждого элемента индивидуально. Вот так же сделайте и конструкцию - через placement-new для каждого элемента индивидуально.

А в C++17 лучше воспользуйтесь готовыми стандартными функциями. Для корректной конструкции/деструкции массивов при ручном выделении сырой памяти уже есть готовые функции типа std::uninitialized_default_construct, std::destroy_n и т.п. (см. <memory>). Не надо приплетать сюда явный массивный placement-new.

Answer 2

Первое, что бросается, это отсутствие V::operator=, и, скорее всего, компилятор создал свой, примерно такой

V& operator=(const V &y)
{
    this->p = y.p; //Из-за этого ошибка в деструкторе
}

и

V::V(const V &y)
{
    this->operator=(y);
}
READ ALSO
Ошибка компиляции в codeblocks

Ошибка компиляции в codeblocks

g++/Code::Blocks/Ubuntu Gnome При попытке компиляции из codeblocks указанного ниже кода вылетает ошибкаНо любопытен тот факт, что выполнение тех же команд...

322
Приоритет перегрузки операторов

Приоритет перегрузки операторов

Почему перегрузка операторов через методы класса приоритетней чем через функции?

200
Виртуальная статическая функция

Виртуальная статическая функция

Почему статическая функция не может быть виртуальной?

352