Currently I have something like this:

class FileWritter{
    ofstream file;
public:
    FileWritter(string filename,A_Class a){//A_Class is class which has defined the >> and the << operators
        file.open(filename.c_str());
        file<<a;
    }
    ~FileWritter(){
        file.close();
    }
};

what I want is to have something like this:

template <class AbstractClass>
class FileWritter{
    ofstream file;
    public:
    FileWritter(string fileName, AbstractClass ab){
        file.open(fileName.c_str());
        file<<ab;
    }
    ~FileWritter(){
        file.close();
    }
};

in this case, the compiller shows this error:

use of class template requires template argument list

my question is how can I use an template class with FileWritter?

Recommended Answers

All 9 Replies

What you are asking about is called "type erasure" it is basically the combination of templating and inheriting. Unfortunately C++ does not support type erasure (as far as I know). As such you have to choose between templates and inheritance. Here is an example of each solution:

Inheritance based:

class AbstractClass
{// private:
string exampleData;
public:
AbstractClass();
virtual string getData()const{return exampleData;}
virtual void setData(string &o){exampleData=o;}
};

ostream &operator<<(ostream &left,const AbstractClass &right)//this should work for any class that inherits from AbstractClass
{
    left<<right.getData();
}

class FileWriter//there is only one 'T' in writer
{// private:
    ostream file;
    public:
    FileWriter(string fileName, const AbstractClass &ab){//MUST BE A REFERENCE OR POINTER!
        file.open(fileName.c_str());
        file<<ab;
    }
    ~FileWriter(){
        file.close();
    }
};

And here is the template based solution:

template <class AbstractClass>
class FileWriter
{// private:
    ostream file;
    public:
    FileWriter(string fileName, const AbstractClass &ab){//Abstract class must implement ostream&<<AbstractClass&
        file.open(fileName.c_str());
        file<<ab;
    }
    ~FileWriter(){
        file.close();
    }
};

Here are example uses of each:

Inheritance method:

class myClass:public AbstractClass
{};//normally this would have something in it. And it can modify get/setData to change its output behaviour
myClass mc;
FileWriter fw("myFile.txt",mc);

Template method:

class myClass
{};//normally this would have something in it. And it can modify the following function to change its output behaviour
ostream &operator>>(ostream &left, myClass &right)
{
    left<<"This is a myClass";//for example
}
myClass mc;
FileWriter fw("myFile.txt",mc);

my FileWriter.cpp file consists of the following:

#include "FileWritter.h"

FileWritter::FileWritter(string fileName,const AbstractType& at)
{
    this->file.open(fileName.c_str());
    file<<(at);
}

FileWritter::~FileWritter(void)
{
    this->file.close();
}

I'm getting the following errors:

error C4430: missing type specifier - int assumed
error C2143: syntax error : missing ',' before '&'
error C2509: '{ctor}' : member function not declared in 'FileWritter'
error C2955: 'FileWritter' : use of class template requires template argument list
error C2509: '{dtor}' : member function not declared in 'FileWritter'

I'm using the class in the following way:

DefinedClass* DefinedClassPtr = new DefinedClass();
new FileWritter<DefinedClass>("fis.sg",*DefinedClassPtr);

I don't understand why I receive the errors

I think you are still getting the errors because you are still trying to use polymorphism. If you use the const AbstractType & (inheritance) technique then you don't even need the <DefinedClass> at all. Also you don't need a pointer! Just this is how you use it:

DefinedClass dc=new DefinedClass;
new FileWritter("filename.file",dc);//it gets referenced automatically!

Also it seems as though in your definition of FileWritter (in FileWriter.h) you didnt define the functions you are trying to use. So here is a translation of your errors:

C4430: You didnt provide a type inside the <> section of your class... we're gonna assume you want ints. (fix this by just not using templates... they aren't correct here!)

C2143: Since it is still waiting on the <> section it gets confused when you throw in an & sign. Same fix as before.

C2509: What are you trying to pull? You didn't tell us you were making a constructor for FileWritter. You can't make an outside of class definition of something not already in the class. Example:

class Test
{
    public:
    void myFunctionA();
};

