specifying interface without definition?

J

Jacek Dziedzic

Hello!

Suppose I'm writing a library to write a binary representation
of some data to a stream. Let's say that to make it extensible
in the future I'm preceding the actual data with some info
like the number of fields, total length, etc. that make up
some kind of header, assuming the actual data may change
over time.

I was hoping to implement a 'class datafile' that would
represent the whole datafile, a 'class record' which would
consist of a header and the actual data. I want the 'record'
class to have a member to write it to a stream, like

record::write(ostream &out) {
// write record number
// write other stuff like sizeof(actual_data)
// write actual_data
}

I was aiming for a design that would allow the class
actual_data to vary, ie. NOT be defined in the library
I'm writing, but be defined in the program that uses
the library. Then I would write programs that would
define their own actual_data class and use the library
to store records into a datafile.

To make the library independent of actual_data I
have decided to only store a pointer to it, so the
record class would look like this
class record {
// some header data
actual_data *content;
}

Now, it turns out that if actual_data is non-POD then
I must provide some means to serialize it, like overloading
<< and >> or providing some save_to_bytes()/restore_from_bytes()
members. These would be used in record::write during
'write actual_data'. Of course these would have to be supplied
by the program using the library, as they depend on the precise
content of actual_data.

The problem is -- how do I convince the library that I can
call these functions using -> on the pointer to actual_data?
Obviously the library doesn't know what actual_data looks like...

So is there any way to tell the compiler "OK, I have a class
called actual_data that has some fields you don't know in
advance, so I can't give you a definition yet, but it definitely
has some get/put methods so you'll know how to store and restore
it?"

It would also be useful if actual_data could pass some info
to the library, like a 'my_size()' member function, but the
same problem occurs.

So how do I tell the compiler that I have a class that supports
some interface without revealing the whole class definition?

thanks in advance,
- J.
 
M

mlimber

Jacek said:
Hello!

Suppose I'm writing a library to write a binary representation
of some data to a stream. Let's say that to make it extensible
in the future I'm preceding the actual data with some info
like the number of fields, total length, etc. that make up
some kind of header, assuming the actual data may change
over time.

I was hoping to implement a 'class datafile' that would
represent the whole datafile, a 'class record' which would
consist of a header and the actual data. I want the 'record'
class to have a member to write it to a stream, like

record::write(ostream &out) {
// write record number
// write other stuff like sizeof(actual_data)
// write actual_data
}

I was aiming for a design that would allow the class
actual_data to vary, ie. NOT be defined in the library
I'm writing, but be defined in the program that uses
the library. Then I would write programs that would
define their own actual_data class and use the library
to store records into a datafile.

To make the library independent of actual_data I
have decided to only store a pointer to it, so the
record class would look like this
class record {
// some header data
actual_data *content;
}

Now, it turns out that if actual_data is non-POD then
I must provide some means to serialize it, like overloading
<< and >> or providing some save_to_bytes()/restore_from_bytes()
members. These would be used in record::write during
'write actual_data'. Of course these would have to be supplied
by the program using the library, as they depend on the precise
content of actual_data.

The problem is -- how do I convince the library that I can
call these functions using -> on the pointer to actual_data?
Obviously the library doesn't know what actual_data looks like...

So is there any way to tell the compiler "OK, I have a class
called actual_data that has some fields you don't know in
advance, so I can't give you a definition yet, but it definitely
has some get/put methods so you'll know how to store and restore
it?"

It would also be useful if actual_data could pass some info
to the library, like a 'my_size()' member function, but the
same problem occurs.

So how do I tell the compiler that I have a class that supports
some interface without revealing the whole class definition?

thanks in advance,
- J.

This is a classic case for templates. Your actual_data class can be a
template parameter to the datafile and/or record class.

#include <ostream>
#include <string>

template< class ActualData >
class DataFile
{
void SerializeTo( std::eek:stream& os )
{
os << actualData_;
}
// ...
private:
ActualData actualData_;
};

struct MyData
{
std::string name;
int id;
};

std::eek:stream operator<<( std::eek:stream& os, const MyData& data )
{
os << data.name << ' ' << data.id << std::endl;
return os;
};

void Foo()
{
DataFile<MyData> myDataFile;
// ...
myDataFile.SerializeTo( std::cout );
}

Cheers! --M
 
H

Heinz Ozwirk

....
I was aiming for a design that would allow the class
actual_data to vary, ie. NOT be defined in the library
I'm writing, but be defined in the program that uses
the library. Then I would write programs that would
define their own actual_data class and use the library
to store records into a datafile.

To make the library independent of actual_data I
have decided to only store a pointer to it, so the
record class would look like this
class record {
// some header data
actual_data *content;
}

Now, it turns out that if actual_data is non-POD then
I must provide some means to serialize it, like overloading
<< and >> or providing some save_to_bytes()/restore_from_bytes()
members. These would be used in record::write during
'write actual_data'. Of course these would have to be supplied
by the program using the library, as they depend on the precise
content of actual_data.

The problem is -- how do I convince the library that I can
call these functions using -> on the pointer to actual_data?
Obviously the library doesn't know what actual_data looks like...

That's (also) what inheritance has been invented for. In your library you
can define some class, that defines all methods your library needs to call.
But you do not provide an implementation of those methods. Instead you
declare them as pure virtual functions. The only function, which you should
implement, is an empty, virtual destructor. You define this class in a
header file, which is used both in your library and the programs using the
library.

If your library only needs to read and write binary data from/to a stream,
such an interface yould look like

class AbstractData
{
public:
virtual ~AbstractData() {}
virtual int read(istream& stream, char* buffer, size_t bytesToRead)
= 0;
virtual int write(istream& stream, char* buffer, size_t
bytesWritten) = 0;
};

A program using your library can define its own class derived from
AbstractData. It must implement all pure virtual methods of its base class,
otherwise it would not be possible to create an instance of that class. Any
decent compiler will take care of that. Such an implementation could look
like this (I'll leave the actual implementation as an exercise :)

class ConcretData: public AbstractData
{
int read(istream& stream, char* buffer, size_t bytesToRead);
int write(istream& stream, char* buffer, size_t bytesWritten);
// ...
// More stuff, that is not directly used by your library
};

If you like it, you can repeat "virtual" for all functions inherrited from
AbstractBase, but you don't have to. Once a function has been declared
virtual it will remaon so in all derived classes. That's also why there
should be an empty, virtual destructor in your base class. Even though there
is no data, that needs to be destructed, the virtual destructor allows you
to delete an instance of any derived class through a pointer to your
abstract base.

HTH
Heinz
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,432
Messages
2,571,680
Members
48,796
Latest member
Greg L.

Latest Threads

Top