[C ++]如果按值传递会创建本地副本,为什么原始对象会被更改?

时间:2017-09-25 08:59:00

标签: c++

我在here上读到一个类似的问题,即按值传递将在函数中创建对象的本地副本。当我尝试这样做时,我的原始对象会被改变,但我不希望它改变。

此测试的目标是尝试传递一个对象,在本地更改它,但保持原始状态不变。

在ObjectList头文件中:

int **board;

包含构造函数和打印函数的ObjectLise类:

ObjectList::ObjectList()
{
    board = new int*[9];
    for(int i = 0; i < 9; i++)
    {
        board[i] = new int[9];
    }

    for(int i = 0; i < 9; i++)
    {
        for(int j = 0; j < 9; j++)
        {
            board[i][j] = 10;
        }
    }
}

void ObjectList::printB()
{
    for(int i = 0; i < 9; i++)
    {
        for(int j = 0; j < 9; j++)
        {
            cout << board[i][j] << ",";
        }
        cout << endl;
    }
}

ChangeBoard类,包含传递给ObjectList的函数。

void ChangeBoard::LetsChange(ObjectList layout)
{
    layout.board[0][0] = 99;
    layout.board[1][0] = 99;
    layout.board[2][0] = 99;
    layout.board[3][0] = 99;
    layout.board[4][0] = 99;
    layout.board[5][0] = 99;
}

在main中我创建了两个对象并将ObjectList对象传递给LetsChange函数,以尝试仅在本地更改对象,即仅在函数中:

ObjectList object = ObjectList();

ChangeBoard change = ChangeBoard();

object.printB();

change.LetsChange(object);
cout << endl;

object.printB();

输出显示原始对象被更改:

10,10,10,10,10,10,10,10,10,
10,10,10,10,10,10,10,10,10,
10,10,10,10,10,10,10,10,10,
10,10,10,10,10,10,10,10,10,
10,10,10,10,10,10,10,10,10,
10,10,10,10,10,10,10,10,10,
10,10,10,10,10,10,10,10,10,
10,10,10,10,10,10,10,10,10,
10,10,10,10,10,10,10,10,10,

99,10,10,10,10,10,10,10,10,
99,10,10,10,10,10,10,10,10,
99,10,10,10,10,10,10,10,10,
99,10,10,10,10,10,10,10,10,
99,10,10,10,10,10,10,10,10,
99,10,10,10,10,10,10,10,10,
10,10,10,10,10,10,10,10,10,
10,10,10,10,10,10,10,10,10,
10,10,10,10,10,10,10,10,10,

4 个答案:

答案 0 :(得分:2)

如果没有实际复制内存(或合适的标准容器)的复制构造函数,那么当您按值传递对象时会发生指针成员board的复制。复制 指针 ,而不是它指向的数据。它是一个所谓的副本,而不是副本。

这意味着在ChangeBoard::LetsChange函数中您有两个对象,其中board成员都指向同一位置。快速打印board成员或只使用调试器,您应该很快就能看到这一点。

在函数中,您修改此board指针指向的内存,而不是对象本身的数据。如果你做了,例如在函数中layout.board = new int*[9];,您将修改实际成员,并且该更改不会反映在调用函数中。

如果你有delete[]board指向的内存的析构函数,那么指针的复制可能会导致undefined behavior。因为那时你将释放两个对象的内存,当你以后在原始对象中使用board成员时,它将不再指向已分配的数据。

这是您需要考虑the rules of three, five and zero

的原因

我建议您停止使用指针,如果大小是编译时常量,则使用std::array;如果在运行时设置大小,则使用std::vector

使用std::arraystd::vector,您可以使用the rule of zero,而无需担心处理自己的内存,复制和删除。

答案 1 :(得分:1)

隐式复制构造函数将在复制对象时复制板指针的内容...因此它仍将指向原始对象的数据。 ex plicit copy constructor可能需要分配和复制board数组以获得您期望的行为。

答案 2 :(得分:0)

正如其他人所说,你的例子并不完整

但是让我试着清楚一点 您的对象object按值传递,例如LetsChange函数调用会生成对象的本地副本,例如其成员。

在您的情况下,我认为board是您班级ObjectList的成员将被复制,但不会被分配到board指向的内存。

因此,当您修改内存时,它将在技术上“影响”您拥有的对象。

答案 3 :(得分:0)

您似乎没有提供复制构造函数。默认的复制构造函数只是复制对象的每个成员(如果为每个成员定义了此操作)。创建了board的副本。但是,指针的副本指向与原始数据相同的数据。因此,您正在通过副本更改技术上不属于任何对象的原始数据。

您始终可以通过编写复制指向的数据的复制构造函数来解决此问题。更好的解决方案是删除new和相应的delete语句,并替换为更合适的替代方法。

大多数人会使用std::vector。它将修复您的默认复制构造函数并移动构造函数。它也是一个可变长度数组,您可能会发现它很有用。

如果您需要强制执行静态大小,std::array几乎是一个更安全,可复制的C风格数组。它不会衰减到指针,它提供了正确的复制构造函数。

最后,如果由于某种原因需要静态数组但在堆上,std::unique_ptr<>std::shared_ptr<>可以保存数组。他们所做的是在内部管理对象的生命周期。区别在于unique_ptr仅允许传递所有权,而shared_ptr s可以指向同一个对象,并确保在最后一个指针被销毁或重置时进行清理。