Skip to main content
gamedev.net
🔒 Locked

A singleton implementation (pointer arithmetic)

Started by IFooBar Jan 7, 2003 at 10:52 AM 36 replies 2.3k views
Original Post
IFooBar
IFooBar
Hello Ive seen this implementation of a singleton and it has some wierd use of pointer arithmetic or something. Could you guys explain to me what this means exactly, here it is
  
int iOffset = (int)(type*)1 - (int)(CSingleton<type>*)(type*)1;
        m_pSingleton = (type*)((int)this + iOffset);
  
m_pSingleton is static of course, and the above math is in the constructor, type is the a typename. CSingleton is the class name. thanks
:::Al:::
[Triple Buffer V2.0] - Resource leak imminent. Memory status: Fragile
[size=2]aliak.net
null_pointer
null_pointer
quote:
Could you guys explain to me what this means exactly


It means that the programmer who wrote it should be shot down in the streets like a mad dog.

quote:
thanks


No problem.
Stoffel
Stoffel
That''s a pretty hacky way to get the exact offset to the ''type'' in the singleton template class. I suppose it''s necessary, but it looks pretty frightening to me. I''ve seen code like this before but I don''t remember where. I think it''s in anticipation of bypassing dynamic casting or maybe a ctor call--not sure.

Think Liberally.
bjogio
bjogio
I have seen this in the Singleton from the ogre source code.
Madness...
[ILTUOMONDOFUTURO]
IFooBar
IFooBar
that code is inside teh singletons ctor. It came with the NVIDIA SDK. this is the complete class they use

  
// Copyright (C) Scott Bilas, 2000.

// All rights reserved worldwide.

#include <cassert>

template <typename T> class Singleton
{
static T* ms_Singleton;

public:
Singleton( void )
{
assert( !ms_Singleton );
int offset = (int)(T*)1 - (int)(Singleton <T>*)(T*)1;
ms_Singleton = (T*)((int)this + offset);
}
virtual ~Singleton( void )
{ assert( ms_Singleton ); ms_Singleton = 0; }
static T& GetSingleton( void )
{ assert( ms_Singleton ); return ( *ms_Singleton ); }
static T* GetSingletonPtr( void )
{ return ( ms_Singleton ); }
};

template <typename T> T* Singleton <T>::ms_Singleton = 0;



:::Al:::
[Triple Buffer V2.0] - Resource leak imminent. Memory status: Fragile
[size=2]aliak.net
sjelkjd
sjelkjd
It''s also in game programming gems
civguy
civguy
Ok we all agree now that it''s been used in various places and it''s ugly, but can anyone throughoutly explain what''s the meaning of that code? I don''t understand it, and what Stoffel said wasn''t enough.. And what benefit does it give over simpler singleton accessors like these:

  
static Base& getInstance() {
static std::auto_ptr<Base*> pInstance = new Child();
return *pInstance;
}

static Base& getInstance() {
static Base instance;
return instance;
}
Let alone the one in Loki (which doesn''t have any extraordinary pointer arithmetic either).
SabreMan
SabreMan
quote:
Original post by civguy
Ok we all agree now that it's been used in various places and it's ugly, but can anyone throughoutly explain what's the meaning of that code?

It's calculating the offset position of the derived subpart (CSingleton<type> ) of the class within the class (type), and then setting m_pSingleton to point at that subpart (this + offset). I answered this same question a few weeks ago, but I can't find it. The assumptions made in the calculation are not guaranteed to work, as they rely on reinterpret_casts. In particular, they are likely to break with MI. A better way to do the same thing is to use a static_cast to get m_pSingleton to point at the correct subpart of the object, and that can handle MI.
quote:

what benefit does it give over simpler singleton accessors like these:

Without reminding myself of the full implications of each technique, I couldn't say for sure. My immediate reaction is that there is no benefit.

[edited by - SabreMan on January 7, 2003 1:55:22 PM]
null_pointer
null_pointer

  # include <cassert>
 
template <class object> class singleton
{
static object* p;
 
public:
singleton ()
{
assert (not p);
p = dynamic_cast <object*> (this);
assert (p);
}
 
virtual ~singleton ()
{
assert (p);
p = 0;
}
 
static object& instance () { assert (p); return *p; }
static object* instance_pointer () { return p; }
};
 
template <class object> object* singleton <object>::p = 0;
 
class something : public singleton <something> {};
 
int main ()
{
}


There...I cannot stand messy code.

[edited by - null_pointer on January 7, 2003 2:32:12 PM]
civguy
civguy
Hmm, at first it was starting to make sense but then it blurred again. Offset was 0 in all my tests, and I tried MI too, also with virtual base classes. When Singleton<T> is constructed, the ''this'' in it''s constructor points to the memory where the object, i.e. T, is. So I can''t see how a non-zero offset to the address could do any good.

Maybe I''ll get it after I get some sleep..
IFooBar
IFooBar
quote:
There...I cannot stand messy code.


Compiles fine. but it dosnt work. Here you can try this with your implementation and it dosnt work like that

  
#include <iostream.h>
#include <conio.h>

template <class type>
class CSingleton
{
private:

static type* m_pSingleton;

protected:
CSingleton()
{
int iOffset = (int)(type*)1 - (int)(CSingleton<type>*)(type*)1;
m_pSingleton = (type*)((int)this + iOffset);
}

public:
virtual ~CSingleton()
{
m_pSingleton = 0;
}

static type* GetPtr()
{
return m_pSingleton;
}

static type& GetVal()
{
return *m_pSingleton;
}
};

