Jump to content
  • Advertisement
Sign in to follow this  
grekster

Complex Classes, Streams and Inheritance (fun!)

This topic is 4825 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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!

Share this post


Link to post
Share on other sites
Advertisement

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



google "template specialization" for a description


Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
#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

Share this post


Link to post
Share on other sites
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:)

Share this post


Link to post
Share on other sites
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!

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
@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:)

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!