Skip to main content
GameDev.net gamedev.net
🔒 Locked

Complex Classes, Streams and Inheritance (fun!)

Started by grekster Jul 31, 2005 at 6:15 PM 7 replies 1.4k views
Original Post
grekster
grekster
Just as a little side project ive been working on my own stream class. Basically it works like this cStream MyStream; MyStream << MyInt << MyFloat << MyDouble; MyStream >> MyInt >> MyFloat >> MyDouble; Its FIFO and works well for basic data types/class. At the heart of it are two templated operator overloads for << and >> Example:

template<class T>
cStream& operator<<(T& rhs)
{
		//Get data size and check available
		//space
	int iSpaceToAdd = 0;
	int iSize = sizeof(rhs);

	if(this->bDynamicSize == true && this->iMemoryLeft < iSize)
	{
		this->ResizeStream(iSize);
	}
	if(this->iMemoryLeft >= iSize)
	{
			//Stream big enough, copy data in.
		this->CopyDataBlockToStream((&rhs),iSize);
	}
	//Return a reference to ourselves	
	return(*this);
}

To extend the class further ive been toying with the idea of having more complex classes (e.g. ones which have memory on the heap) working with the stream. To do this I thought about haveing a Serialise/Deserialise (for want of better names) function for the class which gets passed a reference to the stream. Then in this function the class adds all its internal data into the stream and the deserialise function will retrieve it. This works well enough but I wanted to to be able to use the same syntax as adding simple types to the stream as more complex ones e.g. MyStream << MyInt << MyComplexClass; To accomplish this I created a base class (cStreamSerialiseBase) with virtual Serialise/Deserialise methods which can then be overloaded by the inheriting class. Then I created an overloaded << and >> function which takes a reference to a cStreamSerialiseBase class e.g.

cStream& operator<<(cStreamSerialiseBase& Class)
{
	Class.Serialise((*this));
	return(*this);
}

Unfortuantly this doesnt work. The class just calls the original templated << and >> instead of the one above. Im sure there is a way to do this but I cant think of another way to acheive what I want. If anyone has any help they can offer Id be very grateful:) Cheers!
Quote: Original post by BosskIn Soviet Russia, you STFU WITH THOSE LAME JOKES!
rKallmeyer
rKallmeyer
teamplate <>cStream& operator<<(cStreamSerialiseBase& Class){	Class.Serialise((*this));	return(*this);}


google "template specialization" for a description


grekster
grekster
Thanks for replying but it still doesnt work. I think I may have made a bad job of explaining myself originally. What I want is to have a class say:

class MyComplexClass : public cStreamSerialiseBase

and then later have:

MyComplexClass MCC;

MyStream << MCC;

and have that call the cStreamSerialiseBase method. Maybe its just not possible:S
Quote: Original post by BosskIn Soviet Russia, you STFU WITH THOSE LAME JOKES!
Enigma
Enigma
#include <iostream>#include <boost/type_traits.hpp>class Base{	public:};class Derived	:	public Base{};class Other{};class Stream{};template < bool serialise, typename TYPE >struct Output{	Stream & operator()(Stream & s, TYPE const & t);};template < typename TYPE >struct Output< true, TYPE >{	Stream & operator()(Stream & s, TYPE const & t)	{		std::cout << "serialise t\n";		return s;	}};template < typename TYPE >struct Output< false, TYPE >{	Stream & operator()(Stream & s, TYPE const & t)	{		std::cout << "output t\n";		return s;	}};template < typename TYPE>Stream & operator<<(Stream & s, TYPE const & t){	return Output< boost::is_base_and_derived< Base, TYPE >::value, TYPE >()(s, t);}int main(){	Stream s;	Derived d;	Other o;	s << o;	s << d;}

There might be an easier way - my template metaprogramming is a little rusty.

Enigma
grekster
grekster
Thanks alot Enigma :) Dont have a chance to look at the example properly im off to bed now (it is after 1:30am after all), Ill have a look through tomorrow. It does look very complex at first glance though!

