0

I need to create a queue of different class objects (These classes are not related). I found a solution as follows:

Create a base class and use polymorphism. Here is how I implemented it,

class Task {
    public:
    virtual void operator()() {
        printf("should not be called\n");    
    } 
};

class TaskRPCB : public Task {
    private:
        int x;
        // other varibles
        std::function<void(int)> func;
    public:
        TaskRPCB(std::function<void(int)>&f , int x) {
            this->func = f;
            this->x = x;
        }
        void operator()() {
            printf("TaskRPCB function is executing...\n");
            func(x);
        }
};

class TaskECB : public Task {
    private:
        // other varibles
        std::function<void(void)> func;
    public:
        TaskECB(std::function<void(void)>&f) : func(f) {}
        void operator()() {
            printf("TaskECB function is executing...\n");
            func();
        }
};

void F1() { // dummy function for example
    cout <<"no x"<<endl;
}
void F2(int x) { // dummy function for example
    cout <<"x : "<<x<<endl;
}

int main() {

    queue<unique_ptr<Task>> Q;

    function<void()>    func1   = F1;
    function<void(int)> func2   = F2;

    TaskECB task1(func1);
    TaskRPCB task2(func2,4);


    Q.emplace(new TaskECB(func1));
    Q.emplace(new TaskRPCB(func2,4));

    (*Q.front())();
    Q.pop();

    (*Q.front())();
    Q.pop();

}

The problem is, I can not push the objects directly as shown above. I have to create an object of an inherited class and pass it to another function to do the push action. It is because ( in my case ) the queue is a part of a thread-safe queue and it has separate Push() method.

template<typename T>
void threadSafeQueue<T>::Push(T newData) { /* TODO: size check before pushing */
    std::shared_ptr<T> data(std::make_shared<T>(std::move(newData)));
                                           /* construct the object before lock*/
    std::lock_guard<std::mutex> lk(mut);
    taskQueue.push(data);
    dataCond.notify_one();
}

Earlier I did not have multiple tasks to execute ( or push ) into the queue, therefore threadSafeQueue<TaskRPCB> workQ declaration worked fine for me.

Creating a base Task class like above is not working because of object slicing

Can you suggest other ways to store objects in the queue ( so that I can still use the lock guarded Push() method )

Thanks !

update : is the correct way of using variant?

typedef std::variant<TaskECB, TaskRPCB> myType;

int main() {

    queue<unique_ptr<myType>> Q;

    function<void()>    func1   = F1;
    function<void(int)> func2   = F2;

    TaskECB task1(func1);
    TaskRPCB task2(func2,4);

    myType x = task1;

    Q.push(make_unique<myType>(x));
    x = task2;
    Q.push(make_unique<myType>(x));

    if((*Q.front()).index() == 0) {
        auto f1 = get<TaskECB>(*Q.front());
        f1();
        Q.pop();
    }
    if((*Q.front()).index() == 1) {
        auto f1 = get<TaskRPCB>(*Q.front());
        f1();
        Q.pop();
    }

}

update2:

using myVariantType = std::variant<TaskECB, TaskRPCB>;

struct VisitPackage {
    void operator()(TaskECB & task) { 
        task();
    }
    void operator()(TaskRPCB& task) {
        task();
    }
};

int main() {
    queue<myVariantType> Q;

    function<void()>    func1   = F1;
    function<void(int)> func2   = F2;

    TaskECB task1(func1);
    TaskRPCB task2(func2,4);

    Q.emplace(task1);
    Q.emplace(task2);

    std::visit(VisitPackage(), Q.front());
    Q.pop();
    std::visit(VisitPackage(), Q.front());
    Q.pop();
}
Debashish
  • 1,155
  • 19
  • 34
  • 4
    polymorphism with smart pointers or unrelated classes with `std::variant` – bolov Mar 21 '19 at 20:49
  • @bolov Can you elaborate with an answer? Thank you for a hint btw! – Debashish Mar 21 '19 at 20:50
  • it's not clear to me what the problem is with `threadSafeQueue` – bolov Mar 21 '19 at 21:32
  • @bolov I updated with a variant example. Is this you expected? – Debashish Mar 24 '19 at 08:59
  • 1
    You don't need pointers. Just use `queue>`. Also prefer `std::visit` over the `if get` – bolov Mar 24 '19 at 10:20
  • thanks ! is it ok now? is `VisitPackage` definition fine ? – Debashish Mar 29 '19 at 12:47
  • And why we don't need to store pointers while using variant? – Debashish Mar 29 '19 at 13:14
  • 1
    yes, it is ok. However if the visit is as simple as you've showed it can be simplified with a simple lambda: `std::visit([](auto task) { task(); }, Q.front());` . Otherwise if you have something more complex you can have a struct just like you do, or you can create an overload of lambdas like shown in this page https://en.cppreference.com/w/cpp/utility/variant/visit – bolov Mar 29 '19 at 13:15
  • 1
    The default go-to should be non-pointers. If you come from a C background or C# where `new` is everywhere you might be tempted to use pointers everywhere. But in C++ that is counter-productive for a plethora of reasons. By default you should be using objects with automatic storage duration. Get this in your habit. That being said there are valid reasons to use pointers. One of these reasons is when you need polymorphic behavior. Polymorphism works only through pointers and references (but is not very useful with references). – bolov Mar 29 '19 at 13:20
  • 1
    a little bit offtopic. Your code can be simplified by bypassing the creation of those intermediary objects (and also improve performance a little): https://godbolt.org/z/N6cmw2 – bolov Mar 29 '19 at 13:34

0 Answers0