-2

I have the following priority_queue with items of type base. The problem is, when I get item from the Queue with top(), then call its f() function via base reference object, it does call the f of base class but not the overriden f. I want child object's f() to be called. Any help? Thanks..

    #include <queue>
    #include <iostream>

    using namespace std;

    class base {

        public:
            int id;

            virtual void f() const {

                cout << "base f()" << endl;
            }

    };

    class D1: public base {

        public:

            void f() const {

                cout << "D1 f()" << endl;
            }

    };

    class D2: public base {

        public:

            void f() const {

                cout << "D2 f()" << endl;
            }

    };

    bool operator<(const base& b1, const base& b2)
            {
        return b1.id > b2.id;
    }

    int main()
    {

        priority_queue<base, deque<base>, less<deque<base>::value_type> > Q;

        D1 d1;
        D2 d2;

        Q.push(d1);
        Q.push(d2);

// this is not something  I want
        const base& b = Q.top();
        b.f();        // base f()


// this works as I want
    const base& b2 = d2;
    b2.f();        // D1 f()

    return 0;

    }
eral
  • 123
  • 1
  • 16
  • 8
    [Object slicing](https://en.wikipedia.org/wiki/Object_slicing). If you want polymorphic behavior, you need to store pointers in your container. – Igor Tandetnik Jan 10 '18 at 05:20
  • I tried that before, it worked but I got strange runtime error, thus I want to avoid it. Is that the only way? – eral Jan 10 '18 at 05:24
  • 3
    Well then, go back to almost-working program and concentrate on fixing the strange runtime error. – Igor Tandetnik Jan 10 '18 at 05:26
  • I won't. Any other idea? – eral Jan 10 '18 at 05:30
  • The C++ standard library doesn't support the idea of polymorphic union. `std::variant` is non-polymorphic. So unless you want to take on some real expert's coding better go for pointers. Try to just store `shared_ptr` instances. That way you minimize problems with object lifetimes. – Cheers and hth. - Alf Jan 10 '18 at 05:33
  • You just rejected the canonical solution, without any good reason. Unless you explain _why_, the question is a dupe. – Passer By Jan 10 '18 at 05:35
  • Fine with me. You are welcome to keep plugging at the non-working program. – Igor Tandetnik Jan 10 '18 at 05:36
  • Ok, I can fix the solution with pointers but this is not the actual issue. I really wonder if there is other workaround? – eral Jan 10 '18 at 05:55
  • Suppose I have to push copy of the object but not pointer to it because the object is local and will disappear. Actually I will have event priority queue and will feed events into it from different ISR s and consume from a dispatcher task. – eral Jan 10 '18 at 05:59
  • Allocate a copy on the heap (at a spot where you still know the real type of the object), and push a (possibly smart) pointer to that. You cannot store objects by value in a container and expect polymorphic behavior. – Igor Tandetnik Jan 10 '18 at 12:39

2 Answers2

1

Same polymorphic premise as using pointers, but here's a possible alternative using std::reference_wrapper:

#include <functional>
#include <queue>
#include <iostream>

using namespace std;

class base {
    public:
        int id;

        virtual void f() const {
            cout << "base f()" << endl;
        }
};

class D1: public base {
    public:
        void f() const {
            cout << "D1 f()" << endl;
        }
};

class D2: public base {
    public:
        void f() const {
            cout << "D2 f()" << endl;
        }
};

bool operator<(const base& b1, const base& b2)
{
    return b1.id > b2.id;
}

int main()
{

    priority_queue<std::reference_wrapper<base>, deque<std::reference_wrapper<base>>, less<deque<std::reference_wrapper<base>>::value_type> > Q;

    D1 d1;
    D2 d2;

    Q.push(d1);
    Q.push(d2);

    // this now works as you want?
    const auto& b = Q.top();
    b.get().f();    // D1 f()


    // this works as I want
    const auto& b2 = d2;
    b2.f();        // D2 f()

    return 0;
}
Phil Brubaker
  • 1,257
  • 3
  • 11
  • 14
  • With g++ 5.4, I got "‘reference_wrapper’ is not a member of ‘std’" error. – eral Jan 10 '18 at 06:30
  • Unfortunately you'll need C++ 11 or higher to use this or smart pointers. "Bare" pointers is the way to go. – Phil Brubaker Jan 10 '18 at 11:04
  • Thank you Phil. At least C++11 has a solution for this. – eral Jan 10 '18 at 11:21
  • Note that this has the same lifetime issues as storing a pointer - a reference to a local object will become dangling exactly the same way a pointer would, when the object goes out of scope. You'd still need to create a copy of the object someplace that would outlive the local scope - that is, on the heap. – Igor Tandetnik Jan 10 '18 at 12:48
  • @Igor, got it. Thank you. – eral Jan 10 '18 at 13:04
1

You should use shared_ptr as they are polymorphic by nature. Below code achieves what you want using shared_ptr

#include <queue>
#include <iostream>

using namespace std;

class base {

    public:
        int id;

        virtual void f() const {

            cout << "base f()" << endl;
        }

};

class D1: public base {

    public:

        void f() const {

            cout << "D1 f()" << endl;
        }

};

class D2: public base {

    public:

        void f() const {

            cout << "D2 f()" << endl;
        }

};

bool operator<(const base& b1, const base& b2)
{
    return b1.id > b2.id;
}

int main()
{

    priority_queue<std::shared_ptr<base>, deque<std::shared_ptr<base>>, less<deque<std::shared_ptr<base>>::value_type>> Q;

    D1 d1;
    D2 d2;

    Q.push(std::make_shared<D1>(d1));
    Q.push(std::make_shared<D2>(d2));

    // Works!
    const shared_ptr<base> b = Q.top();
    b->f();        // D1 f()

    return 0;

}
Mayank Jain
  • 2,504
  • 9
  • 33
  • 52