If anyone else has any other ideas im all ears:)

Quote: Original post by BosskIn Soviet Russia, you STFU WITH THOSE LAME JOKES!
grekster
grekster
I think im nearly there but I dont know how to convert between my templated type and my base class. At the moment im doing this:

template<>void Func<true,class T>::func(cStream& s, T& rhs){	cStreamSerialiseBase* p = static_cast<cStreamSerialiseBase*>(rhs);	s.InsertionS(p);}


but I just get:

error C2440: 'static_cast' : cannot convert from 'T' to 'cStreamSerialiseBase *'

My code basically follows Enigma's but there is nothing in his post that idicates a solution to my problem:S

Once again any Help would be much appreciated!

Quote: Original post by BosskIn Soviet Russia, you STFU WITH THOSE LAME JOKES!
Enigma
Enigma
Quote:
Original post by grekster
My code basically follows Enigma's but there is nothing in his post that idicates a solution to my problem:S


Sure there is. Just build on it something like:
#include <iostream>#include <iterator>#include <string>#include <vector>#include <boost/type_traits.hpp>class Stream{	public:		std::vector< unsigned char >::iterator begin();		std::vector< unsigned char >::const_iterator begin() const;		template < typename TYPE >		Stream & copy(TYPE const & t);		std::vector< unsigned char >::iterator end();		std::vector< unsigned char >::const_iterator end() const;	private:		void copyDataBlockToStream(unsigned char const * data, unsigned int bytes);		void resizeStream(unsigned int bytes);		std::vector< unsigned char > data_;};std::vector< unsigned char >::iterator Stream::begin(){	return data_.begin();}std::vector< unsigned char >::const_iterator Stream::begin() const{	return data_.begin();}template < typename TYPE >Stream & Stream::copy(TYPE const & t){	//Get data size and check available	//space	unsigned int size = sizeof(TYPE);	unsigned int memoryLeft = data_.capacity() - data_.size();	if(/*dynamicSize && */memoryLeft < size)	{		resizeStream(size);	}//	if(memoryLeft >= size)	{			//Stream big enough, copy data in.		copyDataBlockToStream(reinterpret_cast< unsigned char const * >(&t), size);	}	//Return a reference to the stream	return *this;}std::vector< unsigned char >::iterator Stream::end(){	return data_.end();}std::vector< unsigned char >::const_iterator Stream::end() const{	return data_.end();}void Stream::copyDataBlockToStream(unsigned char const * data, unsigned int bytes){	std::copy(data, data + bytes, std::back_inserter(data_));}void Stream::resizeStream(unsigned int){	// don't do anything, vector will resize itself}class Serialisable{	private:		Stream & serialise(Stream & s) const;};template < bool serialise, typename TYPE >struct Output{	Stream & operator()(Stream & s, TYPE const & t);};template < typename TYPE >struct Output< true, TYPE >{	Stream & operator()(Stream & s, TYPE const & t)	{		t.serialise(s);		return s;	}};template < typename TYPE >struct Output< false, TYPE >{	Stream & operator()(Stream & s, TYPE const & t)	{		return s.copy(t);	}};template < typename TYPE>Stream & operator<<(Stream & s, TYPE const & t){	return Output< boost::is_base_and_derived< Serialisable, TYPE >::value, TYPE >()(s, t);}class SimpleSerialisableInteger{	public:		int integer;};class SimpleSerialisableCharArray{	public:		char charArray[5];};class SerialisableIntegerPointer	:	public Serialisable{	public:		Stream & serialise(Stream & stream) const;		int * integer;};class SerialisableString	:	public Serialisable{	public:		Stream & serialise(Stream & stream) const;		std::string string;};class ErroneouslyImplementedSerialisableDoublePointer	:	public Serialisable{	public:		double * integer;};Stream & SerialisableIntegerPointer::serialise(Stream & stream) const{	stream << *integer;	return stream;}Stream & SerialisableString::serialise(Stream & stream) const{	stream << string.size();	for (std::string::const_iterator character = string.begin(), charactersEnd = string.end(); character != charactersEnd; ++character)	{		stream << *character;	}	return stream;}int main(){	SimpleSerialisableInteger ssi;	SimpleSerialisableCharArray ssca;	SerialisableIntegerPointer sip;	SerialisableString ss;	ErroneouslyImplementedSerialisableDoublePointer eisdp;	ssi.integer = 7;	char text[] = "hello";	std::copy(text, text + 5, ssca.charArray);	int anInteger = 42;	sip.integer = &anInteger	ss.string = "Am I serialising correctly?";	Stream s;	s << ssi << ssca << sip << ss;// << eisdp;	std::copy(s.begin(), s.end(), std::ostream_iterator< char >(std::cout, " "));	std::cout << '\n';	std::copy(s.begin(), s.end(), std::ostream_iterator< int >(std::cout, " "));}

