I'm working on a program for an internship interview, and I came across a problem. The program consists of a base class, Document, which PdfDocument and WordDocument derive from. You can't (shouldn't be able to) make a Document by itself. Then I have a class called PrinterQueue which is just a linked list of Documents. You can pop a document from the front or add one to the back. I made a tiny node struct that allowed me to link them together via Node *next;
Anyways, in data structures class, our node class had T data; as a templated variable. Due to a virtual string Type() = 0; in Document, I can't make a Node of type Document because data is not a pointer. Also, if I change that to virtual string Type() {return"undefined";} I now have an error where if I print the contents of PrinterQueue, it says all the Types are undefined instead of .pdf or .doc which they actually should be.
So what is the right thing to do? I thought as an abstract data type, a linked list could link anything together but I guess not. If I change data to *data, and did delete data; in ~Node(), I got an error saying I deleted something that wasn't allocated. Any help would be appreciated.
> it says all the Types are undefined instead of .pdf or .doc which they actually should be.
A base class object behaves as a base class object.
You've got polymorphism when a base class pointer (o reference) refers to a derived object.
So your container should be std::list<Document*>
> I got an error saying I deleted something that wasn't allocated.
¿Did you allocate it? with new
You must deal with pointers or references to get polymorphic behavior. Storing a pdfDocument or a WordDocument in a Document variable would cause slicing - the part of the class that is unique to pdfDocument or WordDocument is effectively sliced off.
If I change data to *data, and did delete data; in ~Node(), I got an error saying I deleted something that wasn't allocated. Any help would be appreciated.
Make sure the memory is allocated by the linked list so that you can safely delete it (although using a smart pointer would be preferred to manual deletion.)
I do not see a need for any template classes or functions.
You have a node with a Document* and a node* as members.
I set it up so that the document (pdf or word) is allocated to the Document in the node.
For brevity, I have used the class names doc (abstract base), pdfDoc and wordDoc (both derived from doc).
// ctor to create a node with a pdf document
node( const pdfDoc& pdf_doc, node* Next ): pDoc(new pdfDoc(pdf_doc)), next(Next) {}
¿why did you do the clone method? You are coupling with all the derived classes, if you want another document type you'll need to add a method to node (¿?)
> I do not see a need for any template classes or functions.
Because the container logic does not depend on the elements. The code is already there, you just need to reuse it.
Thanks for all the help, especially fun2code. Sorry for the template confusion -- I was reusing a templated node I've used before, and did not realize I could use <Document*> as a template type. Anyways, the node is no longer an issue. When I have some time I need to run it through valgrind again and see if I have any memory leaks.
fun2code, I understand why you did two ctors for node -- one for pdf and one for word -- but I believe I could just pass in Document* instead and set pDoc to that.
@ne555. I used the clone method because I set up this queue for each node to own the document stored in it. A copy of the document will have to be made from the local instance of a document passed to the push_back().
Oddly, I included the clone() just to handle the copy ctor issue with the node structure, forgetting that I could also use it to provide a single "type neutral" ctor for the nodes. With this node ctor I no longer need to add new ctors for new document types:
Now I can make due with a single push_back() for the printerQueue class:
1 2 3 4 5 6 7 8 9 10 11 12 13
void printerQueue::push_back( const doc& r_doc )
{
if( back )
{
// the node ctor will construct the right document type via use of the virtual clone()
back->next = new node( r_doc, NULL );
back = back->next;
}
else
{
front = back = new node( r_doc, NULL );
}
}
Is that better? What were you thinking of?
@dem7w2: I am down to 1 ctor now.
If you were to pass in a Document* and just equate pDoc to it then the node would not "own" the document. The printerQueue would merely be a list of pointers to Documents allocated elsewhere. It could be done this way and it would be somewhat simpler too.
Besides, the extra dynamic allocation issue makes the problem more interesting.
@ne555:
Thanks. That was quite the oversight on my part, not using the clone() for the primary purpose that one would add the method for!
ne555 wrote:
> I do not see a need for any template classes or functions.
Because the container logic does not depend on the elements. The code is already there, you just need to reuse it.
I think I get this. If I create a template class for the node, I would be able to use the queue for any type which provides a virtual clone(), not just Document types.
Kind off, but prefer to simply use the STL than implement the container myself.
With the `cloningNode' the memory management is solved, so one less thing to worry about :)
By the way myQueue< doc, cloningNode<doc> > Q;
The template parameters are dependent (the nodes will contain the type of the data),
so to avoid misuse you could
1 2 3 4 5 6 7 8 9 10 11 12 13 14
template<class T>
struct cloningNode{
typedef T data_type;
//...
};
// N = node type
template<class N>
class myQueue
{
public:
typedeftypename N::data_type data_type; // the `T'
//...
};
And instantiate it as myQueue< cloningNode<doc> > Q;