我有一个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
和delete
来解决我的问题。毕竟,内存是在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
没有定义任何用于复制/移动j
的复制/移动构造函数或复制/移动赋值运算符。当main()
中的std::vector
重新分配其内部数组时,其中任何现有的S
对象都会在内存中复制/移动。
在您的示例中,j
无效,因为S
根本没有试图管理j
。对于main()
的std::vector
中的每个S
对象,当它被复制/移动时,它的j
指针会被浅层复制到新对象中,而当旧对象被破坏时,j
指向的std::vector
也会被破坏,使新对象中的j
悬空,这就是为什么稍后访问j
时会得到segfault的原因。
当class
或struct
拥有指向资源的指针时,它必须正确实现3/5/0规则,以便在复制/移动资源时复制/移动该资源。简而言之,该规则规定,如果class
/struct
需要实现析构函数、复制/移动构造函数或复制/移动赋值运算符,则可能需要实现所有这些运算符。在您的情况下,由于您有一个销毁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
,然后可以完全删除析构函数(但不能删除复制/移动构造函数和复制/移动赋值运算符(,例如:
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;
}