简体   繁体   中英

Why can't polymorphic derived classes be nested inside the base class?

For example, I tried to do something like this:

class Animal {
public:
    virtual const char *says() const = 0;

    static Animal *makeLion() { return new Lion(); }
    static Animal *makeTiger() { return new Tiger(); }
    static Animal *makePig() { return new Pig(); }

private:
    class Lion : public Animal { // error: invalid use of incomplete type ‘class Animal’
    public:
        virtual const char *says() const
        {
            return "roar";
        }
    };

    class Tiger : public Animal { // error: invalid use of incomplete type ‘class Animal’
    public:
        virtual const char *says() const
        {
            return "meow";
        }
    };

    class Pig : public Animal { // error: invalid use of incomplete type ‘class Animal’
    public:
        virtual const char *says() const
        {
            return "That\'s all Folks!";
        }
    };
};

The compiler complains that Animal is an incomplete type. But why is Animal an incomplete type, if the inner class definitions are not needed to define the outer class itself (since no non-static variables of the inner class types are declared by value in the outer class)?

Is there a way around this or a better way to do what I'm trying to do?

A class is not complete until the closing brace } of the class definition.

Re “is there a way around this”, you can do things like this:

struct A
{
    struct B;
};

struct A::B
    : A
{};

But it's not a common pattern. I can't remember ever having seen it.

Is there a way around this or a better way to do what I'm trying to do?

Don't use nested classes. Just move the derived classes out of Animal .


On a separate note, having functions

static Animal *makeLion() { return new Lion(); }
static Animal *makeTiger() { return new Tiger(); }
static Animal *makePig() { return new Pig(); }

in Animal is a symptom of poor design. A base class should, as much as possible, be agnostic of classes derived from it.


Here's a suggestion for a cleaner interface and implementation:

Animal.h:

namespace AnimalsNamespace
{
   // The base class
   class Animal
   {
      public:
         virtual const char *says() const = 0;
   };


   // Functions to construct objects of various sub-types of Animal.
   // Moving these out of Animal and putting them in the namespace makes
   // Animal a little bit cleaner.

   Animal* makeLion();
   Animal* makeTiger();
   Animal* makePig();
}

Animal.cpp:

namespace AnimalsNamespace
{
   class Lion : public Animal
   {
      public:
         virtual const char *says() const
         {
            return "roar";
         }
   };

   class Tiger : public Animal
   {
      public:
         virtual const char *says() const
         {
            return "meow";
         }
   };

   class Pig : public Animal
   {
      public:
         virtual const char *says() const
         {
            return "That\'s all Folks!";
         }
   };

   Animal* makeLion() { return new Lion(); }
   Animal* makeTiger() { return new Tiger(); }
   Animal* makePig() { return new Pig(); }
}

A possible solution is to put definition of Lion, Tiger, Pig in function scope:

class Animal {
public:
    virtual ~Animal() = default;
    virtual const char *says() const = 0;

    static std::unique_ptr<Animal> makeLion()
    {
        class Lion : public Animal
        {
        public:
            virtual const char *says() const override
            {
                return "roar";
            }
        };
        return std::make_unique<Lion>();
    }
    static std::unique_ptr<Animal> makeTiger() {
        class Tiger : public Animal
        {
        public:
            virtual const char *says() const override
            {
                return "meow";
            }
        };
        return std::make_unique<Tiger>();
    }
    static std::unique_ptr<Animal> makePig() {
        class Pig : public Animal
        {
        public:
            virtual const char *says() const override
            {
                return "That\'s all Folks!";
            }
        };
        return std::make_unique<Pig>();
    }
};

int main() {    
    std::cout << Animal::makeLion()->says() << std::endl;
}

Demo .

Bad design. Making an object should not be done in Animal class. Consider redesigning the classes like:

class Animal
{
public:
    virtual const char* says() const = 0;
};

class Lion : public Animal
{
public:
    virtual const char *says() const
    {
        return "roar";
    }
};

class Tiger : public Animal
{
public:
    virtual const char *says() const
    {
        return "meow";
    }
};

class Pig : public Animal
{
public:
    virtual const char *says() const
    {
        return "That\'s all Folks!";
    }
};

And then use like this:

Animal* p = new Pig;
cout << p->says() << endl;

Otherwise, you have to add makeXXX() functions every time you add a new kind.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM