Advertisement

Weird C++ type conversion problem (HARD)...

Started by September 03, 2000 03:08 PM
74 comments, last by Dire.Wolf 24 years, 3 months ago
I am having on hell of a problem fleshing out my Handle template class. I have created a Handle template class and I would like it to support inheritance relationships: Ex:
        

template<class T>
class Handle
{
	public:
		friend class T2;
		
		// constructor

		explicit Handle(T* p) : ptr(p), 
		                        count(new long(1)) 
		{
		}

		// copy constructor

		Handle(const Handle<T>& p) throw() : ptr(p.ptr), 
		                                     count(p.count)
		{
			++*count;
		}

		// destructor

       ~Handle() throw ()
	    {
			release();
		}

		// assignment operator

		Handle<T>& operator=(const Handle<T>& p) throw()
		{
			if(this != &p)   // protect from self assignment

			{
				release();

				ptr   = p.ptr;
				count = p.count;
				
				++*count;
			}
		
			return *this;
		}

		// conversion operator, used for inheritance
		template<class T2> operator Handle<T2>()
		{
			return Handle<T2>(p);
		}

		// return a reference via the deferencing operator

		T& operator *() const throw()
		{
			return *ptr;
		}
		
		// return a pointer to the internal data

		T* operator ->() const throw()
		{
			return ptr;
		}
		
		// assign a new representation

		void Bind(T* p)
		{
			release();

			ptr   = p;
			count = new long(1);
		}

		// return the reference count

		long Count() const throw()
		{
			return *count;
		}

	private:
		// decrememnt the count var and perform check to determine

		// if internal data reps can be freed

		void release()
		{
			if(--*count == 0)
			{
				delete count;
				delete ptr;

				// make sure pointers are NULL'd

				count = 0;   
				ptr   = 0;
			}
		}		

		// point to rep and reference count var

		T     *ptr;
		long  *count;
};

namespace std
{
	using digitalfiends::Handle;

	template<class T> ostream& operator <<(ostream& os, const Handle<T>& rhs)
	{
		return os << *rhs;
	} 
}

class Animal
{
    public:
        virtual void Speak() { cout << "Animal" << endl;
};

class Dog : public Animal
{
    public:
        void Speak() { cout << "Dog" << endl;
};

class Cat : public Animal
{
    public:
        void Speak() { cout << "Cat" << endl;
};

int main()
{
    Handle<Animal>   myanimal(new Animal);
    Handle<Dog>      mydog(new Dog);

    myanimal = mydog;  // error: no binary '='


    return 0;
}

        
You can see by the conversion operator,
operator Handle 
, that this should work but alas, it doesn't. The problem I have with this is that in Bjarne Stroustrup's, "The C++ Programming Language - Special Edition", on page 350 he shows how to make a 'smart point' that understands inheritance/polymorphism. Unfortunately it doesn't seem to work. Anyone have any other ideas or approaches? BTW I'm using Visual C++ 6.0 SP4. Thanks in advance guys, - Dire Wolf direwolf@digitalfiends.com Edited by - Dire.Wolf on 9/3/00 3:09:20 PM Edited by - Dire.Wolf on 9/3/00 3:10:46 PM
[email=direwolf@digitalfiends.com]Dire Wolf[/email]
www.digitalfiends.com
What you need to do is define a template operator =, something like this (not tested though)

    template<class TOther>Handle<T>& operator=(const Handle<TOther>& p) throw ()    


Then you''ll be able to assign any types that are convertable. But I don''t think this is supported by VC. If I remember correctly, a template operator = and copy ctor wont stop the compiler generating one, so you''ll need the existing ones or it''ll all go pair shaped.

You could make your existing operator = simpler by increasing the ref count before you call release. If you do it that way you don''t need to check for self assignment.
Advertisement
Ok, I don''t have your answer, but here''s a lead. I had a similar problem when I made my own exception class heirarchy and made a template for wrapping the built in exceptions into my class heirarchy. It worked fine as long as all objects required the same inheiritance signiture (none / single / two bases etc...) but I found no way to make one template support differences in the inheiritance system. Let me give an example (off the top of my head), but FIRST I want to point out that you did not put T2 in your template specification. I expect you meant this:

template
class Handle
{
etc...
}

But that doesn''t solve your problem exactly.

What I did was this (for single inheiritance):
(NOTE - I use typename to help the compiler resolve potential ambiguity ... just read it as class)
// note .. due to templates this system may require one extra intermediate class but I haven''t tested it yet

// declaration
template
class Exception : public ObjectBaseType
{
};


YOUR SYSTEM will be more complicated, because you have about multiple permutations of inheiritances. Here''s the three main ones:

// declaration
template
HandleWithoutBase
{
// your stuff ... which I will help you fill in later after you respond to this post
};

// declaration
template
HandleWithInheiritance: public ObjectBaseType
{
// your stuff ... which I will help you fill in later after you respond to this post
};

tell me what you think ...
Fuck ... the mesasge board removed my template stuff (due to the angle brakets) I can''t post my solution until someone tells me how to embed code blocks in post. Thanks in advance.
quote:
I can''t post my solution until someone tells me how to embed code blocks in post


put it inside [ source ] [ / source ], but without the spaces. Or check the faq.
I just noticed that I left the friend class T2 declaration in my code sample...that was embarassing. I was trying to grant friendship to a member template which is in my new code but I forgot to remove it before posting.

What I''m trying to do now is this:

