为什么这不是 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++ 实现的行为而泄漏内存,但它是未定义的行为,您应该修复它。
在这种情况下这不是内存泄漏,因为...
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]
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.
几个月前我问了
现在我很难理解为什么这不是内存泄漏:
#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++ 实现的行为而泄漏内存,但它是未定义的行为,您应该修复它。
在这种情况下这不是内存泄漏,因为...
分配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]std::unique_ptr
deallocates using thestd::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.