[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.c++

Deleting from destructor

mc

10/9/2008 5:03:00 PM

Hi Experts,

This may be obvious but I can't find anything one way or another. We have
a class which is always allocated using new, e.g. Foo* foo = new Foo() so we
came up with the idea of releasing the memory from the destructor as
follows:

Foo::Foo()
{
// Initialize stuff
m_This = this; // m_This is a "void*"
}

Foo::~Foo()
{
// Release all resources
// and finally the memory
delete m_This;
}

It's been working fine so far (both Windows and Linux) but we're wondering
about it being either the worse thing to do..... Any thoughts.

Thank you.

Regards,

MC


13 Answers

Victor Bazarov

10/9/2008 5:10:00 PM

0

mc wrote:
> This may be obvious but I can't find anything one way or another. We have
> a class which is always allocated using new, e.g. Foo* foo = new Foo() so we
> came up with the idea of releasing the memory from the destructor as
> follows:
>
> Foo::Foo()
> {
> // Initialize stuff
> m_This = this; // m_This is a "void*"
> }
>
> Foo::~Foo()
> {
> // Release all resources
> // and finally the memory
> delete m_This;
> }
>
> It's been working fine so far (both Windows and Linux) but we're wondering
> about it being either the worse thing to do..... Any thoughts.

That is a VERY BAD IDEA. Basically, the only correct way to invoke the
destructor of an object that was created dynamically is to use 'delete'
with the expression evaluating to the pointer to that object. In most
cases if you have

Foo* foo = new Foo();

somewhere, then somewhere else you have

delete foo;

That will invoke the destructor and *then* deallocate the memory. Now,
if you duplicate the pointer somewhere (in the object itself, or in some
other place), and then use 'delete' with that pointer, you will be
*deleting* the object twice. I am not sure what 'delete (void*)ptr'
does (or does not) compared to 'delete ptr'. Most likely it can't call
the destructor since the type is unknown. But it will, and I am certain
of it, deallocate the memory.

So, if you do use 'delete' with your 'foo', you're deallocating the
memory twice, which is undefined behaviour. If you don't use 'delete
foo', then how do you get the destructor to be called? DO you call it
explicitly (foo->~Foo();)? Then it's rather nonsensical, you should
instead use the normal idiomatic way and let the system perform all the
necessary clean-up for you.

V
--
Please remove capital 'A's when replying by e-mail
I do not respond to top-posted replies, please don't ask

mc

10/9/2008 5:19:00 PM

0

Thanks Victor. I understand what you said and knew. Let me put more
context here but adding a more complete example:

void SKEL::bind(const MCU& mcu, ...)
{
// const FOO& MCU::foo()
// {
// return (*new Foo());
// }
FOO foo = mcu.foo(); // Because of the const FOO&
returned, foo becomes equal to what was returned by MCU::foo()

// do stuff
// when exising here, the destructor for FOO is called and the memory is
release as per previous post
}

The destructor for the object is only called once and no memory leaks were
detected.

MC

"Victor Bazarov" <v.Abazarov@comAcast.net> wrote in message
news:gcldu5$9f0$1@news.datemas.de...
> mc wrote:
>> This may be obvious but I can't find anything one way or another. We
>> have a class which is always allocated using new, e.g. Foo* foo = new
>> Foo() so we came up with the idea of releasing the memory from the
>> destructor as follows:
>>
>> Foo::Foo()
>> {
>> // Initialize stuff
>> m_This = this; // m_This is a "void*"
>> }
>>
>> Foo::~Foo()
>> {
>> // Release all resources
>> // and finally the memory
>> delete m_This;
>> }
>>
>> It's been working fine so far (both Windows and Linux) but we're
>> wondering about it being either the worse thing to do..... Any thoughts.
>
> That is a VERY BAD IDEA. Basically, the only correct way to invoke the
> destructor of an object that was created dynamically is to use 'delete'
> with the expression evaluating to the pointer to that object. In most
> cases if you have
>
> Foo* foo = new Foo();
>
> somewhere, then somewhere else you have
>
> delete foo;
>
> That will invoke the destructor and *then* deallocate the memory. Now, if
> you duplicate the pointer somewhere (in the object itself, or in some
> other place), and then use 'delete' with that pointer, you will be
> *deleting* the object twice. I am not sure what 'delete (void*)ptr' does
> (or does not) compared to 'delete ptr'. Most likely it can't call the
> destructor since the type is unknown. But it will, and I am certain of
> it, deallocate the memory.
>
> So, if you do use 'delete' with your 'foo', you're deallocating the memory
> twice, which is undefined behaviour. If you don't use 'delete foo', then
> how do you get the destructor to be called? DO you call it explicitly
> (foo->~Foo();)? Then it's rather nonsensical, you should instead use the
> normal idiomatic way and let the system perform all the necessary clean-up
> for you.
>
> V
> --
> Please remove capital 'A's when replying by e-mail
> I do not respond to top-posted replies, please don't ask


Rolf Magnus

10/9/2008 5:42:00 PM

0

mc wrote:

> Thanks Victor. I understand what you said and knew. Let me put more
> context here but adding a more complete example:
>
> void SKEL::bind(const MCU& mcu, ...)
> {
> // const FOO& MCU::foo()
> // {
> // return (*new Foo());
> // }
> FOO foo = mcu.foo(); // Because of the const FOO&
> returned, foo becomes equal to what was returned by MCU::foo()

Yes. It becomes a copy of it.

> // do stuff
> // when exising here, the destructor for FOO is called and the memory
> is release as per previous post

The memory for the object foo in SKEL::bind is, but there is the other FOO
object that was dynamically allocated. It's still hanging around, and you
lost all pointers to it, so you can't ever deallocate it. That's a memory
leak. With the destructor you described, you also happen to use delete with
a pointer to memory you didn't get from new, which results in undefined
behavior.

> }
>
> The destructor for the object is only called once and no memory leaks were
> detected.

Sounds like your memory debugger has a problem.

mc

10/9/2008 6:17:00 PM

0

I haven't lost the location where the object was; remember that in the
constructor a private member is initialized to the value of where the object
resides in memory; and the destructor uses a delete for that.


"Rolf Magnus" <ramagnus@t-online.de> wrote in message
news:gclfpm$td3$01$1@news.t-online.com...
> mc wrote:
>
>> Thanks Victor. I understand what you said and knew. Let me put more
>> context here but adding a more complete example:
>>
>> void SKEL::bind(const MCU& mcu, ...)
>> {
>> // const FOO& MCU::foo()
>> // {
>> // return (*new Foo());
>> // }
>> FOO foo = mcu.foo(); // Because of the const FOO&
>> returned, foo becomes equal to what was returned by MCU::foo()
>
> Yes. It becomes a copy of it.
>
>> // do stuff
>> // when exising here, the destructor for FOO is called and the memory
>> is release as per previous post
>
> The memory for the object foo in SKEL::bind is, but there is the other FOO
> object that was dynamically allocated. It's still hanging around, and you
> lost all pointers to it, so you can't ever deallocate it. That's a memory
> leak. With the destructor you described, you also happen to use delete
> with
> a pointer to memory you didn't get from new, which results in undefined
> behavior.
>
>> }
>>
>> The destructor for the object is only called once and no memory leaks
>> were
>> detected.
>
> Sounds like your memory debugger has a problem.
>


Rolf Magnus

10/9/2008 7:59:00 PM

0

Please don't top-post.

mc wrote:

> "Rolf Magnus" <ramagnus@t-online.de> wrote in message
> news:gclfpm$td3$01$1@news.t-online.com...
>> mc wrote:
>>
>>> Thanks Victor. I understand what you said and knew. Let me put more
>>> context here but adding a more complete example:
>>>
>>> void SKEL::bind(const MCU& mcu, ...)
>>> {
>>> // const FOO& MCU::foo()
>>> // {
>>> // return (*new Foo());
>>> // }
>>> FOO foo = mcu.foo(); // Because of the const FOO&
>>> returned, foo becomes equal to what was returned by MCU::foo()
>>
>> Yes. It becomes a copy of it.
>>
>>> // do stuff
>>> // when exising here, the destructor for FOO is called and the
>>> memory is release as per previous post
>>
>> The memory for the object foo in SKEL::bind is, but there is the other
>> FOO object that was dynamically allocated. It's still hanging around, and
>> you lost all pointers to it, so you can't ever deallocate it. That's a
>> memory leak. With the destructor you described, you also happen to use
>> delete with
>> a pointer to memory you didn't get from new, which results in undefined
>> behavior.
>
> I haven't lost the location where the object was; remember that in the
> constructor a private member is initialized to the value of where the
> object resides in memory; and the destructor uses a delete for that.

Yes, you're right. I missed that. So the copy's destructor frees the
original object's memory. However, the original's destructor isn't properly
called as it's supposed to, which probably won't lead to problems in the
example you showed, but will in less trivial programs. This is very bad
style.




mc

10/9/2008 9:33:00 PM

0

Why is there a need for the original's destructor to be called? After all,
the original is copied verbatim and consequently the destructor is called
once as expected.

Be that as it may, this is indeed quite obfuscated. The reasoning for
that is that the main caller does not know the object is being allocated
using new and hence is not supposed to call delete. So we had to find a way
to free the memory used and went for a new in place (see actual code below):

const Region& MCU::buffer(void) const
{
char* p = new char[sizeof(Region)];
return (*new(p) Region(std::string(TEMP), 1500));
}



with the Region ctdt:

Region::Region(const std::string& name, size_t size)
{
_this = reinterpret_cast<char *>(this);
_cache = new unsigned char[size], _offset = 0;
_fd = ::open(name.c_str(), O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR
| S_IRGRP | S_IROTH);
assert(_fd != -1);
}


Output::Buffer::~Buffer()
{
if (_fd != -1)
{
::write(_fd, (char *)_cache, _offset);
::close(_fd);
}
delete[] _cache;
delete[] _this;
}



Does that make sense?






Victor Bazarov

10/9/2008 10:16:00 PM

0

mc wrote:
> Why is there a need for the original's destructor to be called?

Because every object constructed must be at some point destructed.
That's the whole idea of OO, objects have lifetime, they are born and
then they die, all in due time in due order. If you don't allow the
object to be properly disposed of, the resources that object allocates
are not freed. And please don't tell us that you know what resources
that object uses and that it doesn't need to free any. It's the
principles that you have to stick to, otherwise your code is impossible
to maintain.

> After all,
> the original is copied verbatim and consequently the destructor is called
> once as expected.

The constructor is called for two objects, the destructor is only called
for one. That's a mistake in logic.

> Be that as it may, this is indeed quite obfuscated. The reasoning for
> that is that the main caller does not know the object is being allocated
> using new and hence is not supposed to call delete.

I believe you've got a problem of ownership to solve. Whoever creates
the object, owns it, unless it transfers the ownership somehow. No, it
is not in the language specification how to transfer ownership, it's in
the design of the program.

> So we had to find a way
> to free the memory used and went for a new in place (see actual code below):
>
> const Region& MCU::buffer(void) const
> {
> char* p = new char[sizeof(Region)];
> return (*new(p) Region(std::string(TEMP), 1500));
> }

This function seems to transfer the ownership of the object to the
caller. That means it's the caller's responsibility to dispose of the
object properly.

If the object is created dynamically, it's a bad idea to return a
reference to it. A more descriptive interface would return a pointer
instead. Even better to return a smart pointer. Those can transfer
ownership by assignment or initialisation, and if the ownership is not
transferred, the pointer owns the object and destroys it when it is
destroyed itself.

> with the Region ctdt:
>
> Region::Region(const std::string& name, size_t size)
> {
> _this = reinterpret_cast<char *>(this);
> _cache = new unsigned char[size], _offset = 0;
> _fd = ::open(name.c_str(), O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR
> | S_IRGRP | S_IROTH);
> assert(_fd != -1);
> }
>
>
> Output::Buffer::~Buffer()
> {
> if (_fd != -1)
> {
> ::write(_fd, (char *)_cache, _offset);
> ::close(_fd);
> }
> delete[] _cache;
> delete[] _this;
> }
>
>
>
> Does that make sense?

A little, I suppose. It seems that somebody designed your class to be
an owner of another instance of the same class, which it's responsible
for disposing of. It's very, very obscure. And in that sense, when you
construct a copy from that hanging instance, why don't you save a
pointer to the object instead of the pointer to void? Using your
original terms,

class Foo
{
Foo const *pPrototype;
public:
Foo() : pOther(0) {}
Foo(Foo const& rP) : pPrototype(&rP) // taking ownership here
{ /* whatever */ }

/*virtual?*/ ~Foo() { delete pPrototype; }
};

The instance constructed by your 'MCU::foo' function will have the
'pPrototype' member set to NULL, which is OK to 'delete'. The
copy-constructed instance will take on removing the prototype instance
along when destroyed.

V
--
Please remove capital 'A's when replying by e-mail
I do not respond to top-posted replies, please don't ask

Kai-Uwe Bux

10/9/2008 10:18:00 PM

0

You did it again. Please do not top-post. It is frowned upon around these
parts.

mc wrote:

> Why is there a need for the original's destructor to be called? After all,
> the original is copied verbatim and consequently the destructor is called
> once as expected.
[snip]

I am not sure, I understand your code. You have:

Foo::Foo() {
// Initialize stuff
m_This = this; // m_This is a "void*"
}

Foo::~Foo() {
// Release all resources
// and finally the memory
delete m_This;
}

const FOO& MCU::foo() {
return (*new Foo());
}

and then

FOO foo = mcu.foo(); // creates a copy

Now the destructor of the foo object will be called. This destructor, in
turn, is going to invoke

delete m_This;

which will call the destructor for the original. Here is what I do not
understand:

a) Why is m_This a void*? It appears that in this case

delete m_This;

will _not_ invoke the destructor of the original.

b) Why are you not running into an infinite loop? After all, the destructor
of the original, once invoked, has to execute

delete m_This;

too.

c) Could it be that (a) and (b) are related, i.e., do you run into an
infinite loop if m_This had type foo*? If that is the case, then with the

delete m_This;

statement in the destructor of the copy foo, you are likely invoking
undefined behavior because of a type mismatch of the actual object and what
you are telling the compiler [5.3.5/3].


Best

Kai-Uwe Bux

mc

10/9/2008 10:36:00 PM

0

Sorry for being opaque but I have no idea what top-posting is....


Ian Collins

10/9/2008 10:43:00 PM

0

mc wrote:
> Sorry for being opaque but I have no idea what top-posting is....
>
>
Ever heard of Google?

--
Ian Collins