0

Setup

I've encountered the following situation in my C++ code (this is an example to illustrate the problem, and has not much to do with the actual code). I have a virtual class Family and two classes that I derive from it:

class Family {
    public:
        virtual double operator()(double const & x) const = 0;
        virtual ~Family();
};


class Mother : public Family {
    public:
        double operator()(double const & x) const override { return x*x; }
};

class Father : public Family {
    public:
        double operator()(double const & x) const override { return x-2; }
};

Then I have another class Car. This class should have a private member that can be either an object from the Mother or Father class. I tried to implement this as

class Car {
    public:
        Car(Family member) : right_seat_(member)  {}
    private:
        Family right_seat_;
};

If one tries to run the above via the main function

int main(){
    Mother lucy = Mother();
    Car Van = Car(lucy);
}

I get the error that member in the constructor of Car cannot be declared since Family is abstract. I understand the problem and why this happens, but I don't know what the right tool in C++ is to solve the problem. Unfortunately I also don't know how to google this properly (I didn't really find any suitable suggestions).


What I tried so far

The only idea that I had was to remove the abstract class altogether and template the Car class. I'd like to avoid this, since in the original problem the two derived classes logically belong to a "superclass", so I don't want to introduce this split if it is not absolutely necessary.

Community
  • 1
  • 1
Sito
  • 494
  • 10
  • 29

2 Answers2

2

You need to use references or pointers for polymorphism. One possible solution would be:

class Car {
    public:
        Car(Family* member) : right_seat_(member)  {}
    private:
        Family* right_seat_;
};
int main(){
    Mother* lucy = new Mother();
    Car Van = Car(lucy);
    ...//do stuff with car
}

Don't forget to delete the pointers once you are done with them.
If you can use c++11 (c++14 for std::make_unique) or higher, using smart-pointers is even better:

class Car {
    public:
        Car(std::unique_ptr<Family>&& member) : right_seat_(std::move(member))  {}
    private:
        std::unique_ptr<Family> right_seat_;
};
int main(){
    std::unique_ptr<Family> lucy = std::make_unique<Mother>();
    Car Van = Car(std:move(lucy));
    ...//do stuff with car
}
melk
  • 530
  • 3
  • 9
  • 1
    need pointers or references yes, but need manual dynamic memory no. `Mother lucy; Car Van = Car(lucy);` would work if `Car` would take a rerference. Or with pointers: `Mother lucy; Car Van = Car(&lucy);`. You are leaking memory – 463035818_is_not_an_ai May 06 '20 at 13:18
  • @idclev463035818 True, which is why i explicitly state that the pointer needs to be deleted (didn't know where to put that delete, though, since that depends on how OP wants to manage object lifetime). – melk May 06 '20 at 13:20
  • my point is: There is no need to manage the lifetime. Use automatic storage and you cannot forget to delete. You introduced a problem that wasnt present in OPs code – 463035818_is_not_an_ai May 06 '20 at 13:22
  • Why do you create Van object in this way Car Van = Car(&lucy)? I think this is more java style or format for creating objects. You can simply create Van object with Car Van{&lucy}. Isn't it better? – Hamed Norouzi May 06 '20 at 13:26
  • @idclev463035818 Yes, automatic storage is better when you can use it, but since this is just an example, i wouldn't assume that the `Mother` object always lives as long (or longer) than `Van` in OPs real code, in which case automatic storage wouldn't work. – melk May 06 '20 at 13:29
  • @HamedNorouzi I just took the example in the question ;) – melk May 06 '20 at 13:29
  • I dont get your point. Objects are destroyed in reverse order of construction, so it is guaranteed that `lucy` outlives `Van` if you use automatic storage, with manual dynamic allocations this is not the case – 463035818_is_not_an_ai May 06 '20 at 13:32
  • @idclev463035818 In this simple `main`, sure. But imagine if `Mother` is created in a function, the passed to `Van`. Obviously, this would create problems if `Van` outlives the function-scope, but `Mother` doesn't. And since i don't know how OP will use these classes, i choose dynamic allocation. – melk May 06 '20 at 13:34
  • in a more complicated scenario using raw owning pointers is even worse.... I give up – 463035818_is_not_an_ai May 06 '20 at 13:35
  • @melk `since i don't know how OP will use these classes` This is a good argument for not suggesting dynamic allocation in my opinion. If you don't know whether it is needed, then it is better to not introduce the complexity of dynamic allocation. Automatic variables are much harder to misuse. Only important rule that one needs to know is to not return references to automatic variables. Dynamic allocation has loads of rules and caveats that take long time for beginners to learn, and introducing it to a simple example can lead the reader away from the point of the example. – eerorika May 06 '20 at 13:39
  • @eerorika I added a smart-pointer version to my answer. I'm somehwat wary of using pointers or references to automatically allocated variables as class-members, since class-instances will have no control over object-lifetime of these members, but that might just be me. – melk May 06 '20 at 13:41
  • sorry me again, that new example is odd. You move from `lucy` in main, so it will be in a moved from state. And it is odd that the `Car` owns the family member, a observing pointer would be more appropriate – 463035818_is_not_an_ai May 06 '20 at 13:45
  • @idclev463035818 Yes, but `make_unique` is available since c++14 – melk May 06 '20 at 13:48
  • @melk Nice. It's indeed best to show a reasonable way to deal with ownership when demonstrating dynamic allocation (i.e. no bare owning pointers). – eerorika May 06 '20 at 13:48
  • @idclev463035818 As to your previous comment, yes kinda, but anyway, the whole point of my answer is in the first sentence (use references or pointers), the rest is just supposed to be an easy example to get OP started. – melk May 06 '20 at 13:52
  • To be pedantic, smart pointers can be used even prior to C++11. It's just that the only standard smart pointer was a bit problematic, and shared pointer isn't in the standard library yet (but is implementable). – eerorika May 06 '20 at 13:53
  • I agree that the first sentence "use references or pointers" is the important one, but my first comment still holds. From your example one can get the impression that polymorphism requires dynamic memory allocation. At least now you show smart pointers, but the whole thing about dynamic allocation isnt striclty necessary (but of course it doesnt hurt to be there) – 463035818_is_not_an_ai May 06 '20 at 13:56
  • @idclev463035818 Indeed, that impression would be wrong. I edited my answer to try and make it clear that dynamic allocation is not necessary to use polymorphism. – melk May 06 '20 at 14:02
  • Thank you for your help! But I think your second example does not compile as it is now. Running the [this](https://pastebin.com/jTXVFWAK) gives me an error `use of deleted function`. – Sito May 06 '20 at 15:26
  • 1
    @Sito My bad, forgot a `std::move`. Fixed. – melk May 06 '20 at 16:29
1

Abstract classes cannot be members because they cannot be instantiated.

Dynamic polymorphism requires indirection. What you can have is a pointer to an abstract base.

This class should have a private member that can be either an object from the Mother or Father class.

A variable in C++ cannot have sometimes one type and other times another type. There is exactly on type for every variable except within templates where different instances of a template can have different types.

Again, this is where you need indirection: You can have a pointer that points to a base, and it doesn't matter which derived class contains that base.

If you want Car to own (i.e. control and be responsible for the lifetime of) the indirectly referred family as it would own a member, then you need to use dynamic allocation. That said, such ownership relationship seems dubious from design perspective.

eerorika
  • 232,697
  • 12
  • 197
  • 326