‘this’ pointer disasterFiled Under: Weekly Tuesday Dose of goodness
- Private Heap Disaster
- Debugging Disasters
- ‘this’ pointer disaster
- Inheritance disaster
Hi all,
This week I’ll be talking about ‘this‘ pointer. Yeah this little pointer that points to self. Unfortunately, this article won’t really apply to Java or C# users. Nevertheless, you guys may also wanna read this article to appreciate the kind of things we go through writing C++.
So how is it possible that a ‘this’ pointer can lead to a disaster?
Read on….
Introduction
Now, we’re all very familiar with ‘this’ pointer, so this article is likely to be short. Yes? Well, I’ll try to until my explanation gets way too long.
As we know, in C++, some of us if not most of us use a smart pointer container of some implementation, for example boost’s shared_ptr<T>. In StridesLib, we use cSmartPtr<T> which is similar to the boost’ variant with some additional important features.
We will not go into that now.
Technically, we know that shared_ptr<T> is always to be declared as a local attribute or a composite attribute (I used to use the word ’stack variable’ because if the object is instantiated on the stack ,this whole object will be on the stack as well).
We only use a pointer of a pointer container for double pointer operations.
But in any case, here’re a few ways you can use smart pointer containers. I’m just going to promote cSmartPtr<T> a little here.
cSmartPtr< cVector4 > vectorPtr = new cVector4(1,2,3); //to instantiate cSmartPtr< cVector4> v2 = vectorPtr; //to initialize with and share cSmartPtr< cVector4> v3; //to declare an empty null pointer container v3 = v2; //to assign with and share v3->x = 2.0f; //dereference pointer and access a member
Smart Pointer Containers are not true pointers!
That’s right. As you can see in the examples above, all 3 pointer containers, vectorPtr, v2 and v3 are all local instances (aka stack instances).
If that’s the case, how can it be instantiated with a new pointer? Normally, this isn’t allowed by the compiler does it? Yes!
In that case, what’s the magic involved here?
Answer…. the big 3!!
Surprise, surprise! How can the big 3 be involved here? Well, basically only 2 of the 3 bigs are used as well as other operator overrides.
What are the bigs used here?
1) Copy Constructor
2) Canonical Copy Assignment Operator
What are the others used?
3) Parameterized Constructor (with a T*)
4) operator = (T* )
5) operator == (T* )
6) operator != (T *)
7) operator ! ()
operator * even
There’re more but I can’t afford to explain every single one of them. Basically, when T is used, the rest of the problems are dealt with by the compiler. As you can see in the set of overrides, it allows the smart pointer container to implicitly take in a true pointer so much so that it looks hidden from the user.
While this is a good feature overall. It sometimes can hide dangers that might stump even the most experienced C++ programmer for a moment.
Now, let’s just realise that cSmartPtr< cVector4 > vectorPtr; is not a true pointer.
A true pointer in this case would be cVector4* vectorPtr; instead.
When disaster strikes…
Here comes the ugly part.
Remember that a smart pointer container can take in a true pointer implicitly?
Here’s the catch - a this pointer is a true pointer, a smart pointer container is not a true pointer. Let’s take a look at this example:
class A {
private:
B bObj;
public:
A();
};
A::A()
{
bObj(this); //this pointer is guaranteed to be valid at this point
}
//in classB.h
//forward declaration
class A;
class B {
public:
B(cSmartPtr<A> parmPtr);
};
//so happens when I instantiate an object of type A?
cSmartPtr< A > aObj = new A();
//1) aObj ctor called, instantiates composite object bObj with (this) pointer
//2) this pointer is interpreted by B's ctor implicitly and is initialized as a cSmartPtr<A> parmPtr instead
Therefore, at this juncture, there’re two smart pointer containers with different context pointing at the same pointer. Here’s how it looks like: (let’s say new A() returns a 0×00001FFF)
aObj —-> 0×00001FFF , ref count = 1
parmPtr —-> 0×00001FFF, ref count = 1
As you can see, there’s absolutely no communication between aObj and parmPtr. So if anyone of them sets themselves to null, thus reducing the reference count to 0, 0×00001FFF will be deleted with size of A. Thus the situation will be:
aObj —-> 0×00000000 , ref count = 0
parmPtr —-> 0×00001FFF, ref count = 1 //parmPtr is now pointing to a dangling pointer!
Can parmPtr escape?
Well, let’s see.
1) Dereference -> Crash due to dangling pointer
2) Check against null -> Return incorrect results
3) Set to null -> Crash since parmPtr will attempt to delete a pointer that has already been deleted
4) Pass it onto another container -> Won’t crash immediately, but the consequence merely delays the inevitable from happening since one of the containers will be 0 and the same thing will happen in 3).
Conclusion
Therefore, it’s impossible to recover from such a flop. In this case, the programmer must understand that this pointer is not a smart pointer container object. He/she must not take it for granted and pass things in just like that.
However, that being said - the implementation of the smart pointer can play a part as well. That is, storing a unique hash map of memory addresses as the key, the smart pointer container as the object. The smart pointer container will register itself the first time for memory protection. Then subsequent smart pointer containers who are not directly related by context will access this hash map to see if there’s already such a container that manages this memory address.
If there’s such a container, then it’ll retrieve that container and increment its reference count, thus joining up.
This is how it should look like even when the pointer containers are not, i repeat, not passed around.
aObj —-> 0×00001FFF , ref count = 2
parmPtr —-> 0×00001FFF, ref count = 2
The problem is - the cost of using a hash map can be expensive. Thus leading to an increased cost of using smart pointer containers afterall.
Bottomline is, programmers need to know their pointer basics, whether they use smart pointer containers or not. They can’t depend on these containers entirely without arming themselves with the right knowledge. Otherwise, if a disaster of such a kind strikes, nobody can save you.
Not even testing tools.
Hope that this helps! Have a great weekend ahead!
Signing off,
Jeremy
- Permalink
- Admin
- 29 Jun 2010 10:00 AM
- Comments (2)
July 1st, 2010 at 5:26 pm
The root cause for this problem can be understand as the implicit construction of cSmartPtr during constructor of class A.
A good fix to the problem will be adding keyword ‘explicit’ on the cSmartPtr’s parameterized constructor.
July 1st, 2010 at 5:35 pm
What if there’re both:
cSmartPtr( cSmartPtr parmPtr )
and
cSmartPtr< T* parmPtr )
Then how?