C++以及何时使用delete

qno*_*nob 9 c++ new-operator delete-operator

我刚刚在C++上重读了一些代码(我现在正在学校学习Java),而且当我必须使用删除时,我有点困惑.

例如:声明两个对象时:

Fraction* f1;
Fraction* f2;
Run Code Online (Sandbox Code Playgroud)

并像这样创建f1和f2:

f1 = new Fraction(user_input1, user_input2);
f2 = new Fraction(user_input3, user_input4);
Run Code Online (Sandbox Code Playgroud)

下次我想使用newoperator创建一个新对象时,是否必须先删除?我很困惑,因为我习惯在java中使用垃圾收集器来处理对象及其删除.在重新使用新内容之前是否必须删除?

if (f1) delete f1;

if (f2) delete f2;

//initialize again...
Run Code Online (Sandbox Code Playgroud)

bra*_*low 14

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


规则大拇指:

  • 尽可能使用静态对象,然后在需要时创建指向该实例的指针.不需要delete电话.
  • 如果创建指向动态对象的指针,请创建清理代码.因此,当你写作时,new也要delete在适当的位置写一些(并确保调用它).
  • 对于每个new关键字,都需要有一个delete关键字.否则,您将获取机器所具有的所有资源,导致应用程序崩溃或停止.它也会使系统变慢.

静态创建对象:

Fraction f1;
Run Code Online (Sandbox Code Playgroud)
  • 无需删除任何在退出创建的scoop时处理的内容.

动态创建对象:

Fraction* f1;
Run Code Online (Sandbox Code Playgroud)

现在,您将此地址写入堆上的内存块.它是无效的,因为您没有为其分配任何内容.好的做法是 - 取决于你声明它的位置 - 为它分配一个NULL(窗口)或0(跨平台).

Fraction* f1 = 0;
Run Code Online (Sandbox Code Playgroud)

何时使用 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;

}
Run Code Online (Sandbox Code Playgroud)

在某些情况下,拥有一个Fraction数组或指向它的指针可能很有用.这里使用int来简化,与跳过错误处理相同:

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

// Usage:
Add( 1 );
Add( 4 );
Run Code Online (Sandbox Code Playgroud)

这里发生了一件事,没有通过动态对象分配任何内存.它们会自动释放.函数返回的指针是指向静态内存块的指针.

将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 ) );
Run Code Online (Sandbox Code Playgroud)

现在你需要内存块泄漏,因为你没有清理代码.

当你之后的每个调用Add(...)delete test,你已经清理内存,但你已经失去了你已经存储中的值int* arr[ 10 ],因为它们都指向记忆保持的值.

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

void CleanUp()
{
    for( int a = 0; a < 10; ++a )
        delete arr[ a ];
}
Run Code Online (Sandbox Code Playgroud)

小用法示例:

int* test;
int  test2;

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

/* do whatever you need */

CleanUp();
Run Code Online (Sandbox Code Playgroud)

为什么我们要使用指针:

int Add( int val )
{
    return val; // indeed very lame
}
Run Code Online (Sandbox Code Playgroud)

当您调用需要参数(类型)的函数时,您不是传入实例而是传递它的副本.在上面的函数中,您将返回该副本的副本.所有涉及的内存都会造成很多重复,你的应用程序会非常慢.

考虑一下:

class Test
{
    int  t;
    char str[ 256 ];
}
Run Code Online (Sandbox Code Playgroud)

如果函数需要类型Test,则复制int和256个字符.因此,使函数只需一个指向Test的指针.然后使用指针指向的存储器,不需要复制.

int Add( int val )
{
    val++;
    return val;
}
Run Code Online (Sandbox Code Playgroud)

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

int i = Add( 1 );
Run Code Online (Sandbox Code Playgroud)

结果: i = 2;

void Add( int* val )
{
    // mind the return type
    *val++;
}
Run Code Online (Sandbox Code Playgroud)

在此示例中,您将地址传递给值,然后 - 在解除引用后 - 向值中添加一个.

int i = 1;
Add( &i );
Run Code Online (Sandbox Code Playgroud)

结果: i = 2;

现在你已经传递了地址i,而不是复制它.在函数中,您直接将1添加到该内存块的值.因为你改变了内存本身,你什么都不返回.


取消/测试有效指针

有时您会遇到以下示例:

if( p != 0 ) // or if( p )
{
    /* do something with p */
}
Run Code Online (Sandbox Code Playgroud)

这只是为了检查指针p是否有效.但是,无效的地址 - 因此不指​​向您保留的内存(访问冲突) - 也将通过.对于您的代码,无效指针是有效地址.

因此,要使用这样的检查,您必须使用NULL(或0)指针.

Fraction* f1 = 0;
Run Code Online (Sandbox Code Playgroud)

当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;
    }
}
Run Code Online (Sandbox Code Playgroud)

我们正在创建两个指针,所以在dtor中我们正在清理那些指针.仅检查ExtendedFeature,因为可能会创建或不创建此功能.始终创建basicFeature.

您可以通过调用一个新函数替换dtor中包含其范围的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;
    }
}
Run Code Online (Sandbox Code Playgroud)

而新的dtor:

Fraction::~Fraction()
{
    delete basicFeature;
    removeExtendedFeature();
}
Run Code Online (Sandbox Code Playgroud)

归零的另一个功能可能是:

int Fraction::getValue()
{
    int result = *basicFeature;
    if( ExtendedFeature )
        result += *ExtendedFeature;
    return result;
}
Run Code Online (Sandbox Code Playgroud)

我对蹩脚的Fraction类表示道歉,并且具有更加蹩脚的扩展功能.但作为一个例子,它将达到目的.


Mat*_*obi 8

创建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
Run Code Online (Sandbox Code Playgroud)


2c2*_*c2c 6

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

使用手动newdelete是不是在C++做一个平常的事.当您在不使用的情况下初始化内容时,new或者delete保证在即将到来时为您处理}.假设每个人都在做自己的工作,并且在涉及对象时遵循RAII原则.

Fraction f1(...);
Run Code Online (Sandbox Code Playgroud)

实例化一个名为f1的Fraction对象.它的析构函数将在到达范围结束时被调用.

"现代"方法是处理上述事情.在极少数情况下,这种方法不起作用,你应该使用智能指针.


gsa*_*ras 3

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

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

删除之前真的需要检查吗?请务必阅读答案,它实际上说不。此外,一个好的做法是将指针(在 后面delete)设置为 NULL,以供将来使用。


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

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

那么,假设您这样做:

f1 = new Fraction(user_input1, user_input2);
f1 = new Fraction(user_input1, user_input2);
delete(f1);
Run Code Online (Sandbox Code Playgroud)

可以吗?

  1. 第一个分配一些指向的new内存。f1
  2. 第二个new将覆盖指针的值,导致f1指向我们分配的新内存块。
  3. 然后我们使用delete并且内存被解除分配......但是等等,哪个内存?第二个new给我们的。
  4. 那么最初的记忆呢new?我们无法访问它,因为我们没有保留指向该内存块的指针。
  5. 这意味着我们有内存泄漏,这是 C++ 中的常见错误。

记住

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


正如 WhozCraig 的评论,您应该在动态创建对象(requirenewdelete)和静态创建对象(自动完成)之间做出明智的选择。

关于这个主题的很好的答案在这里这里