Создал класс:
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-ом и удалять его, но уже появились другие ошибки.
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.
Первое, что бросается, это отсутствие V::operator=, и, скорее всего, компилятор создал свой, примерно такой
V& operator=(const V &y)
{
this->p = y.p; //Из-за этого ошибка в деструкторе
}
и
V::V(const V &y)
{
this->operator=(y);
}
Виртуальный выделенный сервер (VDS) становится отличным выбором
g++/Code::Blocks/Ubuntu Gnome При попытке компиляции из codeblocks указанного ниже кода вылетает ошибкаНо любопытен тот факт, что выполнение тех же команд...
Почему перегрузка операторов через методы класса приоритетней чем через функции?