Runsva
Runsva

Reputation: 675

Inherited Class Factory?

I'm attempting to understand how to properly create an inherited class factory in C++. I'm using C++ 14 in this example.

I made a simple example to try and understand how this works:

#include <iostream>

class Animal
{
public:
    virtual std::string Sound();
};

class Dog : Animal
{
public:
    std::string Sound() {return "Woff!";};
};

class Cat : Animal
{
public:
    std::string Sound() {return "Meow!";};
};

class Snake : Animal
{
public:
    std::string Sound() {return "Ssss!";};
};

Animal GetAnimal(std::string name)
{
    if (name == "Dog")
        return Dog();
    else if (name == "Cat")
        return Cat();
    else if (name == "Snake")
        return Snake();

    return Animal();
}

int main()
{
    std::cout << "Sound of a dog: \"" << GetAnimal("Dog").Sound() << "\"\n";
    std::cout << "Sound of a cat: \"" << GetAnimal("Cat").Sound() << "\"\n";
    std::cout << "Sound of a snake: \"" << GetAnimal("Snake").Sound() << "\"\n";

    return 0;
}

Where Dog, Cat and Snake are all inherited classes from Animal. I want to acquire a local instance of each animal type from the GetAnimal function, provided the animal name as an argument to that function.

The code above prompts the following error upon a compilation attempt for each one of the return lines in the GetAnimal function:

E0269   conversion to inaccessible base class "Animal" is not allowed

Is it not possible to assign an inherited class object to a variable of the inherited class's base type? What would be the proper way of doing something like this in C++?

Thanks for reading my post, any guidance is appreciated.

Upvotes: 1

Views: 91

Answers (2)

James Kanze
James Kanze

Reputation: 154027

There are a number of issues here:

  • By default, inheritance in C++ is private, which means that the base class is only visible in the derived class. Any code outside the derived class will not see it. So an instance of Dog will not convert to Animal outside of member functions of Dog.
  • Inheritance in C++ only works through pointers or references. When you return an instance of Animal (not a pointer or a reference), something called slicing occurs: a new instance of Animal is created, by copying the Animal part of the derived class. This new instance is of type Animal, however, and virtual functions in it will resolve to the function in Animal. Which leads us to
  • Animal has a pure virtual function, so is abstract, and cannot be instantiated. Your code should result in a compiler error when you try to return Animal.

To correct this, each of these points must be addressed:

  • Declare the inheritance public.
  • Return a reference or a (possibly) smart pointer. Given the fact the derived classes are idempotent (as they contain no data members), I'd probably go with returning a reference (probably const: in this case, it really doesn't make any difference, but it's a good idea to always return const references unless you intend for the caller to modify the object).
  • You can't instantiate Animal, so if there is no match, you'll have to do something else. You could return a pointer, rather than a reference, and return a null pointer if there is no match for name, or you could raise an exception, or even abort (considering no match as an assertion failure) -- the correct solution here depends on the application which will be using this code. If no match is an expected condition, returning a pointer is probably most appropriate. This would be the case if, for example, name comes from user input. Alternatively, if no match is considered something exceptional (say because name comes from a file, and no match will only occur if the file is corrupted), an exception will allow the program to recover, or at least output an error message with more information. Finally, if name has been pre-vetted, and no match is a hard programming error, aborting may be appropriate. (But it depends on the application -- in critical applications, it's usual to abort, so that the back-up will take over, but in applications with significant user input, you definitely don't want to abort without given the application the chance to save the data.)

Upvotes: 2

Ted Lyngmo
Ted Lyngmo

Reputation: 117871

You need to use base class pointers or references and also to use public inheritance. It's usually a good idea to make the base class destructor virtual too if you plan on destructing the objects through a base class pointer, which is usually what you want to do.

It could look like this:

#include <iostream>
#include <memory>

class Animal {
public:
    virtual ~Animal() = default;
    virtual std::string Sound() = 0; // needs to be implemented by derived class
};

class Dog : public Animal {
public:
    std::string Sound() override { return "Woff!"; };
};

class Cat : public Animal {
public:
    std::string Sound() override { return "Meow!"; };
};

class Snake : public Animal {
public:
    std::string Sound() override { return "Ssss!"; };
};

std::unique_ptr<Animal> GetAnimal(std::string name) {
    if (name == "Dog")
        return std::make_unique<Dog>();
    else if (name == "Cat")
        return std::make_unique<Cat>();
    else if (name == "Snake")
        return std::make_unique<Snake>();

    throw std::runtime_error("Invalid animal");
}

And used like so:

int main() {
    std::cout << "Sound of a dog: \"" << GetAnimal("Dog")->Sound() << "\"\n";
    std::cout << "Sound of a cat: \"" << GetAnimal("Cat")->Sound() << "\"\n";
    std::cout << "Sound of a snake: \"" << GetAnimal("Snake")->Sound() << "\"\n";
}

Output:

Sound of a dog: "Woff!"
Sound of a cat: "Meow!"
Sound of a snake: "Ssss!"

Upvotes: 4

Related Questions