views:

1157

answers:

3

Hi,

I have a std::queue that is wrapped as a templated class to make a thread-safe queue. I have two versions of this class: one that stores value types, one that stores pointer types.

For the pointer type, I'm having trouble deleting the elements of the queue on destruction. The reason is that I don't know a way to remove the items from the queue safely.

This reference http://www.cplusplus.com/reference/stl/queue/ states (vacuously, so I guess it doesn't actually STATE it) that the only way to remove elements from the queue is to call pop(). The reference also says that pop() calls the destructor for the item.

Well, this causes problems with my pointer types because they may or may not actually point to aggregates. If one of them points to an aggregate, they all will, but because the wrapper is templated, there is no guarantee which type (aggregated or non-aggregated) we are dealing with.

So when pop() calls the destructor, what happens? How do I ensure that everything is being removed and the memory deallocation properly?

Lastly, my solution is using an older version of GCC for ARM9. I don't have control over this. I understand that there are libraries that have smart pointers and containers that would assist here, but they are off-limits for me.

Thanks.

+8  A: 

Pointers themselves don't actually have destructors, so calling pop() on a queue containing a pointer won't call the destructor of the object your pointer points to.

Greg Hewgill
Thanks for the answer. This is what I was wondering, if it wasn't clear from my rambling question.
San Jacinto
Greg, I appreciate the fast answer and still vote up, but I changed my acceptance to Neil's answer because it has a little more thorough explanation of what's happening.
San Jacinto
I agree. Although the line of thinking is very good :D. The first tell-tale sign should be that a pointer doesn't have a destructor.
Hassan Syed
@Hassan no, it doesn't have a destructor, but the compiler can keep track of the types and therefore can call delete if we are talking about a pointing type. I didn't think it was likely, but what ACTUALLY happens is what matters.
San Jacinto
Compilers call `T::~T()` when you destroy a _smart_ pointer to a `T`, e.g. std::auto_ptr<T> or `std::tr1::shared_ptr<T>`. That's pretty much the definition of _smart_ pointers. It doesn't do such things for raw pointers, if only to maintain C compatibility.
MSalters
+4  A: 

Online sources are worth what you pay for them - get a proper reference like Josuttis's book. pop() does not "call the destructor" - it simply removes an element from the queue adaptor's underlying representation (by default a std::deque) by calling pop_front() on it. If the thing being popped has a destructor, it will be used when the popped object goes out of scope, but the queue class has nothing to do with it.