I've made a very simple stream class here which just writes into a vector. I copied your old operator<< code into the copy member function and adjusted it based on the fact that vectors always have room and therefore don't need resizing.

You'll also note that that class Serialisable has no virtual members. You might want to use a pure virtual serialise function to enforce implementation in subclasses, but by making it a non-virtual private function you can achieve a similar effect without requiring run-time dispatch. Trying to output a subclass of Serialisable which does not override the serialisable function will result in a compile-time error complaining that Serialise::serialise is private. ErroneouslyImplementedSerialisableDoublePointer demonstrates this in the example above.

Enigma
Nemesis2k2
Nemesis2k2
I haven't read your post or the subsequent replies in great detail (it's late, and I'm tired), but why don't you drop the template idea and just write a pair of nonmember stream operators (<< and >>, I call them stream operators in this context) for each class you want to be able to serialize? This way you have total control on a per-class basis, which is often what you really want when serialising an object. It's pretty simple to implement, and it gets the job done.
grekster
grekster
@Enigma: There must me a bug somewhere else in my code but when i try to do t.serialise() i get lots of linker errors

main.obj : error LNK2019: unresolved external symbol "public: void __thiscall Func<0,int>::func(class cStream &,int &)" (?func@?$Func@$0A@H@@QAEXAAVcStream@@AAH@Z) referenced in function "public: class cStream & __thiscall cStream::operator<<<int>(int &)" (??$?6H@cStream@@QAEAAV0@AAH@Z)main.obj : error LNK2019: unresolved external symbol "public: void __thiscall Func<1,class cTest>::func(class cStream &,class cTest &)" (?func@?$Func@$00VcTest@@@@QAEXAAVcStream@@AAVcTest@@@Z) referenced in function "public: class cStream & __thiscall cStream::operator<<<class cTest>(class cTest &)" (??$?6VcTest@@@cStream@@QAEAAV0@AAVcTest@@@Z)main.obj : error LNK2019: unresolved external symbol "public: void __thiscall Func<0,struct tagRECT>::func(class cStream &,struct tagRECT &)" (?func@?$Func@$0A@UtagRECT@@@@QAEXAAVcStream@@AAUtagRECT@@@Z) referenced in function "public: class cStream & __thiscall cStream::operator<<<struct tagRECT>(struct tagRECT &)" (??$?6UtagRECT@@@cStream@@QAEAAV0@AAUtagRECT@@@Z)main.obj : error LNK2019: unresolved external symbol "public: void __thiscall Func<0,double>::func(class cStream &,double &)" (?func@?$Func@$0A@N@@QAEXAAVcStream@@AAN@Z) referenced in function "public: class cStream & __thiscall cStream::operator<<<double>(double &)" (??$?6N@cStream@@QAEAAV0@AAN@Z)Debug/AniFileGenerator.exe : fatal error LNK1120: 4 unresolved externals


im going to go through all the code and see where ive went wrong. Thnaks for the indepth example you game me:)

@Nemesis2k2: Sounds like a very easy solution, wish I had thought of it originally:)
Quote: Original post by BosskIn Soviet Russia, you STFU WITH THOSE LAME JOKES!

Topic Locked

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

Sign in to reply to this topic.