2

I would like to use virtual functions of objects of different classes (derived from the same base class) without a) constructing all the objects or b) using new. Please see the code example:

#include <iostream>
class A{
    public:
    virtual void p(void){std::cout << "Im A" << std::endl;};
};
class B : public A{
    public:
    virtual void p(void) override {std::cout << "Im B" << std::endl;};
};
class C : public A{
    public:
    virtual void p(void) override {std::cout << "Im C" << std::endl;};
};

int main(){
    bool cond = true;   // some condition
    A* o1;
    if (cond) o1 = new B(); else o1 = new C();
    o1->p();    // will call correct p(), i.e. either B::p or C::p but not A::p

    A o2 = B();
    o2.p();     // will call A::p

    A* o3;
    B tmp1; C tmp2; // construct both objects altough only one is needed
    if (cond) o3 = &tmp1; else o3 = &tmp2;
    o3->p();    // will call correct p(), i.e. either B::p or C::p but not A::p

    A* o4;
    if (cond) {B tmp; o4 = &tmp;} else {C tmp; o4 = &tmp;}  // uses address of local variable
    o4->p();    // will call correct p(), i.e. either B::p or C::p but not A::p

    return 0;
}

I want the behavior of o1 but without calling new. o2 doesnt work (calls function of base class and if base class is abstract it doesnt work at all). o3 works but constructs all the different objects although only one is needed. o4 kinda "works" but uses a reference to a local variable outside of its scope.

What would be the correct / best / modern C++ way to do it?

Mehdi Mostafavi
  • 880
  • 1
  • 12
  • 25
A.Krad
  • 21
  • 2
  • The bottom line is that if you want to use a `C`, you need to store a `C` _somewhere_. Also,`o4` is plain undefined behavior. – Brian61354270 Mar 17 '20 at 17:49
  • 2
    `o2` has type `A` in fact, not `B`, that's why `A::p` is being called. `o2` is constructed via copy constructor of `A` class – Renat Mar 17 '20 at 17:55
  • 2
    @Renat which is more commonly known as [object slicing](https://stackoverflow.com/questions/274626/). – Remy Lebeau Mar 17 '20 at 18:08
  • @Brian Yeah, but I would like to avoid having B **and** C although only either of it needed. The only way I see in this context is using new as for o1. I was hoping for a better way... – A.Krad Mar 17 '20 at 18:18

6 Answers6

2

How to use virtual functions in derived objects without new

Like this:

B b;
C c;

A& a = cond ? b : c;
a.p();

without a) constructing all the objects

You can also do this:

if (cond) {
    B b;
    b.p();
} else {
    C c;
    c.p();
}

At this point it doesn't really matter that the function is virtual though since we are using static dispatch. Which is better than dynamic dispatch.


o4 kinda "works" but uses a reference to a local variable outside of its scope.

I.e. the behaviour of the program is undefind i.e. it doesn't work at all.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • That satisfies the OP's 2nd requirement, "*without b) using new*", but it breaks the OP's 1st requirement, "*without a) constructing all the objects*" – Remy Lebeau Mar 17 '20 at 17:54
  • @RemyLebeau Added suggestion for that – eerorika Mar 17 '20 at 17:58
  • The suggestions you describe are already known to the OP, as they are stated in the question itself. So this answer is not really answering anything. – Remy Lebeau Mar 17 '20 at 18:05
  • @RemyLebeau If OP knew this, then it answers that OP already knows the answer. It's useful since OP apparently didn't know that they knew it all along. That said, I don't see my second suggestion in their question. – eerorika Mar 17 '20 at 18:11
  • @eerorika I exactly want to avoid this, i.e. constructing all objects just to use one. The 2nd "solution" was left out because its possible in this simple example but not in a more complicated code base where ::p could be anywhere – A.Krad Mar 17 '20 at 18:21
  • 1
    @A.Krad You should create a minimal example of a case where this solution is not possible, and write a question about that example. – eerorika Mar 17 '20 at 18:23
2

It will be better to use a helper function when you want to avoid using new but be able to use different derived types based on some condition.

void do_stuff(A& obj)
{
}

int main()
{
    bool cond = true;   // some condition

    if (cond)
    {
       B tmp;
       do_stuff(tmp);
    }
    else
    {
       C tmp;
       do_stuff(tmp);
    }

    return 0;
}
R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • Thanks. This would work in this simple framework but not in the more complicated codebase this issue occurs. – A.Krad Mar 17 '20 at 18:19
  • @A.Krad, the strategy would work for code base that has more complicated logic. If you need help with that, we can start a discussion separate from this comment thread. – R Sahu Mar 17 '20 at 18:22
1

