首页 文章

什么时候被称为C析构函数?

提问于
浏览
89

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

更具体的问题:

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

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

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

10 回答

  • 1

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

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

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

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

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

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

    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");
    }
    
  • 0

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

    答案是肯定的 . @DavidSchwartz给出了一个例子,但这是一个相当不寻常的例子 . 我在许多C程序员一直使用的东西的引导下: std::vector (和 std::deque ,虽然它没有被使用得那么多) .

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

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

    现在,如果你从矢量中找到一个项目会发生什么?它不能只使用 delete - 它会释放整个内存块;它需要销毁该内存中的一个对象而不破坏任何其他对象,或释放它控制的任何内存块(例如,如果你从一个向量中的5个项目,然后立即 push_back 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
    

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

  • 12
    • 使用 new 创建对象时,您负责调用 delete . 当您使用 make_shared 创建对象时,生成的 shared_ptr 负责保持计数并在使用计数变为零时调用 delete .

    • 超出范围确实意味着留下一个区块 . 这是在调用析构函数时,假设该对象未使用 new 分配(即它是一个堆栈对象) .

    • 关于唯一需要显式调用析构函数的时间是使用placement new分配对象时 .

  • 5

    1)对象不是“通过指针”创建的 . 有一个指针分配给你'新'的任何对象 . 假设这就是你的意思,如果你在指针上调用'delete',它实际上会删除(并调用析构函数)指针取消引用的对象 . 如果将指针指定给另一个对象,则会发生内存泄漏; C中没有任何东西可以为你收集垃圾 .

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

    3)不是真的 . 可能会有暗魔法暗示其他情况,但通常您希望将“新”关键字与“删除”关键字相匹配,并将所有必需品放入您的析构函数中,以确保它能够正确清理自己 . 如果不这样做,请务必向使用该类的任何人详细说明析构函数,以了解如何手动清理该对象的资源 .

  • 3

    为了给问题3提供详细的答案:是的,有一些(罕见的)你可以明确地调用析构函数,特别是作为一个新的位置对应,就像dasblinkenlight观察到的那样 .

    举一个具体的例子:

    #include <iostream>
    #include <new>
    
    struct Foo
    {
        Foo(int i_) : i(i_) {}
        int i;
    };
    
    int main()
    {
        // Allocate a chunk of memory large enough to hold 5 Foo objects.
        int n = 5;
        char *chunk = static_cast<char*>(::operator new(sizeof(Foo) * n));
    
        // Use placement new to construct Foo instances at the right places in the chunk.
        for(int i=0; i<n; ++i)
        {
            new (chunk + i*sizeof(Foo)) Foo(i);
        }
    
        // Output the contents of each Foo instance and use an explicit destructor call to destroy it.
        for(int i=0; i<n; ++i)
        {
            Foo *foo = reinterpret_cast<Foo*>(chunk + i*sizeof(Foo));
            std::cout << foo->i << '\n';
            foo->~Foo();
        }
    
        // Deallocate the original chunk of memory.
        ::operator delete(chunk);
    
        return 0;
    }
    

    这种事情的目的是将内存分配与对象构造分离 .

  • 2
    • Pointers - 常规指针不支持RAII . 没有明确的 delete ,就会有垃圾 . 幸运的是C有auto pointers为你处理这个!

    • Scope - 想一想变量何时对程序不可见 . 正如你所指出的那样,通常这是在 {block} 的末尾 .

    • Manual destruction - 永远不要尝试这个 . 让范围和RAII为您带来魔力 .

  • 3

    每当你使用"new",即将地址附加到指针,或者说,你在堆上声明空间时,你需要"delete"它 .
    1.yes,当你删除某些东西时,会调用析构函数 .
    2.当调用链表的析构函数时,会调用's objects'析构函数 . 但如果它们是指针,则需要手动删除它们 . 3.当空间被"new"声明时 .

  • 59

    是的,如果对象超出范围(如果它在堆栈上)或者在指向对象的指针上调用 delete 时,则会调用析构函数(a.k.a. dtor) .

    • 如果通过 delete 删除指针,则将调用dtor . 如果重新分配指针而不先调用 delete ,则会出现内存泄漏,因为该对象仍然存在于内存中 . 在后一种情况下,不调用dtor .

    • 一个好的链表实现将在列表被销毁时调用列表中所有对象的dtor(因为你要么调用一些方法来破坏它,要么它超出了范围本身) . 这取决于实现 .

    • 我对此表示怀疑,但如果有一些奇怪的情况我不会感到惊讶 .

  • 0

    如果不是通过指针创建对象(例如,A a1 = A();),则在对象被破坏时调用析构函数,总是在对象所在的函数完成时调用 . 例如:

    void func()
    {
    ...
    A a1 = A();
    ...
    }//finish
    

    当代码被执行到行"finish"时,将调用析构函数 .

    如果通过指针创建对象(例如,A * a2 = new A();),则在删除指针时调用析构函数(删除a2;) . 如果该点未被用户明确删除或给定删除之前的新地址,发生内存泄漏 . 那是一个错误 .

    在一个链接如果我们使用std :: list <>,我们不需要关心析构函数或内存泄漏,因为std :: list <>已经为我们完成了所有这些 . 在我们自己编写的链表中,我们应该编写析构函数并明确删除指针 . 否则会导致内存泄漏 .

    我们很少手动调用析构函数 . 它是为系统提供的功能 .

    抱歉我的英语不好!

  • 0

    请记住,在为该对象分配内存后立即调用对象的构造函数,而在释放该对象的内存之前调用析构函数 .

相关问题