0

Consider the following scenario:

class AbsBase {
public:
    virtual double foo() = 0;
};

class Concrete1 : public AbsBase {
public:
    double foo() {
        return 1;
    }
};

class Concrete2 : public AbsBase {
public:
    double foo() {
        return 2;
    }
};

struct Config {
    /**
     * Must use a pointer to `AbsBase`, since it is an abstract base class, 
     * and the concrete instances could be of either of the derived classes.
     */
    AbsBase* thing;

    void setThing(AbsBase* t) {
        thing = t;
    }
};

This works, but I would prefer it if thing were a copy of the argument passed to setThing. I.e. if thing were of type Concrete1 (as opposed to an abstract base class) I could do something like this:

Concrete1 thing;
void setThing(const Concrete1& t) {
    thing = Concrete1(t);
}

However I can't see a way of achieving this without instantiating an object of an abstract type (which is illegal). Any ideas?

Sean Bone
  • 3,368
  • 7
  • 31
  • 47
  • you can try making `thing` a reference by declaring it: `AbsBase &thing`. – Kagiso Marvin Molekwa Nov 05 '19 at 09:23
  • otherwise I just don't see how what you want is possible since it wouldn't even make sense anyways. – Kagiso Marvin Molekwa Nov 05 '19 at 09:24
  • But passing by reference will not copy an object – Sergey Aleksandrovich Nov 05 '19 at 09:24
  • @marvinIsSacul That would eliminate the possibility to change the object that it is referring to. – Simon Kraemer Nov 05 '19 at 09:25
  • but think about it this way, how can you create a copy of something that does not really exist (an abstract type) ? – Kagiso Marvin Molekwa Nov 05 '19 at 09:25
  • Just had a thought: how about creating a `clone()` method in the concrete classes which returns a heap-constructed copy of itself? – Sean Bone Nov 05 '19 at 09:27
  • @marvinIsSacul I think it makes sense. It's not a copy of something that doesn't exist, it's a copy of an object which respects a certain interface. – Sean Bone Nov 05 '19 at 09:28
  • I'm not getting why you're trying to do this. Would you have multiple members in Config, one `Concrete1`, one `Concrete2` etc. or just one member? Have you tried writing the code that would use this (assuming you got it to work) to see if it is actually usable? – Mat Nov 05 '19 at 09:30
  • @SeanBone creating a `clone()` method wouldn't change `thing`. It would still have to be a pointer (or reference). It cannot be anything else because it is an abstract class. – Kagiso Marvin Molekwa Nov 05 '19 at 09:30
  • Possible duplicate of [How to copy/create derived class instance from a pointer to a polymorphic base class?](https://stackoverflow.com/questions/5731217/how-to-copy-create-derived-class-instance-from-a-pointer-to-a-polymorphic-base-c) – apple apple Nov 05 '19 at 09:30
  • @Mat multiple instances of `Config` would have different types for `thing`, and `thing` could be changed on the fly. Yes, I currently have an implementation of the example shown above. – Sean Bone Nov 05 '19 at 09:37

3 Answers3

2

You can use a template function to achieve the wanted behavior. I would recommend to also use std::unique_ptr to prevent memory issues.

class AbsBase {
public:
    virtual ~AbsBase() = default;
    virtual double foo() = 0;
};

class Concrete1 : public AbsBase {
public:
    double foo() {
        return 1;
    }
};

class Concrete2 : public AbsBase {
public:
    double foo() {
        return 2;
    }
};

struct Config {
    /**
     * Must use a pointer to `AbsBase`, since it is an abstract base class, 
     * and the concrete instances could be of either of the derived classes.
     */
    std::unique_ptr<AbsBase> thing;

    template <typename T>
    void setThing(const T& t) {
        static_assert(std::is_base_of<AbsBase, T>::value, "T must inherit AbsBase");
        static_assert(std::is_copy_constructible<T>::value, "T must be copy-constructible");
        thing.reset(new T{t});
    }

    void test()
    {
        std::cout << (thing?thing->foo():0.0) << std::endl;
    }
};

https://gcc.godbolt.org/z/fID5UM

Simon Kraemer
  • 5,700
  • 1
  • 19
  • 49
2

Here is an example (with std::cout for more understanding). Don't forget to make a virtual destructor of AbsBase to avoid memory leak.

#include <iostream>

class AbsBase {
public:
    virtual double foo() = 0;
    virtual ~AbsBase() {
        std::cout << "destructor AbsBase" << std::endl;
    }
};

class Concrete1 : public AbsBase {
public:
    Concrete1() {
        std::cout << "default constructor of Concrete1" << std::endl;
    }
    Concrete1(const Concrete1 & other) {
        std::cout << "copy constructor of Concrete1" << std::endl;
    }
    Concrete1 & operator=(const Concrete1 & other) {
        std::cout << "assignment operator of Concrete1" << std::endl;
    }
    virtual ~Concrete1() {
        std::cout << "destructor of Concrete1" << std::endl;
    }
    double foo() {
        return 1;
    }
};

class Concrete2 : public AbsBase {
public:
    Concrete2() {
        std::cout << "default constructor of Concrete2" << std::endl;
    }
    Concrete2(const Concrete2 & other) {
        std::cout << "copy constructor of Concrete2" << std::endl;
    }
    Concrete2 & operator=(const Concrete2 & other) {
        std::cout << "assignment operator of Concrete2" << std::endl;
    }
    virtual ~Concrete2() {
        std::cout << "destructor of Concrete2" << std::endl;
    }
    double foo() {
        return 2;
    }
};

class Config {
private:
    /**
     * Must use a pointer to `AbsBase`, since it is an abstract base class, 
     * and the concrete instances could be of either of the derived classes.
     */
    AbsBase* thing;

public:
    Config() : thing(0) {
        std::cout << "constructor of Config" << std::endl;
    }
    ~Config() {
        std::cout << "destructor of Config" << std::endl;
        if (thing) {
            std::cout << "delete thing" << std::endl;
            delete thing;
        }
    }
    template<typename T>
    void setThing(T & t) {
        std::cout << "setThing" << std::endl;
        if (thing) {
            std::cout << "delete thing" << std::endl;
            delete thing;
        }
        thing = new T (t);
    }
    AbsBase* getThing() {
        return thing;
    }
};

int main() {
    Config config;
    Concrete1 concrete1;
    Concrete2 concrete2;
    std::cout << "=================" << std::endl;
    std::cout << "save concrete1" << std::endl;
    config.setThing(concrete1);
    std::cout << config.getThing()-> foo() << std::endl;
    std::cout << "=================" << std::endl;
    std::cout << "save concrete2" << std::endl;
    config.setThing(concrete2);
    std::cout << config.getThing()-> foo() << std::endl;
    std::cout << "=================" << std::endl;
    std::cout << "destruction of all local variables" << std::endl;
    return 0;
}

Output:

constructor of Config
default constructor of Concrete1
default constructor of Concrete2
=================
save concrete1
setThing
copy constructor of Concrete1
1
=================
save concrete2
setThing
delete thing
destructor of Concrete1
destructor AbsBase
copy constructor of Concrete2
2
=================
destruction of all local variables
destructor of Concrete2
destructor AbsBase
destructor of Concrete1
destructor AbsBase
destructor of Config
delete thing
destructor of Concrete2
destructor AbsBase
1

In addition to the duplicate which use clone mothod, in your case you may also be able to accept the concrete type by template.

struct Config {
    AbsBase* thing;
    template <typename T>
    void setThing(T& t) {
        thing = new T(t);
    }
};
apple apple
  • 10,292
  • 2
  • 16
  • 36