    template<class T2>Handle(const Handle<T2>& rhs);    


The problem with using this is that during a construction of a Handle with an instance of Handle the variable ''rhs'' doesn''t refer to the same type so you must use an accessor function to return a pointer to rhs reference count variable. Since this has to be a public function, anyone could gain access to the count pointer which could become dangerous (not to mention violating the idea of encapsulation).

So my new question is how to define friendship for a member template...

Thanks for the quick responses guys!

- Dire Wolf
direwolf@digitalfiends.com


[email=direwolf@digitalfiends.com]Dire Wolf[/email]
www.digitalfiends.com
Advertisement
How about:
templateclass Handle{...    Handle(const Handle&) ...    Handle& operator =(const Handle&) ......    operator Handle() ...} 


I''m just brainstorming here so there''s no guarantee it will work. As I see it the problem is that compiler cannot figure out how to convert Handle to const Handle&. So, we introduce baseT and would do something like Handle.
And as I''m writing I realize that Xai is right, ''cause if you say Handle the compiler will barf. So, you''d need a template class HandleNoBase.

Hmm... this really looks interesing, I''ll try to work it out and post it here when I''m done.
''twas me. Need to check my brain for parity errors - always forget to enter the username.
Well, in the first place, you use templates to generate different classes based on certain compile-time parameters. So always use a pointer to the base class (so the generated classes are of the same type) and the operations will work fine:


int main(int, char*)
{
Handle&ltAnimal> myanimal = new Animal;
Handle&ltAnimal> mydog = new Dog;

myanimal = mydog;
}



Yes, the templated operator= should solve that problem, along with the templated copy constructors. Don''t bother with conversion operators here, because they can be very irritating and tricky. VC doesn''t seem to handle all of the implicit conversions correctly. Also, make absolutely sure that you have virtual destructors in your classes when you use them with Handle&ltT>!

Oh, about the private thing, make a public UnBind function that returns the pointer after assigning its own pointer to NULL:


T* UnBind()
{
T* temp = ptr;

ptr = NULL;
delete count;
count = NULL;

return temp;
}



That function might come in handy for other tasks, too... I''m not sure what it''ll do to your reference counting, though. You use it like this:


// inside the templated operator =
this->ptr = rhs.UnBind();



BTW, the "cannot access private member" error comes up in the templated operator= but not the templated constructor - anyone know why? Probably just a bug in VC...

Happy Coding!


- null_pointer
Sabre Multimedia
Dire.Wolf, I think this is what you wanted. It only allows to "upcast" handles.

//
// I decided to delete the code that I posted here. It was
// ugly, it had severe limitations and I posted a better
// version below.
//

As you see it ain't pretty. I've tried to get rid of that second arg in the constructor but it involved two templates referencing each other - and compiler happily barfed up INTERNAL ERROR when it saw that . Unfortunately that bastardized constructor kinda defeats the whole point of having a smart pointer. If you can figure out a way to fix this - I'd really like to see your code.
I've also got a "downcast" version working (using 2 templates), but it's even uglier. If you want it - let me know and I'll post it.
quote:
By null_pointer
Don't bother with conversion operators here, because they can be very irritating and tricky. VC doesn't seem to handle all of the implicit conversions correctly.

Seem to work fine for me.

Anyway, Dire.Wolf, it's all yours.

Edited by - vladg on September 4, 2000 10:11:03 AM

Edited by - vladg on September 4, 2000 10:27:44 PM

This topic is closed to new replies.

Advertisement