#c #pointers #segmentation-fault
#c #указатели #ошибка сегментации
Вопрос:
У меня есть struct
, который содержит такой указатель:
struct S {
S();
~S();
int i;
std::vector<int>* j;
};
S::S() {
i = 0;
j = 0;
}
S::~S() {
if (j != 0) {
cout << "Delete " << j << std::endl;
delete j;
}
}
Я хочу ввести неизвестное число S
в std::vector
using push_back()
. Однако, когда vector
перераспределяется его память для увеличения, он вызывает деструктор S
и аннулирует указатель j
.
Я понимаю, почему приведенный ниже пример segfaults , но я хотел бы знать, есть ли хороший способ, которого я не знаю, для управления этим случаем.
На практике, я думаю, что я могу исправить свою проблему, удалив деструктор ~S
и delete
‘ing main()
перед уничтожением vector
в конце его области видимости. В конце концов, память выделяется main()
, но у меня такое чувство, что S
она должна иметь дело с ее памятью в своем деструкторе.
int main()
{
cout << "Insert the first one" << std::endl;
std::vector<S> v;
v.push_back(S());
v[0].j = new std::vector<int> {1,2,3};
cout << "Insert a new one" << std::endl;
v.push_back(S());
v[1].j = new std::vector<int> {2,3,4};
std::cout << (*(v[0].j))[1] << std::endl; // segfault
std::cout << (*(v[1].j))[1] << std::endl;
return 0;
}
Комментарии:
1. Любая причина, по которой вы не используете
std::unique_ptr
?2. Вы знаете, что такое глубокое копирование? Вы знаете, как написать конструктор копирования?
3. Почти никогда нет причин иметь указатель
std::vector
-владелец .4. Ваш класс не соблюдает правило 3/5/0 , что вызывает проблемы, с которыми вы сталкиваетесь.
5. но у меня такое чувство, что S должен иметь дело со своей памятью в своем деструкторе — не вините деструктор —
S
класс имеет неправильную семантику копирования, уже отмеченную классом, не соблюдающим правило 3. Но зачем искать эту проблему, когда в этом нет необходимости? Просто объявите astd::vector<int> j;
.
Ответ №1:
S
не определяет никаких конструкторов копирования / перемещения или операторов присваивания копирования / перемещения, с помощью которых можно копировать / j
перемещать. Когда std::vector
in main()
перераспределяет свой внутренний массив, все существующие S
в нем объекты копируются / перемещаются в памяти.
j
становится недействительным в вашем примере, потому S
j
что вообще не пытается управлять. Для каждого S
объекта в main()
‘s std::vector
, когда он копируется / перемещается, его j
указатель копируется неглубоко в новый объект, а когда старый объект уничтожается, то std::vector
указываемый на j
него также уничтожается, оставляя j
новый объект зависшим, вот почему вы получаете ошибку segfault при j
последующем доступе.
Когда class
or struct
владеет указателем на ресурс, он ДОЛЖЕН правильно реализовать правило 3/5/0 для копирования / перемещения этого ресурса при копировании / перемещении самого ресурса. В двух словах, правило гласит, что если a class
/ struct
необходимо реализовать либо деструктор, конструктор копирования / перемещения, либо оператор присваивания копирования / перемещения, ему, вероятно, необходимо реализовать их все. В вашем случае, поскольку у вас есть деструктор, который уничтожает j
, вам нужно добавить другие, чтобы убедиться, что у каждого S
объекта есть действительный j
для уничтожения.
В вашем примере я бы предложил ВООБЩЕ НЕ выделять std::vector
извне S
. Пусть S
обрабатывает это распределение, внешний код должен просто j
заполняться значениями по мере необходимости.
Попробуйте что-то более похожее на это:
struct S {
S();
S(const S amp;);
S(S amp;amp;);
~S();
Samp; operator=(S);
int get_i() const;
void set_i(int);
std::vector<int>amp; get_j();
const std::vector<int>amp; get_j() const;
void set_j(std::vector<int>);
private:
int i;
std::vector<int>* j;
};
S::S() {
i = 0;
j = new std::vector<int>;
}
S::S(const S amp;src) {
i = src.i;
j = new std::vector<int>(*(src.j));
}
S::S(S amp;amp;src) {
i = src.i; src.i = 0;
j = src.j; src.j = nullptr;
}
S::~S() {
cout << "Delete " << j << std::endl;
delete j;
}
Samp; S::operator=(S rhs) {
S temp(std::move(rhs));
std::swap(i, temp.i);
std::swap(j, temp.j);
return *this;
}
int S::get_i() const {
return i;
}
void S::set_i(int new_value) {
i = new_value;
}
std::vector<int>amp; S::get_j() {
return *j;
}
const std::vector<int>amp; S::get_j() const {
return *j;
}
void S::set_j(std::vector<int> new_value) {
*j = std::move(new_value);
}
int main()
{
cout << "Insert the first one" << std::endl;
std::vector<S> v;
v.emplace_back();
v[0].set_j({1,2,3});
cout << "Insert a new one" << std::endl;
v.emplace_back();
v[1].set_j({2,3,4});
std::cout << v[0].get_j()[1] << std::endl;
std::cout << v[1].get_j()[1] << std::endl;
return 0;
}
В качестве альтернативы, вообще не используйте new
/ delete
вручную, используйте std::unique_ptr
вместо этого, тогда вы можете полностью удалить деструктор (но не конструкторы копирования / перемещения и операторы присваивания копирования / перемещения), например:
struct S {
S();
S(const S amp;);
S(S amp;amp;);
Samp; operator=(S);
int get_i() const;
void set_i(int);
std::vector<int>amp; get_j();
const std::vector<int>amp; get_j() const;
void set_j(std::vector<int>);
private:
int i;
std::unique_ptr<std::vector<int>> j;
};
S::S() {
i = 0;
j = std::make_unique<std::vector<int>>();
// or, prior to C 14:
// j.reset(new std::vector);
}
S::S(const S amp;src) {
i = src.i;
j = std::make_unique<std::vector<int>>(*(src.j));
// or, prior to C 14:
// j.reset(new std::vector<int>(*(src.j)));
}
S::S(S amp;amp;src) {
i = src.i; src.i = 0;
j = std::move(src.j);
}
Samp; S::operator=(S rhs) {
S temp(std::move(rhs));
std::swap(i, temp.i);
std::swap(j, temp.j);
return *this;
}
int S::get_i() const {
return i;
}
void S::set_i(int new_value) {
i = new_value;
}
std::vector<int>amp; S::get_j() {
return *j;
}
const std::vector<int>amp; S::get_j() const {
return *j;
}
void S::set_j(std::vector<int> new_value) {
*j = std::move(new_value);
}
int main()
{
cout << "Insert the first one" << std::endl;
std::vector<S> v;
v.emplace_back();
v[0].set_j({1,2,3});
cout << "Insert a new one" << std::endl;
v.emplace_back();
v[1].set_j({2,3,4});
std::cout << v[0].get_j()[1] << std::endl;
std::cout << v[1].get_j()[1] << std::endl;
return 0;
}
Однако, с учетом сказанного, на самом деле нет веских причин для j
динамического выделения вообще. std::vector
реализует правило 3/5/0 для самого себя, поэтому вы можете избавиться от динамического указателя в S
и позволить компилятору управлять std::vector
объектом за вас, точно так же, как это делается для std::vector
объекта в main()
, например:
struct S {
int i = 0;
std::vector<int> j;
};
int main()
{
cout << "Insert the first one" << std::endl;
std::vector<S> v;
v.emplace_back();
v[0].j = {1,2,3};
cout << "Insert a new one" << std::endl;
v.emplace_back();
v[1].j = {2,3,4};
std::cout << v[0].j[1] << std::endl;
std::cout << v[1].j[1] << std::endl;
return 0;
}
Комментарии:
1. Спасибо за объяснение. Это проясняет ситуацию.