下面是我试图理解的“三个规则”的错误实现。
在调试程序时,我发现调试器在清理int *k
时遇到问题,可以通过定义int *k = nullptr
或在复制构造函数中将其设置为合理的值来解决。
但是,我不明白该程序导致的错误(访问冲突)是如何出现的。
我确实知道,在复制分配构造函数v1
的{{1}}不再指向有效的内存地址之后。
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;
}
答案 0 :(得分:3)
实际上非常简单:您的副本构造函数不会创建副本。实际上,它不会初始化任何成员,因此由此构造函数创建的任何实例都充满了废话。
对于operator=(Vector2 other)
的调用,将调用复制构造函数来创建other
(这是三个规则的要点),因此other
充满了废话。
然后,将k
(又名this
)的有效v1
与k
的the脚other
交换。
然后,在调用v1
的析构函数时,它会在糟糕的delete[] k
->访问冲突中调用k
。
使您的副本构造函数进行复制。或至少使其正确初始化k
(例如,初始化为nullptr
)。
答案 1 :(得分:0)
在other
中构造operator=
使用了复制构造函数,该构造函数不会创建指向值的新副本。您的副本构造函数甚至可能不会复制k
,因为它是POD类型,因此不一定是默认构造的或默认复制的。
然后在销毁它时尝试将其销毁两次。或者根据堆栈布局等随机因素,它可能根本不会复制k
,然后尝试delete
一个无效的指针。
答案 2 :(得分:0)
您的问题在Vector2(const Vector2 &other)
您可以通过按值传递在operator =
中隐式使用此构造函数;但是您无法将k分配给该构造函数中的任何值。
这导致交换将有效k替换为无效k,然后删除无效k;导致您的崩溃。
答案 3 :(得分:0)
可以通过安排事件的确切顺序来得出解决方案,例如:更多的打印输出和测试,何时调用什么参数
开始于:v1 = v2;
v2
使用参数other(无论其他参数如何)调用复制构造函数,尤其是:其int* k
并不指向有效内存。为简单起见,我们将其称为新的Vector2 v3。 由于在步骤1中未正确初始化v3
,因此在副本构造函数中实际上出现了错误。
步骤2和步骤3基本上是“隐藏”,将错误从v3
转移到v1
上。
现在有趣的问题是v3
是如何实际生成的?不是默认的构造函数!