问题
A couple of months ago I asked this question where I asked why there was a memory leak. Apparently, I forgot a virtual destructor.
Now I'm struggling to understand why this is not a memory leak:
#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;
}
Note that I do not have a virtual destructor for Base
.
I thought that because I have a vector m
of type unique_ptr<Base>
only the destructor from Base
gets called and my variable b_
in Derived
would leak but according to valgrind this is not the case.
Why is this not a memory leak?
I have tested this with valgrind-3.13.0
回答1:
Deleting an object via a polymorphic pointer when the base class doesn't have a virtual destructor is undefined behaviour.
Undefined behaviour may mean your code leaks memory, crashes or works perfectly.
In this case the runtime library presumably allocated a single block of memory for your object and is able to delete that block correctly even when it is pointed to by a pointer of a different type. This is probably true for most runtimes but there are no guarantees. E.g. when using malloc()
and free()
you don't need to supply the size of the malloc()
to free()
, the same is happening here.
If you had defined a destructor in Derived
you would see that it is not being called.
回答2:
There would be a memory leak if b
was an object that had resources (memory, network...) because of undefined behavior.
Here, by chance, the derived destructor doesn't do anything and the memory for the object is freed properly (this time). But anything more than built-in/trivially destructible types could trigger a memory leak (I suggest you try a vector
of size 10 for instance).
BTW, o
is not used?
回答3:
It doesn't leak memory because of how your C++ implementation behaves, but it is undefined behavior and you should fix it.
It's not a memory leak in this case because...
std::make_unique allocates using
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 which uses
operator delete
.
It doesn't matter from the perspective of memory leaks that the types are different, because delete
will still be called with the pointer to the object allocated by new
.
It would also be a memory leak if Derived
did not have a trivial destructor. If it held a std::vector
for example, that vector
's destructor would be called by ~Derived
. If it's not called, the storage for the vector
would (detectably) leak.
See also: How does delete work?
However, all of this is undefined behavior per the C++ standard, so it might theoretically stop working any time. See [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.
来源:https://stackoverflow.com/questions/53372775/why-is-this-not-a-memory-leak-in-c