C++拷贝构造后修改新对象值导致原对象值同步改变问题
问题根源
修改v4元素时同步改动v2的核心原因是拷贝构造、拷贝赋值运算符中std::memcpy的参数完全传错,导致浅拷贝问题:
- 你写的
std::memcpy(&data, &(v.data), v.elements);中,前两个参数传的是两个对象里data指针变量自身的地址,而非指针指向的堆数组首地址:&data是当前对象中存放指针的栈内存地址,&v.data是源对象中存放指针的栈内存地址。执行这句代码时,会直接把源对象data存储的地址值拷贝覆盖当前对象的data值,相当于你刚给新对象new出来的独立内存直接泄漏,两个对象的data最终指向同一块堆内存,修改其中一个的元素自然会影响另一个。 - 第三个参数传了元素个数
v.elements,但std::memcpy要求传入待拷贝的总字节数,就算前两个地址写对,这里长度不对也无法完成正确的元素拷贝。 - 额外的小语法问题:
main函数中vec v4 = v2末尾缺少分号,无法正常编译。 - 额外的内存泄漏隐患:类没有定义析构函数释放动态申请的数组内存,程序运行会产生内存泄漏。
修正方案
- 修正
std::memcpy的三个参数:- 目标地址直接传
data(新对象new出来的数组首地址) - 源地址直接传
v.data(源对象存储元素的数组首地址) - 拷贝长度传
v.elements * sizeof(double)(所有元素占的总字节数)
- 目标地址直接传
- 补全析构函数,补上main函数里缺失的分号。
修正后的核心代码如下:
class vec { private: size_t elements; double* data; public: vec(size_t size) : elements{ size }, data{ new double[size] } { std::cout << "First constructor" << "\n"; }; vec(size_t size, double ival) : elements{ size }, data{ new double[size] } { std::cout << "Second constructor" << std::endl; for (size_t i = 0; i < elements; i++) { data[i] = ival; } } vec(std::initializer_list<double> iList): vec(static_cast<size_t>(iList.size())) { std::cout << "Third constructor" << std::endl; size_t count{ 0 }; for (auto element: iList) { data[count] = element; count++; } } // 修正拷贝构造 vec(const vec& v) : elements{ v.elements }, data{ new double[v.elements] }{ std::cout << "Copy constructor " << "\n"; std::memcpy(data, v.data, v.elements * sizeof(double)); } // 补全析构函数 ~vec() { delete[] data; } // 修正拷贝赋值,同时优化为异常安全的写法:先拷贝新数据再释放旧内存 vec& operator=(const vec& v) { std::cout << "Copy assignment " << "\n"; if (this != &v) { const auto new_data{ new double[v.elements] }; std::memcpy(new_data, v.data, v.elements * sizeof(double)); delete[] data; data = new_data; elements = v.elements; } return *this; } double& operator[](size_t idx){ return this->data[idx]; } friend std::ostream& operator<<(std::ostream& os, const vec& v) { for (size_t i = 0; i < v.elements; i++) { os << v.data[i] << "\n"; } return os; } }; int main(void) { vec v2 = {4, 5, 6}; vec v4 = v2; // 补全缺失的分号 v4[0] = 11; v4[1] = 12; return 0; }
修正后v2和v4持有独立的堆内存,修改v4的元素不会对v2产生任何影响。
内容的提问来源于stack exchange,提问作者radar101
相关产品推荐
相关产品推荐

