Laurent Claessens
Laurent Claessens

Reputation: 695

task list in C++ (vector with more than one type)

My aim is to fill a list of task; each will be an object containing the description of the task. Let'say there will be only two type of tasks : file copy and repertory copy.

Since a vector cannot contain more than one type of objects, I though to create a generic task class and two classes that inheritate from that one.

Here is the code :

#include <iostream>
#include <deque>
#include <string>

using namespace std;

class GenericTask{
    public :
        string config;
    GenericTask(string s){
        config=s;
    }
    void run(){
       cout<<"Running generic task" <<endl;
    }
};

class FileCopyTask : public GenericTask{
    public: 
        string filename;
    FileCopyTask(string cf,string fn):GenericTask(cf)
    {
        filename=fn;
    }
    void run(){
        cout<<"file :"<<filename<<endl;
    }
};

class RepertoryCopyTask : public GenericTask{
    public: 
        string repname;
    RepertoryCopyTask(string cf,string rn):GenericTask(cf)
    {
        repname=rn;
    }
    void run(){
        cout<<"repertory : "<<repname<<endl;
    }
};

void run_next(deque<GenericTask> &task_list){
    task_list.front().run();
    task_list.pop_front();
}


int main()
{
    RepertoryCopyTask rtask("configuration","/home");
    FileCopyTask ftask( "configutation","gile.tex" );

    deque<GenericTask> task_list;
    task_list.push_back(rtask);
    task_list.push_back(ftask);
    run_next(task_list);
}

As it, it does not work because run_next expect a GenericTask and both rtask and ftask are treated as generic.

How should I do ?

I already tried to add template here and there, but ultimately it does not work because I need to know the type inside the deque before to "extract" something.

Can I consider this as an answer ?

Upvotes: 0

Views: 1221

Answers (4)

umut
umut

Reputation: 82

I made some changes in your source. Defined your base fn as virtual and stored objects with pointers. You can check it below.

#include <iostream>
#include <deque>
#include <string>

using namespace std;

class GenericTask{
    public :
        string config;
    GenericTask(string s){
        config=s;
    }
    virtual void run(){
       cout<<"Running generic task" <<endl;
    }
};

class FileCopyTask : public GenericTask{
    public: 
        string filename;
    FileCopyTask(string cf,string fn):GenericTask(cf)
    {
        filename=fn;
    }
    void run(){
        cout<<"file :"<<filename<<endl;
    }
};

class RepertoryCopyTask : public GenericTask{
    public: 
        string repname;
    RepertoryCopyTask(string cf,string rn):GenericTask(cf)
    {
        repname=rn;
    }
    void run(){
        cout<<"repertory : "<<repname<<endl;
    }
};

void run_next(deque<GenericTask*> &task_list){
    task_list.front()->run();
    task_list.pop_front();
}


int main()
{
    RepertoryCopyTask* rtask = new RepertoryCopyTask("configuration","/home");
    FileCopyTask* ftask = new FileCopyTask( "configutation","gile.tex" );

    deque<GenericTask*> task_list;
    task_list.push_back(ftask);
    task_list.push_back(rtask);
    run_next(task_list);
}

Upvotes: 1

m8mble
m8mble

Reputation: 1545

A classical case of virtual. The run methods need to be declared virtual s.t. you are actually calling RepertoryCopyTask::run() on an object of type GenericTask.

When done correctly,

FileCopyTask t("a", "b");
GenericTask & g = t;
g.run();

will call FileCopyTask::run instead of GenericTask::run (which it would in the original question).

When you did this, you can't store your FileCopyTasks and RepertoryCopyTask in a contaianer for GenericTask. This is because they might even have different size. To get around this, you should store unique_ptrs for them in some container, i.e.

std::vector<std::unique_ptr<GenericTask> > tasks;

This would be the correct way of solving your problem.

Upvotes: -1

CinCout
CinCout

Reputation: 9619

Why not create objects of FileCopyTask and RepertoryCopyTask and save them as pointers to GenericTask? This way you can leverage the power of runtime polymorphism.

Like this:

int main()
{
    std::unique_ptr<GenericTask> ftask = std::make_unique<FileCopyTask>("configutation","gile.tex");
    std::unique_ptr<GenericTask> rtask = std::make_unique<FileCopyTask>("configuration","/home");
    ...
}

void run_next(deque<std::unique_ptr<GenericTask> > &task_list)
{
    ....
}

Also, do not forget to mark the run() method in class GenericTask as virtual. Also provide a virtual destructor.

Upvotes: 1

utnapistim
utnapistim

Reputation: 27365

How should I do ?

Consider these steps:

  • define GenericTask as a base class (add virtual destructor, make void run virtual)
  • override the run function in derived classes
  • store elements in the queue as std::unique_ptr, instead of "by value" to avoid the slicing problem.

I already tried to add template here and there, but ultimately it does not work because I need to know the type inside the deque before to "extract" something.

You can add a boost::variant as the value, allowing the storage of unrelated types.

Can I consider this [this=answer proposing boost::any as value type] as an answer ?

Yes. boost::variant would be similar (the difference is that boost::any supports setting any value; boost::variant only supports values of the types provided as variant arguments).

Upvotes: 0

Related Questions