4

I have a base abstract class like this;

class X {
public:
    virtual ~X(){}
    virtual doSomething() = 0;
};

Then I am implementing this with couple of classes like Y, Z etc. each having their own constructor, destructor and doSomething implementation. In my main function I do;

int main(){
    std::vector<X *> v;
    X *x1 = new Y();
    X *x2 = new Z();
    v.push_back(x1);
    v.push_back(x2);
    for(auto p : v){
        p->doSomething()
    }
}

This calls respective doSomething implementations as expected. But my problem is that I am using pointers to abstract class to manipulate a hierarchy of derived classes through its base class. This forces me to use new to create instances on the heap and I have to delete them manually afterwards. Is there a way to do something like this;

int main(){
    std::vector<X> v;
    Y x1();
    Z x2();
    v.push_back(x1);
    v.push_back(x2);
    for(auto p : v){
        p.doSomething()
    }
}

So that destructors will be called automatically when I get out of main. I know I can't create a member of abstract class but using pointers to achieve all this seems strange. Surely there must be a way of doing this without pointers and new-delete. It would be great if someone showed me the best practice.

meguli
  • 1,426
  • 1
  • 18
  • 35

2 Answers2

5

You want to use an appropriate smart pointer:

std::vector<std::unique_ptr<X>> v;

v.push_back(std::make_unique<Y>());
v.push_back(std::make_unique<Z>());

// all done

(There are of course still dynamic allocations, since you cannot manage an unbounded number of unconstrained types without runtime indirection, but you should never have to think about any of the dynamic management manually. The type system can do all the work for you.)

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • I'm curious if you think `push_back(make_unique())` is better than `emplace_back(new Y)`. – John Zwinck May 01 '16 at 11:53
  • Well I guess I should have included this in my first comment, but: why? – John Zwinck May 01 '16 at 11:54
  • 2
    @JohnZwinck: You should have! (E.g. by changing "if" to "why".) The answer is because a) you never ever want to say `new` in your code, b) you want your code to be subexpression-wise correct, and c) because push-back (which adds values) is semantically simpler and splits responsibilities better than the more invasive emplacement (which is for direct-initializing through explicit constructors). – Kerrek SB May 01 '16 at 11:55
  • Can you explain more in the answer, I am a little unexperienced with smart pointers. I am guessing internally they are reference counted so that all the pointer magic I would do otherwise done for me. Still, a detailed explanation would be helpful. Also, is this possible in pre C++11 in any other way without smart pointers, how would people do this before C++11? – meguli May 01 '16 at 11:58
  • 4
    @meguli: How about a deal, between friends: You spend the next 24 hours searching the internet, and Stack Overflow in particular, for explanations and usage examples of `unique_ptr`. Then, if after that point you are still unsure, let me know and I will write up more details. How's that? – Kerrek SB May 01 '16 at 12:02
  • Agreed, but what about the solution before C++11 era? Did you have to implement your own smart pointers or managing your news and deletes manually was the normal way to go? – meguli May 01 '16 at 12:08
  • 1
    @meguli: Pre-C++11 it was a lot harder, because the type system was much weaker due the lack of rvalue references, and so you couldn't express a lot of the really useful "responsibility transfer" semantics in the type system. Those were dark days :-S – Kerrek SB May 01 '16 at 12:11
  • @meguli: Before C++11 we just used Boost smart pointers. In certain cases we still need to, e.g. here: http://stackoverflow.com/a/13913161/4323 – John Zwinck May 01 '16 at 12:24
2

Despite the good answer posted by Kerrek SB, the OP asked for a solution (let me say) new-free and it's worth adding one more answer for that.


You can use a std::reference_wrapper.
It follows a minimal, working example:

#include<vector>
#include<functional>
#include<iostream>

class X {
public:
    virtual ~X(){}
    virtual void doSomething() = 0;
};

class Y: public X {
    void doSomething() override {
        std::cout << "Y" << std::endl;
    }
};

class Z: public X {
    void doSomething() override {
        std::cout << "Z" << std::endl;
    }
};

int main(){
    std::vector<std::reference_wrapper<X>> v;
    Y x1{};
    Z x2{};
    v.emplace_back(x1);
    v.emplace_back(x2)
    for(X &p : v){
        p.doSomething();
    }
}

Note that in this case you must guarantee that the lifetime of the stored objects overcomes the one of the vector.
You won't have this problem if you plan to use std::unique_ptrs, as suggested in an other answer by Kerrek SB.

skypjack
  • 49,335
  • 19
  • 95
  • 187