template <class type>
type* CSingleton<type>::m_pSingleton = 0;


class CTemp : public CSingleton<CTemp>
{
public:
CTemp()
{
x = 5;
}
~CTemp()
{
x = 0;
}

int GetX() { return x; }
void SetX( int num ) { x = num; }

private:
int x;
};


class CTemp2 : public CSingleton<CTemp2>
{
public:
CTemp2()
{
x = 0.5f;
}
~CTemp2()
{
x = 0.0;
}

float GetY() { return x; }
void SetY( float num ) { x = num; }

private:
float x;
};


void main()
{
CTemp* tmp = new CTemp;

cout<<tmp->GetX()<<endl;
tmp->SetX( 3 );

CTemp* tmp2 = CTemp::GetPtr();

cout<< tmp2->GetX()<< endl;

tmp2->SetX( 7 );

CTemp tmp3 = CTemp::GetVal();
cout<< tmp3.GetX()<< endl;
////////////////////////////////////////

CTemp2* p = new CTemp2;

cout<<p->GetY()<<endl;
p->SetY( 0.3f );

CTemp2* p2 = CTemp2::GetPtr();

cout<< p2->GetY()<< endl;

p2->SetY( 0.7 );

CTemp2 p3 = CTemp2::GetVal();
cout<< p3.GetY()<< endl;
cout<< tmp2->GetX()<< endl;


delete tmp;
delete p;
}


The above code works fine, BUT when your singleton is used with it, it fails. So a dyname_cast dosnt work.



:::Al:::
[Triple Buffer V2.0] - Resource leak imminent. Memory status: Fragile
[size=2]aliak.net
RamboBones
RamboBones
Well as other people have quite clearly pointed out it simply calculates the offset of a variable inside the singleton class. I personaly think that it''s a hack that defeats the purpose of high level programming and basically breaks every form and rule of programming I live by. But then again it''s a damn neat hack...




OMG. It''s him again. Call the SHRINK!!!
rambo_bones@hotmail.com
WebSite: http://www.illicitdev.net/~rambobones
GlRott Page: http://www.glrott.tk
RamboBones
RamboBones
Dman I can''t believe that I reposted 3 times!!!
null_pointer
null_pointer
quote:
Hmm, at first it was starting to make sense but then it blurred again. Offset was 0 in all my tests, and I tried MI too, also with virtual base classes. When Singleton is constructed, the 'this' in it's constructor points to the memory where the object, i.e. T, is. So I can't see how a non-zero offset to the address could do any good.


It seems as if it is trying to be portable to other compilers through trying to figure out the alignment instead of relying on the standard. Offset may well be zero, but that seems like it was intended to be the most common case.

quote:
Compiles fine. but it dosnt work.


Apparently it is impossible to use dynamic_cast to cast from a base class pointer to a derived class pointer on days that begin with a "W".


  
// (insert my previous singleton example sans main ()...)

 
int main ()
{
something s; // assertion failed - p == 0 after dynamic_cast!

something::instance ();
}


Perhaps someone else can test it tomorrow...

[edited by - null_pointer on January 8, 2003 1:35:06 PM]
null_pointer
null_pointer
Oh, how stupid! Null, you colossal idiot! You should know that you cannot downcast to an object that has not yet been constructed.

Does anyone know of a perfectly legitimate way to get a pointer to an object that has not yet been constructed?
SabreMan
SabreMan
quote:
Original post by null_pointer
Apparently it is impossible to use dynamic_cast to cast from a base class pointer to a derived class pointer[...]

...when the derived class subpart of the object has not yet been constructed. I mentioned earlier in this thread that you can use static_cast. Try substituting that in for the dynamic_cast.
null_pointer
null_pointer
This should work fine now...


  // working code...

# include <cassert>
# include <ciso646> // Visual C++ 7.0- only

 
template <class object> class singleton
{
static object* p;
 
public:
singleton (object* pointer_to_object)
{
assert (not p);
this->p = pointer_to_object;
assert (p);
}
 
virtual ~singleton ()
{
assert (p);
p = 0;
}
 
static object& instance () { assert (p); return *p; }
static object* instance_pointer () { return p; }
};
 
template <class object> object* singleton <object>::p = 0;
 
class something : public singleton <something> { public: something () : singleton <something> (this) {} };
 
int main ()
{
something s;
something::instance ();
}
null_pointer
null_pointer
quote:
...when the derived class subpart of the object has not yet been constructed. I mentioned earlier in this thread that you can use static_cast. Try substituting that in for the dynamic_cast.


You cannot use static_cast to downcast.

Hmm, seems like I am not the only "idiot" here.
SabreMan
SabreMan
quote:
Original post by null_pointer
Hmm, seems like I am not the only "idiot" here.

No doubt you will be apologising for that remark when you have tried it.

quote:
C++ Standard 5.2.9 para 5
An rvalue of type "pointer to cv1 B", where B is a class type, can be converted to an rvalue of type "pointer to cv2 D", where D is a class derived from B, if a valid standard conversion from "pointer to D" to "pointer to B" exists, cv2 is the same cv-qualification as, or greater cv-qualification than, cv1, and B is not a virtual base class of D.

Obviously, it will not work with virtual base classes.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.