anon
Thx for clarifying, I had trouble wrapping my head around that statement: "This calls the removed element's destructor"
Emile Vrijdags
assuming the popped object was inserted into a smart pointer of some form.
Hassan Syed
Thanks for the info and the book link.
San Jacinto
Assuming nothing. If the object has a destructor it will be called (but not by pop()), if not, well, there's no destructor to call.
anon
destructors only get called on items automatically -- when they go out of scope -- if they have smart pointers..... am I missing something here ... ?
Hassan Syed
Is it really fair to say "not by pop()"? That's like saying main does nothing, because main only calls other functions. But main does everything.
GMan
Yes - I don't think you understand how smart pointers work. But that;s a subject too big for an SO comment.
anon
@GMan Fair enough. But "calling the destructor" is usually reserved in C++ parlance for when you actually do call it - for example when you use placement new. In this case, all pop does is remove an element from a deque.
anon
WTF ? , an object can be new'd or stack allocated, the former will not automatically release on OOS without a smart ptr -- your totality does not hold.
Hassan Syed
and the guy is talking about both pointers and value types ... in a c++ context. I would say it is fair to assume they might be new'd.
Hassan Syed
@Hassan My totality is just fine. Please re-read what I wrote.
anon
@Neil In my program, I call pop(). The very next line in my code, let's pretend, is i++; Has the destructor been called by the time i++ happens? I'm not asking if the pop() function called the destructor, I'm asking if by virtue of pop() being called if it's been removed.
San Jacinto
@Hassan: containers always, always destroy their contained objects when they're removed from the container. Always. Doesn't matter whether the contained object is a user-defined type, an integer, a pointer, or a smart pointer. It's destroyed (or nothing happens, if it has no destructor). The only question then, is what destroying the popped object actually does. For a pointer, it does nothing. A `new`d object cannot ever be the contents of a container, whether "inserted into a smart pointer" or not. A pointer to it might be the contents of a container, but that's a completely different thing.
Steve Jessop
@GMan Just to clarify if I have a deque of strings and do d.pop_front() am I calling the destructor for the string I pop? Well yes, but most C++ programmers don't think of it that way. I think :-)
anon
To re=phrase Neils comments. When you pop an object (from a standard container) the destructor will be called on the object poped. A pointer object does NOT have a destructor (what it points at may have a destructor but that is not what is stored in the queue). So a pointer object will simply be reoved from the queue.
Martin York
The popped object could be the copy used in the queue, or the copy returned. In either case only a boost smart pointer would fix your totality.
Hassan Syed
-1: For being correct but obtuse and unclear.
Martin York
@San Jacinto A good question! I suggest starting another question and posting the exact code for at least the function that you are asking about. Otherwise there is no clear answer.
anon
@steve you can always dereference the new'd pointer to put the item into the queue :/ and this is a common idiom :D
Hassan Syed
@Martin Assuming you are talking to me, the corrective for an obtuse and unclear answer is to post an accute and transparent one.
anon
@Martin, wish I could change to (-1)
Hassan Syed
I'm tempted to start shouting things like DON'T PANIC MR MAINWARING! Really, there are three (?) issues here and all should be addressed in separate questions.
anon
Thanks, Neil and Steve. Your comments (and answer) combined have cleared it up for me.
San Jacinto
@Hassan: that doesn't put the object in the queue, it puts a copy of it in the queue.
Steve Jessop
@Hassan: what do you mean, "The popped object could be the copy used in the queue, or the copy returned"? `pop()` doesn't return anything.
Steve Jessop
@Neil: I've just realised that this business about what calls what, is probably the reason that everyone (including me) ends up writing all their documentation in the passive voice, and it comes out with a Flesch-Kincaid equivalent older than Methuselah. "the object is removed from the underlying container before pop() returns, with the consequence that its destructor (if any) is called before pop() returns" vs. "pop() removes the object from the underlying container, hence calls its destructor (if any)".
Steve Jessop
@Neil: Already done by "Greg Hewgill"
Martin York
@Steve I don't see what is wrong with "removes the object from the underlying container" - the thought of reproducing an explanation of destructor semantics in each case is frightening. And in fact this is the way the C++ Standard is written.
anon
@Martin It is not accepted SO practice to vote people down for being correct, no matter how difficult you may find their answers.
anon
A: 

"How do I ensure that everything is being removed and the memory deallocation properly?"

If you absolutely have to store pointers in your queue, and you want them to be automatically freed when they're poped, then instead of a queue of pointers, you need a queue of objects which store a pointer, and delete it in their destructor. You could for example use a queue of shared_ptr. shared_ptr isn't in the standard library, but it's part of TR1 and is widely available.

Otherwise, it's the responsibility of the caller to delete the object:

T *off = q.front();
q.pop();
delete off;

The summary is that containers of pointers to dynamically allocated objects are a bit awkward. If you can design your program so that containers store copies of your objects, instead of pointers to dynamic objects, then do so. Failing that, you're responsible for resource ownership, not the container. STL containers know nothing about ownership, they just copy and destroy their value_type. Copying and destroying pointers does nothing to the objects they point to.

Steve Jessop
I thought of this but rejected it because I don't know the future uses of the templated class. I don't want to give raw access to the queue for users b/c of thread safety concerns, and I can't guarantee that the users would use the idea you just mentioned, so I decided against it.
San Jacinto
If your class is using its template parameter as the value type for a hidden queue, then it should probably do what containers do: if the user allocates anything, then the user has to arrange to eventually free it (for instance by using a smart pointer as the value type). For instance if it's an event queue, and the user insists on using a plain pointer, then they could perhaps free the object in the event handler. But it's not the container's problem, and IMO you shouldn't try to write containers which treat pointers specially.
Steve Jessop
... admittedly, this just punts the problem on to the next guy. But there are good reasons not to confuse containers with ownership, not least of which is that someone might want to use your container to store pointers, without giving you ownership (because they point to static data or something). I assume boost::ptr_deque can be used as the underlying container for a queue, so you could have two template parameters (like queue), and let the user use that if does what they want.
Steve Jessop
Normally I'd agree with this sound logic, but for my immediate use, the removal of elements happens in anomalous situations. In my class, the items are removed only when the destructor is called, which means there was likely a large failure for this particular subsystem. I'm essentially using this queue as the middleman for producer/consumer, and if there have been items that are produced, they need to be disposed of properly and right away if there has been an error this big. Otherwise, the driver (consumer) thread will continue to operate and things will go badly in the hardware sense.
San Jacinto