Click here to Skip to main content
15,881,248 members
Please Sign up or sign in to vote.
5.00/5 (4 votes)
See more:
We're using home-spun smart pointers since shared_ptr wouldn't suffice. We created strong pointer and weak pointer classes, and const and non-const versions of each. The strong pointers to the same object are reference counted and weak pointers to that same object aren't counted and they don't keep the strong pointer alive but use an IsValid() function to see if the ref count for the strong pointer is above 0. It seems to have been working fine for years.

Recently, an insidious bug presented itself. After days of debugging I finally noticed that the new thing being created had the same address as the old thing.

Objects in this project have two layers, the GUI and the non-GUI. The GUI object had a const weak pointer to the non-GUI object parallel to it. When we were done with the GUI, we marked it for deletion and reset the strong pointer for the non-GUI object, freeing the memory. The bad thing was that weak pointer (on the GUI), though not valid, still held the value of non-GUI's address.

When a new GUI was needed soon thereafter, the new GUI was created and it created a new non-GUI object which happened to be the same address as the old non-GUI object. Meanwhile, the old GUI hadn't quite been removed yet.

In this project, Non-GUI objects have weak pointers to the GUI object as well. I believe what happened is that when the old GUI was finally removed, it set the parallel non-GUI object's weak pointer (to itself) to NULL. Since the old non-GUI and the new non-GUI had the same address the new one lost its pointer to the new GUI and the program crashed. I suppose the IsValid() returned true because a new one with the same address had been added to the managed objects list. In the 10 years I've been using C++, I never really thought about the possibility that the same address could come back soon for use. I'm up for a comment on this.

My question is, what would be the best design for fixing this?

An easy way to fix it was to remove the GUI immediately instead of marking it for deletion. Before that I tried manually setting the weak pointer to null but it was const and I saw that a lot of things would have to change, including the document, which is designed to be const so that inadvertent changes aren't made to it, so that didn't seem like the right fix. Maybe in the remove process the GUI shouldn't set the pointer to itself on the non-GUI to NULL. I'm not sure.

I wonder if anyone with experience in memory management knows how these things should work and if there is a way I can improve my smart pointers for future clients. I always want to ask for help when I feel myself resisting a change that could affect a lot of things. Maybe you see something I've missed or know a good trick. Maybe you can tell me whether marking a thing for deletion is an OK practice or not. It seemed like a good way to decouple things at the time, and a way to keep intelligence in the correct class. The cause of the bug is so difficult to find that something must be done to prevent it in the future.
Posted

Using smart pointers and weak pointers is generally the design of last resort in C++. While I'm not saying that this applies to your case but I've lost count of the number of times that teams I've worked in have tied themselves up in knots with object lifetime, tried to solve them with smart pointers and then found themselves tied up in even bigger knots.

I suppose the first question to ask is why do you need some form of smart pointer to manage the interaction between the external representation (the GUI) and the internal representation (your business objects or whatever buzz words fit)? Generally you'd aim to have an object model consisting of your internal representation and then create specific GUI objects as and when they're needed to edit or display the internal objects. In simple cases you can do this editing with automatic objects and not have to worry about object lifetime and memory allocation.

Now this model implies that you've got something like:

- user selects internal object to edit
- application creates external object with a reference to the internal object to let the user edit the internal object
- user uses external object to make their changes
- changes are committed back to the internal object

Validation of data and all that good stuff can be integrated into this melange to taste. The first problem that arises is that in some systems something else can change the internal object while it's being edited. There are several ways to cope with this, including:

- let the user edit the internal object through the external one but discard all their changes at the end and say "ooops." This can actually work quite well when the probability of collision is low.

- everytime the user changes something in the external object ask the internal object whether anything's changed and if it has tell the user. This can work okay but the constant yattering from the external object to the internal object is a pain in the bum to code.

- when something changes in the internal object have it call out to any external objects it's created (using an observer) to tell them what's changed.

In all these methods you don't actually need to manually manage object lifetimes that much. However, if your internal object can be swept into the dustbin of history at any time you're going to have to implement some form of callback to tell the external object it's all gone horribly wrong and abandon everything. Or you have to use something like a reference counted pointer to make sure that the external object keeps the internal object alive to avoid a heavy crash. And then the internal object needs a weak pointer to the GUI so it can find it if it needs to tell it something's changed.