//This works, because the compiler knows that there is a myFunctionA inside Test
void Test::myFunctionA()
{
    //dosomething
}

//This doesn't work, because the compiler wasn't told in advance that this would be here!
void Test::myFunctionB()
{
    //dosomething
}

C2955: Again... when are you going to give us the <>... we have no idea what we are dealing with!

C2509: Again... you never told us you were going to shove a destructor into FileWritter... we want advance notice.

All of your problems can be solved by removing the genericity of your FileWritter class and then fixing up the implementation a bit.

First of all, if you don't need the AbstractClass beyond the scope of the constructor, then all you need to do is make the constructor templated:

class FileWriter {
    ofstream file;
  public:

    template <class AbstractClass>
    FileWriter(const string& fileName, const AbstractClass& ab) {
        // must turn on io-exceptions, as the only way to signal error from the constructor.
        file.exceptions( ofstream::failbit | ofstream::badbit );
        file.open( fileName.c_str() );
        file << ab;
    }

    ~FileWriter() {
        file.close();
    }
};

Second thing is, I don't understand why you want to have a constructor that does the serialization of an object. It doesn't make sense. The only way to use this class is to create a temporary FileWriter to serialize one object, and then immediately destroy that temporary writer object. In other words, it will always boil down to this function:

template <class AbstractClass>
void writeToFile(const string& fileName, const AbstractClass& ab) {
  ofstream file;
  file.exceptions( ofstream::failbit | ofstream::badbit );
  file.open( fileName.c_str() );
  file << ab;
}; // file will be destroyed here, which closes the file.

Why do you need a class to do this? At the very least, your FileWriter class should have the constructor and object serialization split into two functions to make it more usable:

class FileWriter {
    ofstream file;
  public:

    FileWriter(const string& fileName) : file(fileName.c_str()) { };

    template <class AbstractClass>
    void write(const AbstractClass& ab) {
        file << ab;
    }

    // convert to bool to know if the writer is in a good state.
    operator bool() const { return file; };

//    ~FileWriter() { file.close(); }  // <-- No need for a destructor, ofstream is a RAII class.
};

And finally, this line:

new FileWritter<DefinedClass>("fis.sg",*DefinedClassPtr);

This is a memory-leak. You allocate some memory (for the FileWritter object) and you don't store the resulting pointer, meaning that there is no way that you could possibly destroy this object (i.e., call the delete operator on the pointer to it). And because your FileWritter object holds a file resource, this is not only a memory-leak, it is also a resource-leak, which is worse, the file will remain open indefinitely (I'm not even sure if killing the process closes to file). Instead, you should simply have:

FileWritter<DefinedClass>("fis.sg",*DefinedClassPtr);  // create a temporary, will be destroyed immediately after construction.

or this (if you use the templated constructor solution):

FileWriter("fis.sg",*DefinedClassPtr);

or this (if you use the function template solution);

writeToFile("fis.sg",*DefinedClassPtr);

or this (if you use the split-up FileWriter class):

FileWriter out("fis.sg");
if( sg ) 
  sg.write(*DefinedClassPtr);

I think you need to rethink your concept a bit before you waste time trying to make a flawed concept work. Writing a good serialization scheme can be a bit tricky, but it is immensely useful, especially if done correctly. So, take the time to think it through a bit more, and also, look at other examples such as Boost.Serialization, my own, and a few others out there.

What Mike_2K said, plus to re-emphasize that writing good serialization/deserialization (orthogonal) methods into abstract classes is very difficult. I have done such for software used to run most semiconductor fabs today (MES frameworks), so I know some of the problems you face. The frameworks I developed would work with either TCL or XML wire format messages. The sender would serialize itself to the appropriate form, and the receiver would automatically detect the format and de-serialize it accordingly into a faithful representation of the object sent. The object could be highly complex, consisting of many sub-objects of various types. At times, the serialized string can be 10+MB in length, and contain recursive (circular) references where one object contains a reference to another previously encountered in the hierarchy. That was one of the more difficult problems to solve. Fortunately, both TCL and XML have constructs to deal with that! :-)

FWIW I wrote the original code for that about 20 years ago - prototyping it in Smalltalk, and then implementing it in C++.

