几个月前,我问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测试过
答案 0 :(得分:5)
当基类没有虚拟析构函数时通过多态指针删除对象是未定义的行为。
未定义的行为可能意味着您的代码会泄漏内存,崩溃或正常运行。
在这种情况下,运行时库可能为您的对象分配了一个内存块,即使该块被其他类型的指针指向,也能够正确删除该内存块。对于大多数运行时来说,这可能是正确的,但并不能保证。例如。在使用malloc()
和free()
时,您无需向malloc()
提供free()
的大小,这里也是如此。
如果您在Derived
中定义了一个析构函数,则会看到它没有被调用。
答案 1 :(得分:3)
如果b
是一个由于行为未定义而具有资源(内存,网络...)的对象,则会发生内存泄漏。
在这里,偶然地,派生的析构函数不执行任何操作,并且对象的内存已正确释放(这次)。但是,除了内置的/可破坏的类型之外,其他任何事情都可能引发内存泄漏(例如,我建议您尝试使用大小为10的vector
)。
顺便说一句,o
未使用?
答案 2 :(得分:1)
它不会因为C ++实现的行为而泄漏内存,但这是未定义的行为,您应该对其进行修复。
在这种情况下,这不是内存泄漏,因为...
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]
std::unique_ptr
使用使用operator delete
的{{3}}取消分配。
从内存泄漏的角度来看,没关系 ,因为delete
仍将使用指向new
分配的对象的指针来调用
如果Derived
没有琐碎的析构函数,也将导致内存泄漏。例如,如果它持有std::vector
,则vector
将调用~Derived
的析构函数。如果未调用,vector
的存储将(可检测)泄漏。
另请参阅:std::default_delete
但是,根据C ++标准,所有这些都是未定义的行为,因此从理论上讲它可能随时停止工作。参见How does delete work?。
在单对象删除表达式中,如果要删除的对象的静态类型不同于其动态类型,并且所选的释放函数(参见下文)不是破坏运算符删除,则静态类型应为基数要删除的对象的动态类型的类,并且静态类型应具有虚拟析构函数或行为未定义。