"Rule of Three" 的执行出错了
Implementation of "Rule of Three" gone wrong
下面是"The rule of three"的错误实现,我试着去理解。
调试程序,我发现调试器在清理 int *k
时出现问题,这可以通过定义 int *k = nullptr
或简单地在复制构造函数中将其设置为合理的值来解决。
但是,我不明白程序的结果错误(访问冲突)是如何产生的。
我知道,在复制赋值构造函数后 v1
的 int *k
不再指向有效的内存地址。
class Vector2 {
public:
std::string name = "default";
int* k;
Vector2(int s, std::string n) : k(new int[s]), name(n) {
}
Vector2(const Vector2 &other) {
std::cout<< "Copy constructor: " << name << std::endl;
}
~Vector2() {
std::cout << "Deleting: " << name << std::endl;
delete[] k;
}
void swap(Vector2& other) {
using std::swap;
swap(k, other.k);
}
Vector2& operator=(Vector2 other) {
std::cout << "Copy assignment constructor: " << name << std::endl;
swap(other);
return *this;
}
};
int main() {
Vector2 v1 = Vector2(2, "v1");
Vector2 v2 = Vector2(4, "v2");
v1 = v2;
std::cout << &v1 << " " << &v2 << std::endl;
std::cout << &v1.k << " " << &v2.k << std::endl;
return 0;
}
下面是上述程序的控制台输出:
Copy constructor: default
Copy assignment constructor: v1
Deleting: default
0000001B5611FA28 0000001B5611FA78
0000001B5611FA50 0000001B5611FAA0
Deleting: v2
Deleting: v1
16:18:42: The program has unexpectedly finished.
在 operator=
中构造 other
使用复制构造函数,它不会创建指向值的新副本。您的复制构造函数甚至可能不会复制 k
,因为它是 POD 类型,因此不一定默认构造或默认复制。
然后当它被破坏时它会尝试破坏它两次。或者根据堆栈布局等随机因素,它可能根本不会复制 k
然后它会尝试 delete
一个无效的指针。
您的问题在 Vector2(const Vector2 &other)
您在 operator =
中通过按值传递隐式地使用此构造函数;但是您未能将 k 分配给该构造函数中的任何值。
这导致交换用无效的k替换有效的k,然后删除无效的k;导致你崩溃。
其实很简单:你的拷贝构造函数不做拷贝。事实上,它没有初始化任何成员,所以这个构造函数创建的任何实例都是垃圾。
对于operator=(Vector2 other)
的调用调用了拷贝构造函数来创建other
(这就是三分法则的要点),所以other
里面全是废话。
然后你将this
(又名v1
)的有效k
与other
.
的蹩脚k
交换
然后,当 v1
的析构函数被调用时,它会在糟糕的 k
--> 访问冲突上调用 delete[] k
。
解决方案
让你的复制构造函数制作一个副本。或者至少,使其正确初始化 k
(例如 nullptr
)。
可以通过列出事件的确切顺序来得出解决方案,例如:更多的打印输出和测试,什么时候调用什么参数:
开始于:v1 = v2;
v2
调用 copy constructor 参数为 other(无论其他是什么),特别是:它的 int* k
没有指向有效内存。为简单起见,我们将此称为新的 Vector2 v3。
- 现在使用 v3 调用 复制赋值构造函数 。
- 然后我们开始交换。
错误实际上出现在 复制构造函数 中,因为 v3
在步骤 1 中未正确初始化。
第2步和第3步基本上就是"hiding",把错误从v3
转移到v1
上。
现在有趣的问题是,v3
实际上是如何生成的?不是默认构造函数!
下面是"The rule of three"的错误实现,我试着去理解。
调试程序,我发现调试器在清理 int *k
时出现问题,这可以通过定义 int *k = nullptr
或简单地在复制构造函数中将其设置为合理的值来解决。
但是,我不明白程序的结果错误(访问冲突)是如何产生的。
我知道,在复制赋值构造函数后 v1
的 int *k
不再指向有效的内存地址。
class Vector2 {
public:
std::string name = "default";
int* k;
Vector2(int s, std::string n) : k(new int[s]), name(n) {
}
Vector2(const Vector2 &other) {
std::cout<< "Copy constructor: " << name << std::endl;
}
~Vector2() {
std::cout << "Deleting: " << name << std::endl;
delete[] k;
}
void swap(Vector2& other) {
using std::swap;
swap(k, other.k);
}
Vector2& operator=(Vector2 other) {
std::cout << "Copy assignment constructor: " << name << std::endl;
swap(other);
return *this;
}
};
int main() {
Vector2 v1 = Vector2(2, "v1");
Vector2 v2 = Vector2(4, "v2");
v1 = v2;
std::cout << &v1 << " " << &v2 << std::endl;
std::cout << &v1.k << " " << &v2.k << std::endl;
return 0;
}
下面是上述程序的控制台输出:
Copy constructor: default
Copy assignment constructor: v1
Deleting: default
0000001B5611FA28 0000001B5611FA78
0000001B5611FA50 0000001B5611FAA0
Deleting: v2
Deleting: v1
16:18:42: The program has unexpectedly finished.
在 operator=
中构造 other
使用复制构造函数,它不会创建指向值的新副本。您的复制构造函数甚至可能不会复制 k
,因为它是 POD 类型,因此不一定默认构造或默认复制。
然后当它被破坏时它会尝试破坏它两次。或者根据堆栈布局等随机因素,它可能根本不会复制 k
然后它会尝试 delete
一个无效的指针。
您的问题在 Vector2(const Vector2 &other)
您在 operator =
中通过按值传递隐式地使用此构造函数;但是您未能将 k 分配给该构造函数中的任何值。
这导致交换用无效的k替换有效的k,然后删除无效的k;导致你崩溃。
其实很简单:你的拷贝构造函数不做拷贝。事实上,它没有初始化任何成员,所以这个构造函数创建的任何实例都是垃圾。
对于operator=(Vector2 other)
的调用调用了拷贝构造函数来创建other
(这就是三分法则的要点),所以other
里面全是废话。
然后你将this
(又名v1
)的有效k
与other
.
k
交换
然后,当 v1
的析构函数被调用时,它会在糟糕的 k
--> 访问冲突上调用 delete[] k
。
解决方案
让你的复制构造函数制作一个副本。或者至少,使其正确初始化 k
(例如 nullptr
)。
可以通过列出事件的确切顺序来得出解决方案,例如:更多的打印输出和测试,什么时候调用什么参数:
开始于:v1 = v2;
v2
调用 copy constructor 参数为 other(无论其他是什么),特别是:它的int* k
没有指向有效内存。为简单起见,我们将此称为新的 Vector2 v3。- 现在使用 v3 调用 复制赋值构造函数 。
- 然后我们开始交换。
错误实际上出现在 复制构造函数 中,因为 v3
在步骤 1 中未正确初始化。
第2步和第3步基本上就是"hiding",把错误从v3
转移到v1
上。
现在有趣的问题是,v3
实际上是如何生成的?不是默认构造函数!