Sorry for the long awaited response. The reason why I need this solution is this: my prorgam needs to handle a few different types of files(some just need to be read, some might need just writing to, and some need both), my current solution is to have each type of file represented by a class, all these classes inherit from a common interface(the interface offers the readFile and writeFile functions), for each class type having specified a definition for the functions and a function to handle specific file-type data, so from my opinion it looks sloppy(not to mention inneficient by having repeated code). The template solution would give me the advantage of having a single class of under 20 rows of code that can be used for all file types.

thank you mike for the generous explanation, but I don't manage to understand a few things from your response:

>  // must turn on io-exceptions, as the only way to signal error from the constructor.
> file.exceptions( ofstream::failbit | ofstream::badbit );
and
> //    ~FileWriter() { file.close(); }  // <-- No need for a destructor, ofstream is a RAII class.

how do these rows work exactly?
PS:for the second row, is this behaviour documented anywhere?

What do you mean by "file type"? If you mean different file formats, then there is no way that you can avoid having to write a special load/save function for each format. But if you mean files with different types of content but saved with the same overall format (an "extensible" format), then what you are describing is indeed what we call a serialization library.

When serializing data, there are fundamentally two things that need to be coded: what do you want to put in the file; and, how are you going to put it there. The first is "content" and the second is "format". A good serialization library would generally separate those two tasks. This is because the type of object that you want to save / load determines the content of the file, which is generally independent of the format. And because every type of object (class) that you might want to save to a file will have different content, there is generally no way to avoid having to manually specify the content to be saved/loaded for each class. However, what you can avoid is the dependency to the format of the destination file.

That's where I have issues with your solution. You are relying on a line like file << ab; to save your object (ab) to the file, this means that your class will have to implement a << operator for IO-streams (and a corresponding >> operator for loading) which means that this function defines both the content and the format. The use of the template that takes an "AbstractClass" argument is just giving you the illusion of "genericity", when in reality, it is just wrapping calls to very non-generic (non-reusable, non-flexible) functions, which are the << and >> operators on IO-streams for the particular class with which the template is instantiated. That's the problem. The presence of the template is misleading you into thinking that there is some sort of generic quality to that code, when there is none. You said it yourself:

The template solution would give me the advantage of having a single class of under 20 rows of code that can be used for all file types.

You're right that you get a single class that can be used for all file types. However, it is useless. Saying "can be used" does not mean "is useful". In my previous post, I managed to deconstruct your code to the point where it was clear that your FileWriter class was nothing more than a useless wrapper around an std::ofstream object. That's a red-flag. It means your class is probably not achieving anything useful, only an illusion.

What you need, and what is usually done in most serialization libraries that I have ever come across, is a simple mechanism for specifying to content to be saved for each class (independent of format), and a set of polymorphic classes to handle the formatting work (take specified content and turn it into a valid format, or vice versa). The canonical example is the Boost.Serialization library, their documentation page has a pretty lengthy discussion of how the library is designed/used. The point is, for each class that you create, you must provide a "serialize" function that essentially contains little more than a list of all the data (or data-members) you want to put into the "file" (or archive), and then, the archive classes (analog to IO-streams) define how things are put into the "file" (or serialized stream). This is truly generic because on the archive side, all you need to define is a set of functions to write properly formatted basic data like primitive types and strings, and on the custom-class side, all you need to define is a list of data members that need to be saved/loaded. This makes any class that has specified that content serializable under any archive of any format that exists. That's where you get the genericity from: from separating the tasks of content specification and formatting.

how do these rows work exactly?

The first line refers to a classic issue: how do you signal an error from a constructor?
There is essentially no other choice to report an error from a constructor but to throw an exception.

The second line tells the IO-stream object to throw an exception upon failure instead of simply going into a "bad-state" as they usually do. See the reference documentation.

The third line comes from the fact that if you don't define a destructor for a class, the compiler will generate one for you, and that destructor will just destroy each of the data members of the class. In this case, the only data member is the file-stream object, which will automatically close the file if it is still open by the time it reaches the destructor of that file-stream object. This is a bit harder to find explicitly said in the reference documentation (you have to look into the standard document), but I assure you, it is guaranteed.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.