0

After searching for hours, I end up here. I have a Container class with a pointer to Base class as member variable. This should either refer to Spec1 or another inherited classes of Base, which I omitted here. The type should be determined by the argument in constructor (e.g. string, enum, int, etc.).

I read much about dynamic memory allocation and why it should be avoided whenever possible. Is it possible to avoid here? Isnt any usual object destroyed after the constructor? Or is the design idea completely wrong? I come from Java :( Thanks in advance.

class Base{
  public:
    virtual ~Base(){}; // required?
    virtual void doSomething() = 0;
};

class Spec1 : public Base {
     public:
       Spec1(){};
       Spec1(int i){
         // whatever
       }
       void doSomething(){
          std::printf("hello world");
       }
};

class Container{
   public:
     Container(String type_message){
      if (type_message.compare("We need Spec1")){
          m_type = new Spec1(1);
      } // add more ifs for other types (Spec2, Spec3 etc.)
     }
     void doSomethingWithSpec(){
        m_type->doSomething();
     }
   private:
      Base* m_type;
 };

int main (int argc, char **argv){
    Container a ("We need Spec1");
    a.doSomething();
}
Lucker10
  • 474
  • 5
  • 16
  • 2
    It is recommended to use `std::unique_ptr` and `std::make_unique` in your case, instead of raw pointers and `new`. – n. m. could be an AI May 30 '18 at 15:50
  • 1
    Your design is fine in general, though `Container` is missing a destructor to call `delete m_type;` (and should also disable the default copy constructor and copy assignment operator, since `m_type` should not be copied as-is). And yes, `~Base` needs to be `virtual`, so `delete m_type;` will work correctly, calling the destructor of whatever derived type you choose to instantiate. – Remy Lebeau May 30 '18 at 16:15
  • Is the destructor called if the program is aborted by ctrl-c (ubuntu terminal)? Or do I have to call the destructor manually? – Lucker10 May 31 '18 at 06:32
  • 1
    Regarding dynamic allocation being 'avoided whenever possible', this is true but if you want polymorphism in C++ then you *must* use pointers or references, so in cases like this (i.e. where a reference isn't suitable) you'll need to use dynamic allocation. – Sean Burton May 31 '18 at 09:02

2 Answers2

1

Requiring Container to be aware of every possible derived class of Base does not sound like good design. That is what factory functions are for.

Have Container store that object as std::unique_ptr to avoid memory leaks and manual memory management.

struct Base {
    virtual ~Base() = default;
    virtual void doSomething() = 0;
};

struct Spec1 : Base {
    void doSomething() override {
        std::printf("%s\n", __PRETTY_FUNCTION__);
    }
};

// Factory function.
std::unique_ptr<Base> createBase(std::string const& type) {
    if(type == "Spec1")
        return std::unique_ptr<Base>(new Spec1);
    throw std::runtime_error("Unknown type " + type);
}

class Container {
    std::unique_ptr<Base> m_type;
public:
    Container(std::string const& type)
        : m_type(createBase(type))
    {}

    void doSomething(){
        m_type->doSomething();
    }
};

int main() {
    Container a ("Spec1");
    a.doSomething();
}
Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
  • Thanks for factory function, didnt know before. But actually I want to keep it short and simple, and I do other stuff in constructor before, so that would be all passed to createBase method. – Lucker10 May 31 '18 at 09:03
  • 1
    @Lucker10 `Container` and `Base` look like [strategy design pattern](https://en.wikipedia.org/wiki/Strategy_pattern). – Maxim Egorushkin May 31 '18 at 09:29
0

Constructor works like any other function. Local variables are allocated in stack and get out of scope whenever the function completes whereas dynamic memory allocation is done in stack and needs to be explicitaly deallocated (in C++ and not in Java) before the function completes.

However in your case

m_type = new Spec1(1);

new Spec1(1) is dynamically allocated in heap and won't get destroyed on completion of Constructor code. The reference is stored in the member variable of the class. So as long as the instance of class Container is in scope, the memory allocated to Spec1(1) can be referred.

To compare just consider other scenario.

Container(String type_message){
      Base* m_type;
      if (type_message.compare("We need Spec1")){
          m_type = new Spec1(1);
      } // add more ifs for other types (Spec2, Spec3 etc.)
     }

Here as soon as constructor finishes, m_type will go out of scope but new Spec1(1) will still be there in heap as the memory leak.

Shashwat Kumar
  • 5,159
  • 2
  • 30
  • 66
  • So there is no possibility to avoid the use of "new" ? Is this a typical use case for "new" ? – Lucker10 May 31 '18 at 06:35
  • new here is similar to new in Java. You can avoid new by not declaring Base class object as pointer. `Base m_type; m_type = Spec1(1)` You should avoid dynamic allocation as much as possible. https://stackoverflow.com/questions/22146094/why-should-i-use-a-pointer-rather-than-the-object-itself is a good read – Shashwat Kumar May 31 '18 at 07:32
  • That does not work for virtual class: `error: cannot declare field ‘Container::m_type’ to be of abstract type ‘Base’ Base m_type;` – Lucker10 May 31 '18 at 09:00