如何避免删除在C++中作为参数给出的对象
How to avoid deletion of object given as a parameter in C++
我是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
实例。
当名为f
的Foo
实例退出作用域时,代码将调用用户提供的析构函数,该析构函数将删除双精度数组。 当名为f
的原始Foo
实例在程序结束时退出范围时,这会将代码打开两次删除同一数组的不幸情况。
在我的机器上运行时,代码不会产生相同的输出。 相反,我看到两条0 3 6
输出线,后跟指示双自由操作的故障。
解决方案是通过引用(或通过 const 引用)传递来避免复制:void func(Foo const &f) { }
或提供一个有效的复制构造函数来制作底层数组的深层副本。 通过引用只是避免使用错误的创可贴。
使用std::vector<double>
可以解决此问题,因为默认复制构造函数将执行深层复制并避免双重释放。 在这个小示例中,这绝对是最好的方法,但它避免了必须了解问题的根源。 大多数C++开发人员将学习这些技术,然后立即尽其所能避免编写手动分配和解除分配内存的代码。
您可能应该将对象作为引用传递func(Foo& f)
,或者 - 如果您根本不想修改它 - 作为常量引用func(const Foo& f)
传递。这不会在函数调用期间创建或删除任何对象。
除此之外,正如其他人已经提到的,你的班级应该更好地实施三法则。
当你将一个值传递给一个函数时,它应该被复制。析构函数在副本上运行,不应影响原始对象。Foo
无法实现复制构造函数,因此编译器提供了默认的构造函数,该构造函数仅执行结构的成员级副本。因此,Func
中Foo
的"副本"包含与原始指针相同的指针,并且其析构函数释放两者指向的数据。
为了便于惯用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){}。 但更好的是,您添加一个创建复制构造函数或添加一个私有声明来阻止它被意外调用。
- 如何使用Visual Studio 2017在C++中为参数化对象数组使用唯一指针
- Arduino C++在构造函数中用参数声明对象数组
- 我使用向量来创建类对象列表.初始化向量时如何使用参数调用构造函数?
- 当指向对象的指针作为参数传递给 std::thread 时,内存可见性
- 如何在不使用指针的情况下将派生类的对象作为参数传递给基类中的函数?
- C++ 类对象作为函数参数
- 如何将对象数组作为参数传递给模板
- 使用向量初始化参数化构造函数的对象数组
- 如何将成员函数作为参数传递并在派生对象上执行方法列表
- 如何编写将展开以定义具有模板参数的对象的宏
- C++类对象 - 遍历基于参数的对象
- 将指针传递到成员的指针,从模板参数包到函数对象
- 如何将子类作为函数的参数传递给期望基类,然后将该对象传递到指向这些抽象类对象的指针向量中?
- 是否可以获取成员函数模板参数的拥有对象?
- C++多参数对象创建
- 在不创建参数对象的情况下解析constexpr函数
- 推断模板参数对象成员的类型
- 如何解析c++ v8中的参数对象
- 当默认参数对象被销毁时
- 为什么函数不能更改其参数对象的地址/引用值?