push_back 调用析构函数时使对象中的指针无效
push_back invalidates pointers in objects when calling the destructor
我有一个 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;
}
}
我想使用 push_back()
将未知数量的 S
放入 std::vector
中。但是,当 vector
重新分配其内存以增长时,它会调用 S
的析构函数并使指针 j
.
无效
我明白为什么下面的例子会出现段错误,但我想知道是否有好的方法来处理这种情况。
实际上,我想我可以通过在 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;
}
S
没有定义任何 copy/move 构造函数或 copy/move 赋值运算符来 copy/move j
。当 main()
中的 std::vector
重新分配其内部数组时,其中任何现有的 S
对象都会在内存中得到 copied/moved。
j
在您的示例中无效,因为 S
根本没有尝试管理 j
。对于 main()
的 std::vector
中的每个 S
对象,当它是 copied/moved 时,其 j
指针将 shallow-copied 放入新对象中,并且当旧对象被销毁时,j
指向的 std::vector
也被销毁,留下新对象中的 j
悬空,这就是为什么在访问 [=17 时出现段错误的原因=] 稍后.
当 class
或 struct
拥有指向资源的指针时,当它本身是 copied/moved.简而言之,该规则指出,如果 class
/struct
需要实现析构函数、copy/move 构造函数或 copy/move 赋值运算符,它可能需要实施所有这些。在你的例子中,因为你有一个销毁 j
的析构函数,你需要添加其他的以确保每个 S
对象都有一个有效的 j
来销毁。
在您的示例中,我建议根本不要从 S
外部分配 std::vector
。让 S
处理该分配,外部代码应该只根据需要用值填充 j
。
试试像这样的东西:
struct S {
S();
S(const S &);
S(S &&);
~S();
S& operator=(S);
int get_i() const;
void set_i(int);
std::vector<int>& get_j();
const std::vector<int>& 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 &src) {
i = src.i;
j = new std::vector<int>(*(src.j));
}
S::S(S &&src) {
i = src.i; src.i = 0;
j = src.j; src.j = nullptr;
}
S::~S() {
cout << "Delete " << j << std::endl;
delete j;
}
S& 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>& S::get_j() {
return *j;
}
const std::vector<int>& 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
,然后你可以完全删除析构函数(但不是 copy/move构造函数和 copy/move 赋值运算符),例如:
struct S {
S();
S(const S &);
S(S &&);
S& operator=(S);
int get_i() const;
void set_i(int);
std::vector<int>& get_j();
const std::vector<int>& 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 &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 &&src) {
i = src.i; src.i = 0;
j = std::move(src.j);
}
S& 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>& S::get_j() {
return *j;
}
const std::vector<int>& 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
对象,就像它一样为 main()
中的 std::vector
对象执行操作,例如:
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;
}
我有一个 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;
}
}
我想使用 push_back()
将未知数量的 S
放入 std::vector
中。但是,当 vector
重新分配其内存以增长时,它会调用 S
的析构函数并使指针 j
.
我明白为什么下面的例子会出现段错误,但我想知道是否有好的方法来处理这种情况。
实际上,我想我可以通过在 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;
}
S
没有定义任何 copy/move 构造函数或 copy/move 赋值运算符来 copy/move j
。当 main()
中的 std::vector
重新分配其内部数组时,其中任何现有的 S
对象都会在内存中得到 copied/moved。
j
在您的示例中无效,因为 S
根本没有尝试管理 j
。对于 main()
的 std::vector
中的每个 S
对象,当它是 copied/moved 时,其 j
指针将 shallow-copied 放入新对象中,并且当旧对象被销毁时,j
指向的 std::vector
也被销毁,留下新对象中的 j
悬空,这就是为什么在访问 [=17 时出现段错误的原因=] 稍后.
当 class
或 struct
拥有指向资源的指针时,当它本身是 copied/moved.简而言之,该规则指出,如果 class
/struct
需要实现析构函数、copy/move 构造函数或 copy/move 赋值运算符,它可能需要实施所有这些。在你的例子中,因为你有一个销毁 j
的析构函数,你需要添加其他的以确保每个 S
对象都有一个有效的 j
来销毁。
在您的示例中,我建议根本不要从 S
外部分配 std::vector
。让 S
处理该分配,外部代码应该只根据需要用值填充 j
。
试试像这样的东西:
struct S {
S();
S(const S &);
S(S &&);
~S();
S& operator=(S);
int get_i() const;
void set_i(int);
std::vector<int>& get_j();
const std::vector<int>& 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 &src) {
i = src.i;
j = new std::vector<int>(*(src.j));
}
S::S(S &&src) {
i = src.i; src.i = 0;
j = src.j; src.j = nullptr;
}
S::~S() {
cout << "Delete " << j << std::endl;
delete j;
}
S& 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>& S::get_j() {
return *j;
}
const std::vector<int>& 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
,然后你可以完全删除析构函数(但不是 copy/move构造函数和 copy/move 赋值运算符),例如:
struct S {
S();
S(const S &);
S(S &&);
S& operator=(S);
int get_i() const;
void set_i(int);
std::vector<int>& get_j();
const std::vector<int>& 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 &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 &&src) {
i = src.i; src.i = 0;
j = std::move(src.j);
}
S& 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>& S::get_j() {
return *j;
}
const std::vector<int>& 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
对象,就像它一样为 main()
中的 std::vector
对象执行操作,例如:
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;
}