【问题标题】:When to use new and delete何时使用 new 和 delete
【发布时间】:2014-12-28 23:55:12
【问题描述】:

我正在重新阅读前段时间关于 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...

【问题讨论】:

  • 在将代码示例发布到此处之前尝试一下是个好主意。确保它可以编译。
  • 仅供参考,同样要问的一个引人注目的问题是您是否应该首先使用new。将其添加到要考虑的事项列表中。
  • 这个if (!f1) delete f1; 永远不会删除一个对象,因为它只会在指针是null0 时删除。无论如何,您都不应该在删除之前测试指针,因为如果您删除空指针,它将什么也不做。所以就继续做吧:delete f1;.
  • 这不是实际代码,它只是(简单)代码的示例(sn-ps)。它确实运行,我只是想确保在必要时删除。感谢您的意见,您是绝对正确的。
  • @qnob:重新声明“它确实运行”,不,它甚至不会编译。不要撒谎。

标签: c++ new-operator delete-operator


【解决方案1】:

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


经验法则:

  • 尽可能使用静态对象,然后在需要时创建指向该实例的指针。不需要delete 呼叫。
  • 如果创建指向动态对象的指针,请创建清理代码。所以当你写new 时,也要写delete somehwere at a proper location(并确保它被调用)。
  • 对于每个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++;
}

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

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 时,它不指向任何东西,否则它指向它所指向的任何东西。

当您在“主”类中有一个指针,该指针已创建或未创建时,这很有用。

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;
}

我为蹩脚的分数类道歉,它具有越来越蹩脚的扩展功能。但作为一个例子,它可以达到目的。

【讨论】:

  • 有些人确实建议删除所有内容与使用 new 运算符相比是相反的顺序:就像在 Fraction 的最后一个示例中一样,我在 dtor 中做错了。 int one = new int(); int two = new int(); delete two; delete one; 不知道为什么这应该是其他“可分级”的时间线。 @Anybody:请填写我的这个上限。
【解决方案2】:

创建 C++ 选项的主要方法有两种。一个在堆栈上(即Fraction f1;),当堆栈帧被弹出时,该内存会自动释放。第二个在堆上(即Fraction* f1 = new Fraction();。关键是new关键字。

基本总结是这样的:你的news 和deletes 必须匹配。每次你new某事,当你完成它时你必须delete它。 “何时完成”由您决定。但是,如果你重用一个变量(见下文),你需要先delete,否则你将无法将原始对象取回delete

Fraction* f1 = new Fraction(); // create memory on heap, will need to free
f1 = new Fraction(); // this is a memory leak because I didn't first free the
                     // original f1 object, which I can no longer access

【讨论】:

    【解决方案3】:

    经验法则是每个new 必须有一个对应的delete

    手动使用newdelete 在C++ 中并不常见。当您在不使用newdelete 的情况下初始化事物时,保证由即将到来的} 为您处理。假设每个人都在做自己的工作并在对象方面遵循 RAII 原则。

    Fraction f1(...);
    

    实例化一个名为 f1 的 Fraction 对象。它的析构函数将在作用域结束时被调用。

    “现代”方法是处理上述问题。在这种方法不起作用的极少数情况下,您应该使用智能指针。

    【讨论】:

      【解决方案4】:

      你需要删除指针指向的对象。

      然后您可以使用new 创建另一个对象,但不要忘记稍后再使用delete。 :)

      删除前真的需要检查吗?请确保您阅读了this 的答案,实际上是 no。此外,一个好的做法是将指针(delete 之后)设置为 NULL,以备将来使用。


      那么,如果你在同一个指针上使用两次new 会发生什么?

      回想一下,new 将分配我们要求的内存。然后,我们需要知道这段内存在哪里。为此,new 返回指向该内存的指针。

      所以,假设你这样做:

      f1 = new Fraction(user_input1, user_input2);
      f1 = new Fraction(user_input1, user_input2);
      delete(f1);
      

      可以吗?

      1. 第一个new 分配了f1 指向的一些内存。
      2. 第二个new会覆盖指针的值,导致 在f1 中指向我们分配的新内存块。
      3. 然后我们使用delete 并且内存被取消分配...但是等等, 哪个内存?第一秒new 给我们的。
      4. 来自第一个new 的内存呢?我们无法访问 它,因为我们没有保留指向该内存块的指针。
      5. 这意味着我们有内存泄漏,这是 c++ 中的常见错误。

      记住

      您应该删除与您使用的 new 关键字一样多的内容!


      作为 WhozCraig 的评论,您应该在对象的动态创建(需要 newdelete)和自动完成的对象的静态创建之间做出明智的选择。

      herehere 关于这个话题的回答很好。

      【讨论】:

      • @qnob 确保检查我更新中的链接,特别是因为您计划将来使用这些指针。 :)
      • 正是链接上写的@Galik。 (但我会让它更明显):)
      • 投反对票的人会这么好心解释原因吗? :) 这样我就可以改进答案和我未来的答案。 :)
      • 在删除它(或free()ing)之前测试一个指针是多余的。这类似于确保无符号变量不是负数。根本没有必要。
      • 我的答案中的链接说@dreamlax 是什么意思。此外,我已经更新了答案,说明链接说这不是必需的。你不同意吗?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-22
      • 2018-01-22
      • 2012-10-26
      • 1970-01-01
      • 2014-12-12
      • 2023-04-04
      • 2011-05-27
      相关资源
      最近更新 更多