struct List {
int size;
int* items;
List& operator=(const List& l);
};
List& List::operator=(const List& l)
{
delete[] items;
size = l.size;
items = new int[20];
for (int i = 0; i < size; i++)
items[i] = l.items[i];
return *this;
}
ostream& operator<<(ostream& out, const List& l)
{
out << "Size: " << l.size << "\t {";
for (int i = 0; i < l.size; i++)
out << l.items[i] << " ";
return (out << "}");
}
int main()
{
int size = 6;
List l1 = { size, new int[size]{ 0, 1, 2, 3, 4, 5 } };
List l2 = l1;
l2.items[1] = 50;
cout << l1 << endl;
cout << l2 << endl;
return 0;
}
我很困惑,因为当我使用重载运算符分配 l2 = l1 时,为什么以后更改l2时l1的内容会发生变化?特别是因为l1作为const传递。它们以某种方式指向内存中的同一对象,而不是副本。
答案 0 :(得分:8)
List l2 = l1;
不调用副本分配运算符(operator=(const List& l)
)。由于“赋值”发生在变量声明期间,因此您将通过复制初始化来初始化l2
,该初始化将调用编译器生成的默认复制构造函数。由于它会进行浅表复制,因此两个对象都将指向相同的数据。
如果要编写自己的类来管理内存/资源,则需要至少提供自己的副本构造函数,副本分配运算符和销毁器。这称为the rule of three。如果要包括移动语义,则需要提供一个移动构造函数和一个移动赋值运算符,这被称为5规则。还有零规则,其中使用已经“做正确的事”的使用类型(就像使用std::vector
一样),允许编译器生成的默认设置为您工作,您可以在The rule of three/five/zero
答案 1 :(得分:5)
List l2 = l1;
尽管使用=
,因为这是一个声明,您正在执行副本构造(通常是“副本初始化”),这与赋值运算符无关。>
您没有定义一个复制构造函数(它看起来应该很像您的复制赋值运算符),因此指针确实是共享的。
如果您撰写以下内容,结果将与您预期的一样:
List l2{};
l2 = l1;
顺便说一句,如果我是您,我会给size
和items
作为默认值(分别为0
和nullptr
)。否则,当您忘记了{}
时,成员将具有未指定的值,并且所有地狱都会崩溃。这可以通过一个不错的默认构造函数来完成,或者通过忘记整个企业并使用std::vector
来代替;)
答案 2 :(得分:3)
List l2 = l1;
调用编译器生成的 copy构造函数,该构造函数不会深度复制指针成员。
如果要对std::vector
使用items
,则可以将构造函数和赋值运算符留给编译器。 (您的代码中没有任何delete[]
调用,这意味着您的类将像漏勺漏水一样泄漏内存。)