3

I should prefer stack allocation to heap allocation. It's better to pass by value (especially if you're creating new objects — but at the same time, if you return by base class, your object will be sliced), or at least by reference than passing pointers (although you can't create a vector of references).

I read all of this carefully, and now I feel that I know less that I knew before. I don't have a slightest idea about how to write a code that, IMO, should be trivial, while respecting the best practices mentioned in all of these well-written and thought-through answers.

Here's what I want to implement. (I don't pretend that it's correct C++, but I just want to convey the idea).

// This thing is purely virtual!
class BaseStuff { }

// Has important behaviour and data members that shouldn't be sliced
class SomeStuff : public BaseStuff { }

// Don't slice me plz
class OtherStuff : public BaseStuff { }

BaseStuff CreateStuff()
{
    // falls a set of rules to create SomeStuff or OtherStuff instance based on phase of the moon
}

std::vector<BaseStuff> CreateListOfStuff()
{
    // calls CreateStuff a lot
}

public static void main()
{
    List<BaseStuff> allTheStuff = CreateListOfStuff();
    // to things with stuff
}
Community
  • 1
  • 1
Max Yankov
  • 12,551
  • 12
  • 67
  • 135

3 Answers3

5

If you want polymorphism, you have to use pointers.

BaseStuff* CreateStuff()
{
   ...
}

List<BaseStuff*> allTheStuff = CreateListOfStuff();

Indeed, you should prefer stack allocation to heap allocation, when possible. But it's not always possible.

Of course, instead of raw pointers, which are inherently unsafe, you may consider using smart pointers - unique_ptr, shared_ptr, etc, but smart pointers nonetheless

List<shared_ptr<BaseStuff>> = CreateListOfStuff
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Armen Tsirunyan
  • 130,161
  • 59
  • 324
  • 434
  • What abut all the `new` and `delete`? – Max Yankov Mar 11 '15 at 19:27
  • @golergka: That can be taken care of my smart pointers. See edit. – Armen Tsirunyan Mar 11 '15 at 19:31
  • 1
    I would go further and change the return type of `CreateStuff()` to a smart pointer. – R Sahu Mar 11 '15 at 19:46
  • @golergka: you still have to use `new` (unless you use `make_shared()` instead), but the whole purpose of smart pointers is to call `delete` automatically for you. You should really strive to *avoid* calling `new` and `delete` directly as much as possible, let the framework handle it for you. – Remy Lebeau Mar 11 '15 at 20:21
3

You're going to store polymorphic pointers to a base class that actually point to a derived type (note I used std::unique_ptrs here):

#include <iostream>
#include <memory>
#include <vector>

class Base
{
public:
  virtual void shout() = 0;
};
class Child : public Base
{
public:
  void shout() { std::cout << "Child!\n"; }
};
class Orphan : public Base
{
public:
  void shout() { std::cout << "Orphan!\n"; }
};

int main()
{
  std::vector<std::unique_ptr<Base>> the_list;
  the_list.reserve(3);
  the_list.emplace_back(std::make_unique<Child>());
  the_list.emplace_back(std::make_unique<Orphan>());
  the_list.emplace_back(std::make_unique<Child>());

  for(const auto& item : the_list)
  {
    item->shout(); // remember, items are (smart) pointers!
  }
}

Live example. You can only store one type in a Standard C++ container, so you must store a (smart) pointer to Base here.

rubenvb
  • 74,642
  • 33
  • 187
  • 332
  • Thanks for your help. But what I wanted to implement still doesn't work: http://coliru.stacked-crooked.com/a/e95d88ccb2e285e6 – Max Yankov Mar 11 '15 at 21:00
  • @golergka you can't make a `std::vector` of `const std::unique_ptr`. Also, `const` on return value is silly (if not completely useless). Just make the variables themselves `const`. See [here](http://coliru.stacked-crooked.com/a/ef9a3a7916221d92). – rubenvb Mar 12 '15 at 10:22
  • thanks! But shouldn't I make everything immutable by default? – Max Yankov Mar 12 '15 at 10:30
  • @golergka it won't have any effect; anyone can copy the `const` return value of a function into a non-`const` variable. That's why it's up to you to make the variable itself `const`, and leave the function without. – rubenvb Mar 12 '15 at 10:32
  • why it is even in the language then? – Max Yankov Mar 12 '15 at 10:37
  • Ah, it's for returning references and pointers — but for returning values const doesn't make any sense. Got it. – Max Yankov Mar 12 '15 at 10:38
  • 1
    @golergka well, there's a lot in the language, and it has its "use". You're right about references, but pointers have the same "problem" as, say, `int`, because you can just take a copy into a non-`const` variable. For a bit more background, follow the string of duplicates to [Does returning by const value affect return value optimization?](http://stackoverflow.com/questions/25360617/does-returning-by-const-value-affect-return-value-optimization) – rubenvb Mar 12 '15 at 10:40
0

One thing you frequently (usually?) do in situations like this is define a member function (traditionally named clone) in the base class to create a copy of an object:

class BaseStuff { 
public:
    virtual BaseStuff *clone() const = 0;
};

class SomeStuff : public BaseStuff {     
    virtual void SomeStuff *clone() const { 
        return new SomeStuff(*this); // needs a correctly functioning copy ctor
    }
};

class OtherStuff : public BaseStuff {
public: 
    virtual OtherStuff *clone() const { 
        return new OtherStuff(*this);
    }
};

This lets us copy an item via a pointer to the base, without slicing. This way if you want (for example) your ListOfStuff to store copies of the items you put into it (rather than just pointers to the original items, which may cease to exist, leaving dangling pointers) you can add an item something like this:

class ListOfStuff { 
    class Node {
        // Note: this is extremely incomplete. Almost certainly needs a dtor,
        // and you want to either implement or delete the copy ctor to either
        // do copying correctly, or assure it can't happen at all.
        Node *next;
        BaseStuff *data;
    public:
        Node(BaseStuff *data, Node *next = nullptr) : data(data), next(next) {}
    };

    Node *begin, *end;

public:
    Add(BaseStuff *item) { 
        BaseStuff *new_item = item->clone();
        end->next = new node(new_item);
        end = end->next;
    }    
};

With one minor modification, I'd agree with Armen Tsirunyan's basic point: when you're dealing with a polymorphic hierarchy, you normally have to do so via a pointer, or (the modification) a reference.

You can use smart pointers instead of pointers in many cases--but not in all cases, and choosing the correct smart pointer for a specific situation can be non-trivial in itself. You do usually want some sort of wrapper around your pointers though--the code above avoids them primarily because I don't know enough about what you're doing to pick the correct one.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111