何时调用C++析构函数?

Pat*_*ray 100 c++ destructor

基本问题:程序何时在C++中调用类的析构函数方法?有人告诉我,只要一个物体超出范围或受到一个物体的影响就会被召唤delete

更具体的问题:

1)如果通过指针创建对象并且稍后删除该指针或给出指向的新地址,那么它所指向的对象是否会调用其析构函数(假设没有其他指向它)?

2)关注问题1,什么定义了一个对象何时超出范围(不是关于对象何时离开给定的{block}).那么,换句话说,什么时候是一个析构函数调用链表中的对象?

3)你想要手动调用析构函数吗?

Dav*_*rtz 66

1)如果通过指针创建对象并且稍后删除该指针或给出指向的新地址,那么它所指向的对象是否会调用其析构函数(假设没有其他指向它)?

这取决于指针的类型.例如,智能指针通常在删除对象时删除它们.普通指针没有.当指针指向不同的对象时也是如此.一些智能指针会破坏旧对象,或者如果它没有更多引用就会销毁它.普通指针没有这样的智慧.它们只保存一个地址,并允许您通过专门执行操作对它们指向的对象执行操作.

2)关注问题1,什么定义了一个对象何时超出范围(不是关于对象何时离开给定的{block}).那么,换句话说,什么时候是一个析构函数调用链表中的对象?

这取决于链表的实现.典型的集合在销毁时会销毁所有包含的对象.

因此,链接的指针列表通常会破坏指针,但不会破坏它们指向的对象.(这可能是正确的.它们可能是其他指针的引用.)但是,专门设计用于包含指针的链表可能会删除自身销毁的对象.

智能指针的链接列表可以在删除指针时自动删除对象,如果没有更多引用,则可以自动删除对象.这一切都取决于你选择你想要的东西.

3)你想要手动调用析构函数吗?

当然.一个例子是,如果你想用另一个相同类型的对象替换一个对象,但又不想释放内存只是为了再次分配它.您可以在适当的位置销毁旧对象并构建一个新对象.(但是,通常这是一个坏主意.)

// pointer is destroyed because it goes out of scope,
// but not the object it pointed to. memory leak
if (1) {
 Foo *myfoo = new Foo("foo");
}


// pointer is destroyed because it goes out of scope,
// object it points to is deleted. no memory leak
if(1) {
 Foo *myfoo = new Foo("foo");
 delete myfoo;
}

// no memory leak, object goes out of scope
if(1) {
 Foo myfoo("foo");
}
Run Code Online (Sandbox Code Playgroud)

  • 我认为你的最后一个例子声明了一个函数?这是"最令人烦恼的解析"的一个例子.(另一个更微不足道的一点是,我猜你的意思是'新的Foo()`,大写'F'.) (2认同)
  • 很好的抓住.谢谢. (2认同)

Jer*_*fin 15

其他人已经解决了其他问题,所以我只想看一点:你是否想要手动删除一个对象.

答案是肯定的.@DavidSchwartz给出了一个例子,但这是一个相当不寻常的例子.我将举例说明许多C++程序员一直使用的内容:( std::vector并且std::deque,尽管它没有那么多使用).

正如大多数人所知,std::vector当你添加的项目多于当前分配所能容纳的项目时,会分配更大的内存块.但是,当它执行此操作时,它具有一个内存块,能够容纳比当前在向量中更多的对象.

为了管理这个问题,有些vector内容是通过对象分配原始内存Allocator(除非另有说明,否则表示它使用::operator new).然后,当您使用(例如)push_back将项添加到vector内部时,向量使用a placement new在其内存空间的(先前)未使用部分中创建项.

现在,如果你erase是一个来自向量的项目会发生什么?它不能只使用delete- 这将释放其整个内存块; 它需要销毁该内存中的一个对象而不破坏任何其他对象,或者释放它控制的任何内存块(例如,如果你erase从向量中获取push_back5个项目,那么立即再添加5个项目,则保证向量不会重新分配你这样做的记忆.

为此,向量通过显式调用析构函数直接销毁内存中的对象,而不是使用delete.

如果,其他人使用连续存储来写一个容器,大致就像一个vector(或者其中一些变体,就像std::deque真的一样),你几乎肯定想要使用相同的技术.

例如,让我们考虑如何为循环环形缓冲区编写代码.

#ifndef CBUFFER_H_INC
#define CBUFFER_H_INC

template <class T>
class circular_buffer {
    T *data;
    unsigned read_pos;
    unsigned write_pos;
    unsigned in_use;
    const unsigned capacity;
public:
    circular_buffer(unsigned size) :
        data((T *)operator new(size * sizeof(T))),
        read_pos(0),
        write_pos(0),
        in_use(0),
        capacity(size)
    {}

    void push(T const &t) {
        // ensure there's room in buffer:
        if (in_use == capacity) 
            pop();

        // construct copy of object in-place into buffer
        new(&data[write_pos++]) T(t);
        // keep pointer in bounds.
        write_pos %= capacity;
        ++in_use;
    }

    // return oldest object in queue:
    T front() {
        return data[read_pos];
    }

    // remove oldest object from queue:
    void pop() { 
        // destroy the object:
        data[read_pos++].~T();

        // keep pointer in bounds.
        read_pos %= capacity;
        --in_use;
    }

    // release the buffer:
~circular_buffer() { operator delete(data); }
};

#endif
Run Code Online (Sandbox Code Playgroud)

不同于标准集装箱,这使用operator newoperator delete直接.对于实际使用,您可能确实想要使用分配器类,但目前它会分散注意力而不是贡献(IMO,无论如何).


das*_*ght 6

  1. 使用时创建对象new,您负责调用delete.当您使用创建对象时make_shared,结果shared_ptr负责delete在使用计数变为零时保持计数和调用.
  2. 超出范围确实意味着留下障碍.这是在调用析构函数时,假设该对象未被分配new(即它是一个堆栈对象).
  3. 关于唯一需要显式调用析构函数的时间是使用展示位置new分配对象时.


Nat*_*ord 6

1) 对象不是“通过指针”创建的。有一个指针分配给您“新建”的任何对象。假设这就是您的意思,如果您在指针上调用“删除”,它实际上会删除(并在其上调用析构函数)指针取消引用的对象。如果将指针分配给另一个对象,则会出现内存泄漏;C++ 中的任何内容都不会为您收集垃圾。

2) 这是两个不同的问题。当变量在其中声明的堆栈帧从堆栈中弹出时,它就会超出范围。通常这是你离开一个街区的时候。堆中的对象永远不会超出范围,尽管它们在堆栈上的指针可能会。没有特别保证会调用链表中对象的析构函数。

3) 不是真的。Deep Magic 可能会提出不同的建议,但通常您希望将“新”关键字与“删除”关键字相匹配,并将所有必要的内容放入析构函数中,以确保它正确地自我清理。如果您不这样做,请务必向使用该类的任何人说明如何手动清理该对象的资源,并使用特定说明对析构函数进行注释。