Which is, interestingly, the other way around from the way you described things. So one thing I'd look at doing is thinking about swapping around who owns who - the internal, application/business objects are the things that use the external, UI objects to edit themselves. Two of the last four companies I've worked for have had this problem (one was monitoring hot swappable drives, the app crashed when a drive was swapped and the management dialogue for that drive was active, not a good thing to happen in demos) and it's been fixed by flipping the object ownership on it's head.

Having said all that...

In your implementation it sounds like the relationship between the smart and weak pointer is a bit bent. A weak pointer can't just keep the value of the managed pointer in case the same address is reused, as you've found.

One way you could get around this problem is to add a new level of indirection between the shared/weak pointer and the pointer it's managing. While I've seen this done a variety of ways the simplest way I've seen (although a bit cumbersome) of doing this is to split off the pointer and a couple of reference counts into their own object:

template<typename T>
class locked_ptr
{
    public:
        locked_ptr( T*ptr ) :
            ptr_( ptr ), strong_references_( 1 ), weak_references_( 0 )
        {
        }

        void strong_lock()
        {
            strong_references_ ++;
        }

        bool strong_unlock()
        {
            if( --strong_references_ == 0 )
            {
                delete ptr_; ptr_ = 0;
            }

            return return !weak_references_ && !strong_references_;
        }

        void weak_lock()
        {
            weak_references_++;
        }

        bool weak_unlock()
        {
            weak_references_--;
            return !weak_references_ && !strong_references_;
        }

    private:
        T *ptr_;
        size_t strong_references_;
        size_t weak_references_;
};


(This is just the management stuff, the actual pointer access is left out). Then you can implement your shared pointer and weak pointer in terms of objects of this class. The idea is that the shared and weak pointer keep a pointer to a locked_ptr and delete it when an unlock function returns true. When the pointer is copied one of the lock functions is called. Note that the strong count manages the pointed to object's lifetime and both the strong and weak counts manage the locked_ptr's lifetime.

Anyway, sorry this answer was so long and rambling, hope some of it helps you get back on track.

Cheers,

Ash
 
Share this answer
 
Comments
Espen Harlinn 17-Jan-11 9:42am    
5+ Thorough answer, I would still recommend using boost's shared_ptr - it has been thoroughly tested and used in "real world" scenarios. There is also the Signals2 library that can be used to implement element monitoring functionality. I don't think he is ready for a redesign ...
Aescleal 17-Jan-11 10:11am    
The questioner said he couldn't use shared_ptr (the standard, tr1 and boosts are essentially identical) so I thought it was worth exploring some alternatives.
Take a long look at boost.org ...
Here is a few pointers:
http://www.boost.org/doc/libs/1_45_0/libs/utility/checked_delete.html[^] - sounds familiar?

Smart Pointers:
http://www.boost.org/doc/libs/1_45_0/libs/smart_ptr/smart_ptr.htm[^]

And generally boost provides excellent functionality and implementations that has seen "real world" usage.

Regards
Espen Harlinn
 
Share this answer
 
Comments
Sergey Alexandrovich Kryukov 17-Jan-11 9:02am    
Looks like a precise find - 5.
Espen Harlinn 17-Jan-11 9:06am    
Well, thanks SAKryukov :)
Aescleal 17-Jan-11 9:29am    
checked_delete isn't what the questioner's after - he's got a problem with an aliased pointer at runtime and not trying to delete an incomplete type at compile time.
Espen Harlinn 17-Jan-11 10:12am    
I was more or less referring to "The C++ Standard allows, in 5.3.5/5, pointers to incomplete class types to be deleted with a delete-expression. When the class has a non-trivial destructor, or a class-specific operator delete, the behavior is undefined. Some compilers issue a warning when an incomplete type is deleted, but unfortunately, not all do, and programmers sometimes ignore or disable warnings." - as a hint to what might be causing his problems. I should probably have been more explissit :)
In case you find shared_ptr weak_ptr from boost too "expensive" (they cost two pointers and one object containing two counters and a functor containing at least a function pointer, and typically also vtable pointer... something many people recommending boost don't mention), in developing yourself weak and strong pointers remember the following:

- smart pointer must have two pointers, one pointing to the pointed object and one pointing to a pair of counters (a "strong counter" and a "totalref counter".)
- strong pointers act on both couters, weak only on the totalref.
- when the "strong counter" goes to zero, the pointed object must be deleted.
- when the "totalref counter" goes to zero the counter pair must itself be deleted.
- ALL POINTERS (no matter if strong or weak) must behave as null in respect to access, dereference, comparisons etc. if the counter pair they are pointing has the strong counter set to zero. The object they point to is anymore existent, and the system may have been used those address to do anything else.
 
Share this answer
 

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900