如何避免删除在C++中作为参数给出的对象

How to avoid deletion of object given as a parameter in C++

本文关键字:参数 对象 删除 何避免 C++      更新时间:2023-10-16

我是C++新手,我不知道如何解决以下问题。

类 Foo 有一个构造函数,它创建一个给定大小的双精度数组。析构函数删除此数组。打印方法打印数组。

#include <iostream>
class Foo {
private:
int size;
double* d;
public:
Foo(int size);
~Foo();
void print();
};
Foo::Foo(int size)
{
this->size = size;
d = new double[size];
for (int i = 0; i < size; i++)
{
d[i] = size * i;
}
}
Foo::~Foo()
{
delete[] d;
}
void Foo::print()
{
for (int i = 0; i < size; i++)
{
std::cout << d[i] << "  ";
}
std::cout << std::endl;
}

现在我有一个函数func(Foo f)它什么都不做。

void func(Foo f){}
int main()
{
Foo f(3);
f.print();
func(f); 
Foo g(5);
f.print();
return 0;    
}

执行此代码将给出以下输出:

0  3  6
0  5  10

尽管我两次都打印f,但不知何故,数组中的值发生了变化。

我猜Foo的析构函数是在执行func(Foo f)后在参数Foo f上调用的,这会释放为d分配的内存,该内存被重新分配给Foo g(5)。但是,如何在不使用矢量或智能指针的情况下避免这种情况呢?

问题出在类的设计上。 默认复制构造函数将在按传递到名为func的独立函数时创建一个新的Foo实例。

当名为fFoo实例退出作用域时,代码将调用用户提供的析构函数,该析构函数将删除双精度数组。 当名为f的原始Foo实例在程序结束时退出范围时,这会将代码打开两次删除同一数组的不幸情况。

在我的机器上运行时,代码不会产生相同的输出。 相反,我看到两条0 3 6输出线,后跟指示双自由操作的故障。

解决方案是通过引用(或通过 const 引用)传递来避免复制:void func(Foo const &f) { }或提供一个有效的复制构造函数来制作底层数组的深层副本。 通过引用只是避免使用错误的创可贴。

使用std::vector<double>可以解决此问题,因为默认复制构造函数将执行深层复制并避免双重释放。 在这个小示例中,这绝对是最好的方法,但它避免了必须了解问题的根源。 大多数C++开发人员将学习这些技术,然后立即尽其所能避免编写手动分配和解除分配内存的代码。

您可能应该将对象作为引用传递func(Foo& f),或者 - 如果您根本不想修改它 - 作为常量引用func(const Foo& f)传递。这不会在函数调用期间创建或删除任何对象。

除此之外,正如其他人已经提到的,你的班级应该更好地实施三法则。

当你将一个值传递给一个函数时,它应该被复制。析构函数在副本上运行,不应影响原始对象。Foo无法实现复制构造函数,因此编译器提供了默认的构造函数,该构造函数仅执行结构的成员级副本。因此,FuncFoo的"副本"包含与原始指针相同的指针,并且其析构函数释放两者指向的数据。

为了便于惯用C++代码使用,除了析构函数之外,Foo还必须至少实现一个复制构造函数和一个赋值运算符。这三者结合在一起的规则有时被称为"三法则",并在其他答案中提到。

下面是构造函数外观的(未经测试的)示例:

Foo::Foo(const Foo& other) {
// copy constructor: construct Foo given another Foo
size = other->size;
d = new double[size];
std::copy(other->d, other->d + size, d);
}
Foo& Foo::operator=(const Foo& other) {
// assignment: reinitialize Foo with another Foo
if (this != &other) {
delete d;
size = other->size;
d = new double[size];
std::copy(other->d, other->d + size, d);
}
return *this;
}

此外,还可以修改func等函数以接受对Foo的引用或对Foo的常量引用,以避免不必要的复制。单独执行此操作也可以解决您遇到的直接问题,但它无助于其他问题,因此在执行其他任何操作之前,您绝对应该实现适当的复制构造函数。

最好写一本关于C++的好书,其中解释了三法则和其他C++陷阱。此外,请考虑使用 STL 容器(如std::vector)作为成员。由于他们自己实现了三规则,因此您的类不需要这样做。

一个问题是调用 func 会创建按位副本。当该副本超出范围时,将调用析构函数来删除数组。 你应该将void func(Foo f){}更改为void func(Foo& f){}。 但更好的是,您添加一个创建复制构造函数或添加一个私有声明来阻止它被意外调用。