#c
#c
Вопрос:
Я некоторое время изучал C и начал писать более крупный проект, просто чтобы понять, что все, что я думал о C , неверно.
В принципе, у меня есть вызываемый класс DenseVector
, который содержит удвоения. Я хочу переместить, скопировать конструкцию и т. Д. Этот вектор, Чтобы использовать его с векторами и т. Д.
Класс выглядит примерно так:
DenseVector.h:
class DenseVector {
private:
double* values = nullptr;
int size;
public:
DenseVector();
DenseVector(int size);
DenseVector(double x, double y);
DenseVector(double x, double y, double z);
DenseVector(const DenseVector amp;other);
DenseVector(DenseVector amp;amp;other);
virtual ~DenseVector();
DenseVectoramp; operator=(const DenseVectoramp; other);
DenseVectoramp; operator=(DenseVectoramp;amp; other);
}
DenseVector.cpp
DenseVector::DenseVector() : DenseVector(3) {}
DenseVector::DenseVector(int size) : size(size) {
this->values = new double[size size % 2]{0};
}
DenseVector::DenseVector(double x, double y) {
this->size = 2;
this->values = new double[2]{x,y};
}
DenseVector::DenseVector(double x, double y, double z) {
this->size = 3;
this->values = new double[4]{x,y,z};
}
DenseVector::DenseVector(const DenseVector amp;other) : size(other.size) {
this->values = new double[size size % 2]{0};
memcpy(values, other.values, size * sizeof(double));
}
DenseVector::DenseVector(DenseVector amp;amp;other) : values(other.values), size(other.size){}
DenseVector::~DenseVector() {
if(values != nullptr){
delete values;
values = nullptr;
}
}
DenseVector amp;DenseVector::operator=(const DenseVector amp;other) {
this->size = other.size;
memcpy(values, other.values, size * sizeof(double));
return *this;
}
DenseVector amp;DenseVector::operator=(DenseVector amp;amp;other) {
this->values = other.values;
this->size = other.size;
return *this;
}
Я предполагаю, что это очень простая реализация для математических векторов в C . Обратите внимание, что внутренний размер массива всегда кратен двум. Это связано с ускорением использования AVX / SSE, которое не является частью этого вопроса.
По сути, я продолжаю получать ошибку сегментации внутри деконструктора при попытке удалить значение, и я понятия не имею, почему это продолжает происходить!
И примером может быть следующий фрагмент:
std::vector<DenseVector> positions;
for(int i = 0; i < 10; i ){
positions.push_back({1,2,3});
}
Это действительно меня смущает, и я был бы очень рад, если бы кто-нибудь мог помочь мне с этой проблемой, поскольку это случалось со мной много раз в других программах.
Также в чем будет разница между использованием push_back
и emplace_back
в этом случае? Следует ли предпочесть одно другому? Я не понимаю, в какой момент объекты будут созданы, перемещены, удалены и т.д.
Приветствую, Финн
Комментарии:
1. Ваш конструктор перемещения выполняет (поверхностное) копирование, а не перемещение.
2. В чем разница?
3. @FinnEggers, при перемещении копии указатели в объекте rvalue устанавливаются в
nullptr
Ответ №1:
Я вижу как минимум две проблемы:
DenseVector amp;DenseVector::operator=(const DenseVector amp;other) {
this->size = other.size;
memcpy(values, other.values, size * sizeof(double));
return *this;
}
Вы не проверяете, достаточно ли у вас места this->values
. Если this->size
меньше other->size
, вам нужно перераспределить.
DenseVector::DenseVector(DenseVector amp;amp;other) : values(other.values),
size(other.size){}
DenseVector amp;DenseVector::operator=(DenseVector amp;amp;other) {
this->values = other.values;
this->size = other.size;
return *this;
}
В обоих случаях вы получаете два указателя, указывающих на одну и ту же память. Теперь, когда вы уничтожаете оба вектора, вы получаете double delete
. Вам нужно иметь other->values = nullptr;
в обеих функциях.
Лучший способ исправить обе проблемы — использовать std::vector
и полагаться на правило нуля.
Комментарии:
1. Это звучит разумно, но я подумал, что при перемещении объекта старый объект не будет удален?
2. @FinnEggers старый объект не будет удален? — В какой-то момент его необходимо удалить. Именно тогда вы столкнетесь с проблемой двойного удаления.
3. Ммм, тогда какой смысл вообще двигаться? Я думал, что можно «просто» передать все данные объекта on другому
4. Но ваш ответ на это. Спасибо за ваш ответ 🙂 Теперь, учитывая, что удаление также будет вызываться для перемещенного объекта, это имеет большой смысл
5. @Finn сам объект никуда не перемещается. Ресурс кучи перемещается — принимается новым объектом. Тогда вам определенно нужен указатель на
nullptr
ресурс старого объекта, потому что его деструктор не должен удалять ресурс во время неизбежного выхода из области видимости (или отклонения в качестве временного объекта). Вы больше не заботитесь об этом старом объекте, но его указатель ДОЛЖЕН БЫТЬ признан недействительным при перемещении ctor и назначении перемещения.
Ответ №2:
У вас есть несколько проблем.
Тот, который находится в деструкторе, delete
должен совпадать с new
not new[]
.
Вы должны использовать delete[]
здесь:
DenseVector::~DenseVector() {
delete [] values;
}
Примечание: удаление нулевого указателя в порядке, проверка не требуется. установка значений в nullptr
значение также бесполезна, так как чтение значения после уничтожения объекта в любом случае является UB.
Ваш конструктор перемещения не перемещается, а выполняет мелкую копию, поэтому у вас будет двойное удаление, оно должно быть
DenseVector::DenseVector(DenseVector amp;amp;other) /*noexcept*/: values(other.values), size(other.size)
{
other.vlalues = nullptr;
other.size = 0;
}
Комментарии:
1. Я видел это раньше, но я видел много случаев, когда люди просто используют delete вместо delete[] . Итак, я предполагаю, что так и должно быть сделано?
2. Они вызывают неопределенное поведение. (как и в случае с тривиальным деструктором (как для double), большинство компиляторов делают то же самое, код, похоже, работает).
Ответ №3:
У вас много проблем:
сначала вы должны переместить объекты в move-ctor и оставить moved-from в допустимом состоянии:
DenseVector::DenseVector(DenseVector amp;amp;other) : values(std::move(other.values)), size(std::move(other.size))
{
other.values = nullptr;
}
- Вы должны использовать ctor-init list для инициализации данных-членов, а не назначать им внутри тела ctor после инициализации по умолчанию в ctor-init-list.
DenseVector::DenseVector(int size) : size(size), values(new double[size]{0}) { }
Также в деструкторе вы освобождаете массив двойников delete
, поведение которого не определено, поэтому используйте его таким образом:
delete[] values;
- Вам не нужно проверять в dtor, является ли
values
этоnullptr
или нет:delete[] values;
Комментарии:
1. Есть ли важная разница между использованием init-list и просто использованием тела конструктора?
2. Конечно! использование тела ctor — это просто присвоение, поэтому данные элемента записываются дважды: один раз инициализированный по умолчанию int ctor-init-list (допускается тогда и только тогда, когда они сконструированы по умолчанию). второе в присваивании в теле конструктора.