3
votes

Suppose there's a vector of Items

vector<Item*> items; //{item1, item2, item3}

Then, in other part of the code,

items[1]->suicide();

where the suicide function is:

void Item::suicide()
{
   delete this;
}

What is items vector size and how it's arrangement now? It is okay to do this?

Edit (may I ask an additional question?): If the desired arrangement of the output is {item1, item3}, size is 2, and no dangling pointer, how to do it in a self-destructing way (from the item2 itself)?

Edit 2 : Thanks for all the answers! Awesome. So I finally decided and found the way to do it from outside of the object because it was a bad practice and unnecessarily complicated

7
See related questions with the tag [self-destruction]: stackoverflow.com/questions/tagged/…Daniel Daranas
Best thing you can do is to rethink your problem. Do you really need suicidal objects? Can you refactor it so that the object tells the caller whether it needs to be destroyed and then the caller deletes/removes from the vector?David Rodríguez - dribeas

7 Answers

6
votes

What is items vector size and how it's arrangement now? The same. The function call does not change the vector contents nor size at all. It just frees the memory the pointer is pointing to.

Is it okay to do this? More precisely: Is it legal C++? Yes. Is it good style programming? No. Let me elaborate on the latter:

  • There should be a separation of concerns: Who's responsible for memory management? The container or the user of the class Item or the class Item itself?

  • Typically the container or user should do that, because he knows what's going on.

  • What's the way to do that? Memory management in modern and safe C++ code is mostly done using smart pointers like std::shared_ptr and std::unique_ptr and containers like std::vector and std::map.

  • If the class Item is a value type (that means you can simply copy it and it has no polymorphic behavior in terms of virtual functions), then just use std::vector<Item> instead for your code. Destructors will be called automatically as soon as an element is removed from the container. The container does it for you.

  • If the class Item has polymorphic behavior and can be used as a base class, then use std::vector<std::unique_ptr<Item>> or std::vector<std::shrared_ptr<Item>> instead and prefer the std::unique_ptr solution, because it adds less overhead. As soon as you stop referring to an object, it will be deleted automatically by the destructor of the smart pointer you are using. You (almost) don't need to worry about memory leaks anymore.

  • The only way you can produce memory leaks is that you have objects that contain std::shared_ptrs that refer to each other in cyclic way. Using std::unique_ptrs can prevent this kind of trouble. Another way out are std::weak_ptrs.

Bottom line: Don't provide a function suicide(). Instead put the responsibility solely into the hands of the calling code. Use standard library containers and smart pointers to manage your memory.

Edit: Concerning the question in your edit. Just write

items.erase( items.begin() + 1 );

This will work for all types: std::vector of values or pointers. You can find a good documentation of std::vector and the C++ Standard library here.

5
votes

The suicide member doesn't change the vector. So the vector contains an element which is an invalid pointer and formally you can't do much with an invalid pointer, even copying or comparing it is undefined behavior. So anything which access it, included vector resizing, is an UB.

While any access if formally UB, there is a good chance that your implementation doesn't behave strangely as long as you don't dereference the pointer -- the rationale for making any access UB is machines where loading an invalid pointer in a register can trap and while x86 is part of them, I don't know of widespread OS working in this mode.

1
votes

Your suicide function does not to anything with the Items vector, let alone it knows anything about it. So from the vector's point of view: nothing changes when you call the function and it's ok to do that.

1
votes

The pointer will become invalid, that's all. You should be careful to not to delete it again. vector<Item*> will NOT delete elements on its own.

0
votes

The vector has no idea what you're doing elsewhere in the code, so it'll keep a dangling pointer to the original Item.

"Is it OK do do that?"

After suiciding the item, you should adjust the vector manually to no longer keep that dangling pointer.

0
votes

That's ok in case of vector of pointers as vector will not call Item's destructor. But you have to somehow know which pointers are still valid.

If you are storing Items in vector by value, calling Item's destructor is not ok. When vector will be destroyed or cleared, it will call item's destructor again, causing application crash.

0
votes

Wow, It seems that you make a typing error. It should be vector<Item *> Items; As to your question:

  1. the size of vector Items does not change, means that, it still has three pointers to Item objects.
  2. the content of the vector does not change: before Items[1]->suicide() , Items[0] = 0x000001, Items[1] = 0x000005, Items[2] = 0x000009 after Items[1]->suicide(), Items[0] = 0x000001, Items[1] = 0x000005, Items[2] = 0x000009
  3. It's definitely OKAY to do so.

Besides, the vector will manage its memory automatically, when you push some elems into it while the capacity is not enough, it will reallocate a larger space, BUT, when you pop some elems or erase some elems, it will never give the redundant memory to the system.

The code of Items[1]->sucide() just return the memory held or pointed by the pointer Items[1] to the system, it will do nothing on the pointer itself, Items[1] still holds the same value, but point an unsafe area.

Unexpectedly, you have made a Design Pattern, suppose you want to design a class and you ONLY allow allocate any object of it on the Heap, you may write the following code:

class MustOnHeap
{
   private:
      ~MustOnHeap() { // ...}
   public:
      void suicide() { delete this;}

};

Then ,the class can not have any instance that is alloacated on the stack, because the destructor is private, and the compiler must arrange the calling of destructor when the object walk out its scope. Therefor, you must allocate them on the heap, MustOnHeap* p = new MustOnHeap; and then destroy it explicitly : p->suicide();