在C++中分配对象

Assignment of objects in C++

本文关键字:对象 分配 C++      更新时间:2023-10-16

为了将我的问题置于上下文中,我正在使用具有以下定义的矩阵类:

Matrix(unsigned int, unsigned int); // matrix of the given dimension full of zeroes
Matrix(Matrix*); // creates a new matrix from another one
int &operator()(int, int);  // used to access the matrix
int **matrix; // the matrix

现在采用这两个代码片段:

第一:

Matrix test(4,4);
Matrix ptr = test;
ptr(0,0) = 95;

第二:

Matrix test(4,4);
Matrix *ptr = &test;
(*ptr)(0,0) = 95;

两个代码具有相同的效果,(0,0)位置的元素接收95(第一个片段与Java非常相似,这也是导致我提出这个问题的原因)。问题是,这两种方法是否正确地分配了对象?

这有点复杂。

考虑这个简单的类:

class Thing1
{
public:
  int n;
}

现在我们尝试第一个实验:

Thing1 A;
A.n = 5;
Thing1 B = A;
B.n = 7;
cout << A.n << " " << B.n << endl;

结果是"5 7"。 AB是两个独立的对象。改变一个不会改变另一个。

第二个实验:

Thing1 *p = &A;
p->n = 9;
cout << A.n << " " << p->n << endl;

结果是"9 9"; p是指向A的指针,所以A.np->n是一回事。

现在事情变得复杂了:

class Thing2
{
public:
  int *p;
};
...
Thing2 A;
A.p = new int(2);
Thing2 B = A;
*(B.p) = 4;
cout << *(A.p) << " " << *(B.p) << endl;

现在结果是"4 4"。赋值B = A复制指针,因此尽管AB是两个不同的对象,但它们的指针指向同一个 int。这是一个浅层副本。一般来说,如果你想做一个深拷贝(也就是说,每个事物都指向它自己的一个int),你必须手动完成,或者给类一个赋值运算符来处理它。由于您的 Matrix 类没有显式赋值运算符,因此编译器会为其提供默认值 - 这是一个浅拷贝。这就是为什么在您的第一个代码段中,两个矩阵似乎都发生了变化。

编辑:感谢@AlisherKassymov指出表单的声明Thing A=B;使用复制构造函数,而不是赋值运算符。因此,要使解决方案在上述代码中工作,复制构造函数必须进行深层复制。 (请注意,如果复制构造函数这样做,您几乎肯定希望赋值运算符也这样做(请参阅三法则))。另请注意,如果这些函数变得复杂,则只需让复制构造函数调用赋值运算符即可。

这两者并不相等。

第一个代码段

Matrix test,是完整的内容被复制到Matrix ptr。稍后使用 ptr 时,仅更改副本,而不更改原始Matrix test

第二个片段

Matrix test的地址被放入指针Matrix *ptr。指针现在保存 test 的地址。当你写(*ptr)时,你取消引用指针并使用原始test的值。

在爪哇语中

在Java中,所有对象都是指针(像int这样的原语不是)。当您将一个对象分配给另一个对象时,默认设置是仅覆盖指针值。工作原理类似于您的第二个示例。

第一个将test的值复制到ptr中。第二个将 ptr 设置为指向 test 地址的指针

这两个操作是不一样的。在第一种情况下,ptr将具有与 test 相同的值,但它们在其中是该数据的两个不同副本,因此您对ptr(0,0) = 95;的分配不会设置test(0, 0)

然而,在第二个实例中,ptr指向test的地址,因此ptr的取消引用测试。因此,当您在此处设置值时,您实际上也在设置test(0, 0)的值。

这可以通过这样的程序轻松验证:

#include <iostream>
class Test{
public:
    int i;
};
int main(){
    Test c;
    c.i = 1;
    Test d = c;
    d.i = 2;
    std::cout << "Value: " << c.i << "n";
    Test* e = &c;
    (*e).i = 2;
    std::cout << "Pointer: " << c.i << "n";
}

当然,如果您动态分配矩阵(新),那么当您按照第一个示例复制值时,指向数据的指针也会被复制,因此当您设置新数据时,它似乎等于第二个示例。

在这种情况下,Java 行为的等效项使用C++引用表示

Matrix test(4,4);
Matrix &ptr = test;
ptr(0,0) = 95;

此代码确实与指针版本执行相同的操作,即它修改原始test对象。

您的第一个代码示例正式创建原始对象的副本,然后修改该副本。但是,您的Matrix类似乎是一个拥有一些较低级别内存(matrix指针)的多级对象。如果您的 Matrix 类复制构造函数实现了浅拷贝逻辑(即它与原始对象共享较低级别的矩阵数据,而不是深拷贝),则修改副本也会显示为修改原始对象。此行为是否正确取决于您的意图。

在您的评论中,您提到第一个代码似乎也修改了原始对象。这立即意味着您的类实际上实现了浅层复制逻辑。看起来它不是您设计的预期部分。显然,您在实现Matrix类时忘记遵循三法则。