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

时间:2018-11-19 10:36:59

标签: c++ memory-leaks valgrind undefined-behavior unique-ptr

几个月前,我问this问题,问为什么内存泄漏。显然,我忘记了虚拟析构函数。

现在我正在努力了解为什么这不是内存泄漏:

#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的虚拟析构函数。

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

我已经用valgrind-3.13.0测试过

3 个答案:

答案 0 :(得分:5)

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

未定义的行为可能意味着您的代码会泄漏内存,崩溃或正常运行。

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

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

答案 1 :(得分:3)

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

在这里,偶然地,派生的析构函数不执行任何操作,并且对象的内存已正确释放(这次)。但是,除了内置的/可破坏的类型之外,其他任何事情都可能引发内存泄漏(例如,我建议您尝试使用大小为10的vector)。

顺便说一句,o未使用?

答案 2 :(得分:1)

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

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

  1. std::make_unique使用new进行分配:

      

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

  2. std::unique_ptr使用使用operator delete的{​​{3}}取消分配。

从内存泄漏的角度来看,没关系 ,因为delete仍将使用指向new分配的对象的指针来调用

如果Derived没有琐碎的析构函数,也将导致内存泄漏。例如,如果它持有std::vector,则vector将调用~Derived的析构函数。如果未调用,vector的存储将(可检测)泄漏。

另请参阅:std::default_delete

但是,根据C ++标准,所有这些都是未定义的行为,因此从理论上讲它可能随时停止工作。参见How does delete work?

  

在单对象删除表达式中,如果要删除的对象的静态类型不同于其动态类型,并且所选的释放函数(参见下文)不是破坏运算符删除,则静态类型应为基数要删除的对象的动态类型的类,并且静态类型应具有虚拟析构函数或行为未定义。