为什么这不是 C++ 中的内存泄漏?

Why is this not a memory leak in C++?

几个月前我问了 问题,我问的是为什么会出现内存泄漏。显然,我忘记了一个虚拟析构函数。

现在我很难理解为什么这不是内存泄漏:

#include <iostream>
#include <vector>
#include <memory>


using namespace std;

class Base{
public:
    explicit Base(double a){
        a_ = a;
    }
    virtual void fun(){
        cout << "Base " << a_ << endl;
    }

protected:
    double a_;
};


class Derived : public Base{
public:
    Derived(double a, double b): Base(a), b_{b}{
    }
    void fun() override{
        cout << "Derived " << a_ << endl;
    }
private:
    double b_;
};



int main() {

    vector<unique_ptr<Base> > m;

    for(int i=0; i<10; ++i){
        if(i%2 == 0){
            m.emplace_back(make_unique<Base>(i));
        }else{
            m.emplace_back(make_unique<Derived>(i, 2*i));
        }
    }

    for(const auto &any:m){
        any->fun();
    }

    return 0;
}

请注意,我没有 Base 的虚拟析构函数。

我认为因为我有一个 unique_ptr<Base> 类型的向量 m 只有来自 Base 的析构函数被调用,我的变量 b_Derived会泄漏,但根据 valgrind 的说法,情况并非如此。 为什么这不是内存泄漏?

我已经用 valgrind-3.13.0 测试过了

如果 b 是一个由于未定义行为而具有资源(内存、网络...)的对象,将会发生内存泄漏。

在这里,派生的析构函数偶然不做任何事情,对象的内存被正确释放(这次)。但是超过 built-in/trivially 的可破坏类型都可能引发内存泄漏(我建议您尝试使用大小为 10 的 vector)。

BTW,o是不是没用过?

当基 class 没有虚拟析构函数时通过多态指针删除对象是未定义的行为。

未定义的行为可能意味着您的代码泄漏内存、崩溃或完美运行。

在这种情况下,运行时库可能会为您的对象分配单个内存块,并且即使当它被不同类型的指针指向时也能够正确删除该块。对于大多数运行时来说,这可能是正确的,但不能保证。例如。当使用 malloc()free() 时,您不需要将 malloc() 的大小提供给 free(),这里也是如此。

如果您在 Derived 中定义了一个析构函数,您会发现它没有被调用。

它不会因为您的 C++ 实现的行为而泄漏内存,但它是未定义的行为,您应该修复它。

在这种情况下这不是内存泄漏,因为...

  1. std::make_unique 使用 new:

    分配

    template<class T, class... Args> unique_ptr<T> make_unique(Args&&... args); [...]
    Returns: unique_­ptr<T>(new T(std::forward<Args>(args)...)).
    [unique.ptr.create]

  2. std::unique_ptr deallocates using the std::default_delete 使用 operator delete.

从内存泄漏的角度来看类型不同没关系,因为delete仍然会用指向分配的对象的指针调用new.

如果 Derived 没有平凡的析构函数,这也将是内存泄漏。例如,如果它包含一个 std::vector,那么 vector 的析构函数将被 ~Derived 调用。如果不调用它,vector 的存储将(可检测到)泄漏。

另请参阅:How does delete work?

但是,根据 C++ 标准,所有这些都是未定义的行为,因此理论上它可能随时停止工作。参见 [expr.delete/3]

In a single-object delete expression, if the static type of the object to be deleted is different from its dynamic type and the selected deallocation function (see below) is not a destroying operator delete, the static type shall be a base class of the dynamic type of the object to be deleted and the static type shall have a virtual destructor or the behavior is undefined.