If using placement new is acceptable, you can use a union to hold the storage, then construct the proper member based on the condition:

union U {
    U() { }
    B b;
    C c;
};

int test(bool cond) {
    U u;
    A *a2;
    if (cond)
        a2 = new (&u.b) B;
    else
        a2 = new (&u.c) C;

    a2->p();
    return 0;
}

The constructor in the union is necessary since the default constructor will be implicitly deleted due to the presence of non-trivial default constructors for the members of the union.

You'll also have to manually destroy your objects when you're done.

1201ProgramAlarm
  • 32,384
  • 7
  • 42
  • 56
0

For starters this code snippet

A* o4;
if (cond) {B tmp; o4 = &tmp;} else {C tmp; o4 = &tmp;}  // uses address of local variable
o4->p();    // will call correct p(), i.e. either B::p or C::p but not A::p

has undefined behavior because outside the if statement the pointer o4 is invalid due to the fact that the local object tmp is not alive outside the scopes of the if sub-statement.

As for this code snippet

A o2 = B();
o2.p();     // will call A::p

then you should use a reference to an object of the type B. For example

B b;
A &o2 = b;
o2.p();

As for the code snippet with the pointer o1 then in any case pointed objects must exists. Either they will be created dynamically as in your example or you can create the both locally and then depending on the condition set the pointer to one of them.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • 2
    It should be noted that `A o2 = B();` [*slices* the 'B' object](https://stackoverflow.com/questions/274626/), which is why `A::p` gets called instead of `B::p` – Remy Lebeau Mar 17 '20 at 18:06
  • Regarding the first statement. Im aware. Thats what my comment meant to say. But thanks. It wasnt explicit enough. Regarding the 3rd statement. That does not answer my question: If there are multiple derived classes and only one is to be used (see example), then I would need to construct all of them, right? – A.Krad Mar 17 '20 at 18:22
0

If you're worried about using new because of memory management, just use a std::shared_ptr instead:

int main(){
    bool cond = true;   // some condition
    std::shared_ptr<A> o1;

    if(cond) {
        o1 = std::make_shared<B>();
    } else {
        o1 = std::make_shared<C>();
    }

    o1->p();    

    return 0;
}

Prints:

Im B

Working example


Note: I was originally going to suggest an std::unique_ptr instead, however I didn't think they were copyable. But I was wrong. std::unique_ptrs will work too.


Note 2: you will need C++14 or later to use std::make_shared() or std::make_unique, but new will work in C++11. That violates your rule of not using new, but at least it does manage memory for you.

  • 1
    Thanks. Definitely better than new. However, my aim is to avoid the heap allocation as I do not see a reason why the objects should be on the heap (lifetime is not beyond the scope) – A.Krad Mar 17 '20 at 21:02
  • @A.Krad Ah. I understand now. Would something like [this](https://barrgroup.com/embedded-systems/how-to/polymorphism-no-heap-memory) work? –  Mar 17 '20 at 21:14
  • Also, in light of this, I might recommend looking into [1201ProgramAlarm's solution](https://stackoverflow.com/a/60728529/10957435), as I don't think that would take any heap memory space. Also, you might want to add your rationale to the question. –  Mar 17 '20 at 21:23
0

If you want to avoid new mainly to avoid the extra cleanup code and potential bugs from dangling pointers, double deletes, etc., just use std::unique_ptr:

std::unique_ptr<A> o1;
if (cond)
    o1 = std::make_unique<B>();
else
    o1 = std::make_unique<C>();
o1->p();

This will do essentially the same as the code using new, but behind the scenes, and also does the delete for you when o1 goes out of scope.

If you have a performance-critical code bottleneck and want to avoid the overhead of the heap memory allocation and deallocations used by new, things get trickier, but you could do it with something like:

using B_or_C_type = std::variant<B,C>;
auto o5 = cond ? B_or_C_type{B{}} : B_or_C_type{C{}};;
std::visit(std::mem_fn(&A::p), o5);

On typical systems, this code will avoid using any heap memory at all, instead using stack memory large enough to hold the larger of B or C. std::variant has the logic to act like a union but making sure only the correct type actually gets used. std::visit applies any functor to a variant, as long as the functor can be called with every type in the variant. Note if you define a variant variable with no initializer, it creates an object with the first type, though it can be reassigned to a different-typed object later. (If creating that default object isn't possible or should be avoided, you could use std::monostate as the first dummy type - but then using std::visit gets trickier.)

aschepler
  • 70,891
  • 9
  • 107
  • 161
  • Thank you. Looks like there is no easy solution that gives me exactly what I want. But this answer comes closest. – A.Krad Mar 18 '20 at 12:01