何时使用新建和删除

2022-06-11 00:00:00 c++ new-operator delete-operator

我正在重读一段时间以前关于C++的代码(我现在正在学校学习Java),我有点困惑什么时候必须使用delete

例如: 声明两个对象时:

Fraction* f1;
Fraction* f2;

和创建f1f2如下:

f1 = new Fraction(user_input1, user_input2);
f2 = new Fraction(user_input3, user_input4);
下次要使用new运算符创建新对象时,是否必须先使用delete?我感到困惑是因为我习惯了让Java中的垃圾收集器来处理对象及其删除。再次使用new之前必须delete吗?

if (f1) delete f1;

if (f2) delete f2;

// initialize again...

解决方案

我不会告诉您何时使用delete,而是尝试解释为什么您无论如何都要使用指针。因此您可以决定何时使用动态对象、如何使用它们以及何时调用delete(以及不调用)。


经验法则:

  • 尽可能使用静态对象,然后在需要时创建指向该实例的指针。不需要delete调用。
  • 如果创建指向动态对象的指针,请创建清理代码。因此,当您编写new时,也要将delete写入到合适位置(并确保该位置被调用)。
  • 对于每个new关键字,需要是delete关键字。否则,您将占用机器拥有的所有资源,从而导致应用程序崩溃或停止。它还会使系统变慢。

静态创建对象:

Fraction f1;
  • 不需要删除任何内容,退出创建它的独家新闻时会处理这些内容。

动态创建对象:

Fraction* f1;

现在您有了指向堆上内存块的地址。它是无效的,因为您没有为其分配任何内容。根据声明位置的不同,最佳做法是为其分配NULL(Windows)或0(跨平台)。

Fraction* f1 = 0;

何时使用delete

一旦您创建了动态对象,从而调用了new运算符,您就需要在某个地方调用delete

int main()
{

    Fraction* f1 = 0;    // Good practise to avoid invalid pointers
                         // An invalid pointer - if( f1 ){ Access violation }

    f1 = new Fraction(); // Could have done this at the previous line

    /* do whatever you need */

    if( f1 )
    {

        delete f1; 
        f1 = 0;          // not needed since we are leaving the application
    
    }

    return 0;

}

在某些情况下,拥有一个Fraction数组或指向它的指针可能很有用。为简单起见,此处使用int,与跳过错误处理相同:

int arr[ 10 ];
int cur = -1;
int* Add( int fraction )
{
    arr[++cur] = fraction;
    return &arr[cur];
}

// Usage:
Add( 1 );
Add( 4 );
这里发生了一件事,没有通过动态对象分配给任何内存。它们会自动释放。该函数返回的指针是指向静态内存块的指针。

arr作为指向int的指针时:

int* arr[ 10 ];
int cur = -1;
int* Add( int* fraction )
{
    arr[++cur] = fraction;
    return arr[cur];
}

// Usage:
int* test;

test = Add( new int( 1 ) );
test = Add( new int( 4 ) );

现在您必须修复由于没有清理代码而泄漏的内存块。

当您在每个Add(...)delete test之后调用时,您清理了内存,但丢失了int* arr[ 10 ]中存储的值,因为它们指向保存该值的内存。

您可以创建另一个函数,并在处理完这些值后调用此函数:

void CleanUp()
{
    for( int a = 0; a < 10; ++a )
        delete arr[ a ];
}

小用法示例:

int* test;
int  test2;

test  = Add( new int( 1 ) );
test2 = *Add( new int( 4 ) ); // dereference the returned value

/* do whatever you need */

CleanUp();

为什么要使用指针:

int Add( int val )
{
    return val; // indeed very lame
}

调用需要参数(类型)的函数时,不是传入实例,而是实例的副本。在上面的函数中,您将返回该副本的副本。这将相当于大量复制所有涉及的内存,并使您的应用程序变得非常慢。

考虑一下:

class Test
{
    int  t;
    char str[ 256 ];
}
如果函数需要类型Test,则需要复制int和256个字符。因此,使该函数只需要指向Test的指针。则使用指针所指向的内存,不需要复制。

int Add( int val )
{
    val++;
    return val;
}

在最后一个示例中,我们向val的副本添加1,然后返回该副本的副本。

int i = Add( 1 );

结果:i=2;

void Add( int* val )
{
    // mind the return type
    *val++;
}

在此示例中,您将地址传递给一个值,然后在取消引用后将1添加到该值。

int i = 1;
Add( &i );

结果:i=2;

现在您已将地址传递给i,而不是复制它。在该函数中,您可以直接将该内存块的值加1。由于您更改了内存本身,因此不返回任何内容。


为空/测试有效指针

有时您会遇到这样的例子:

if( p != 0 ) // or if( p )
{
    /* do something with p */
}
这只是为了检查指针p是否有效。然而,无效的地址--因此不指向您已保留的内存(访问冲突)--也将通过。对于您的代码,无效指针是有效地址。

因此,要使用此类检查,您必须NULL(或0)指针。

Fraction* f1 = 0;

f1 == 0时,它不指向任何内容,否则它指向它指向的任何内容。

当您在已创建或未创建的"main"类中有指针时,此功能非常有用。

class Fraction
{
    public:
    int* basicFeature;
    int* ExtendedFeature = 0; // NULL this pointer since we don't know if it
                              // will be used
    Fraction( int fraction )
    {
        // Create a pointer owned by this class
        basicFeature = new int( fraction );
    }
    Fraction( int fraction, int extended ) // mind the static
    : Fraction( fraction )
    {
        // Create a pointer owned by this class
        ExtendedFeature = new int( extended );
    }
    ~Fraction()
    {
        delete basicFeature;
        if( ExtendedFeature )
            // It is assigned, so delete it
            delete ExtendedFeature;
    }
}
通过构造函数,我们创建了两个指针,因此在析构函数中,我们清理了这些指针。仅检查ExtendedFeature,因为可能会创建也可能不会创建。basicFeature始终创建。

您可以通过调用一个新函数来替换if语句,包括其在析构函数中的作用域:removeExtendedFeature(),其中该函数实现将是:

Fraction::removeExtendedFeature()
{
    if( ExtendedFeature )
    {
        // It is assigned, so delete it
        delete ExtendedFeature;
        // Now it is important to NULL the pointer again since you would
        // get an access violation on the clean up of the instance of 
        // the class Fraction
        ExtendedFeature = 0;
    }
}

和新的析构函数:

Fraction::~Fraction()
{
    delete basicFeature;
    removeExtendedFeature();
}

清空的另一个功能可能是:

int Fraction::getValue()
{
    int result = *basicFeature;
    if( ExtendedFeature )
        result += *ExtendedFeature;
    return result;
}

我为这个糟糕的类片段道歉,因为它有一个更糟糕的扩展功能。但作为一个例子,它将达到目的。

相关文章