0

I'm trying to understand virtual classes in C++. In Wikipedia, I found this example:

#include <iostream>

class Machine {
public:
    void run() { }

    class Parts {
    public:
        virtual int get_wheels() = 0;

        virtual std::string get_fuel_type() = 0;
    };
};

// The inner class "Parts" of the class "Machine" may return the number of wheels the machine has.
class Car: Machine {
public:
    void run() { 
        std::cout << "The car is running." << std::endl; 
    }

    class Parts: Machine::Parts {
    public:
        int get_wheels() override {
            std::cout << "A car has 4 wheels." << std::endl;
            return 4;
        }

        std::string get_fuel_type() override {
            std::cout << "A car uses gasoline for fuel." << std::endl;
            return "gasoline";
        }
    };
};

I can get the number of wheels of a car with:

Car::Parts c_p;
c_p.get_wheels();

Is there any other (simple) way? Is there any way instantiating only Car car ?

Update:

I understand the concerns, but I find it useful as a nested interface (with minimal changes):

#include <iostream>
#include <memory>

class Machine {
public:
    virtual void run() = 0;

    class Parts {
    public:
        virtual int get_wheels() = 0;
        virtual std::string get_fuel_type() = 0;
    };
};

class Car: public Machine {
public:
    void run() { 
        std::cout << "The car is running." << std::endl; 
    }

    class Parts: public Machine::Parts {
    public:
        int get_wheels() override {
            std::cout << "A car has 4 wheels." << std::endl;
            return 4;
        }

        std::string get_fuel_type() override {
            std::cout << "A car uses gasoline for fuel." << std::endl;
            return "gasoline";
        }
    };
};

int main () {
    std::shared_ptr<Machine> X = std::make_shared<Car>();
    (*X).run();
    std::shared_ptr<Machine::Parts> Y = std::make_shared<Car::Parts>();
    (*Y).get_wheels();
    return 0;
}

I don't find any other code with this functionality. The only thing I miss is the possibility to access get_wheels directly from X. For instance, let's consider that I have a Machine in my program. The kind of machine I have will be specified dynamically. I want to know the number of wheels of this machine, but the method get_wheels must be inside a nested class Parts. The closer to solve this problem that I've got is with the code above, which gives me Machine and Machine::Parts as interfaces.

Medical physicist
  • 2,510
  • 4
  • 34
  • 51
  • 1
    Wow, didn't know virtual classes were a thing... – Taron Jan 16 '20 at 13:42
  • imho you are right that the example in unnecessarily complex. You can remove both outer classes (`Car` and `Machine`) to get a simpler example (you'll have to rename one of the `Parts`) – 463035818_is_not_an_ai Jan 16 '20 at 13:42
  • 1
    To get polymorphic behavior you have to go through a pointer or reference. So for example if you had a function that took a `const Machine::Parts&`, you could still pass it `c_p` and get the behavior from `Car::Parts` instead of `Machine::Parts`. But creating the object the first time always needs to use the concrete type. – 0x5453 Jan 16 '20 at 13:42
  • 3
    what irritates me about the example is that both `Machine` and `Car` merely define a nested class, but actually the `Car` isnt made of `Car::Parts` and neither is the `Machine` made of `Machine::Parts`. Really not a good example – 463035818_is_not_an_ai Jan 16 '20 at 13:45
  • 1
    The example given on the wikipedia page originally wasn't even valid C++, see [this revision introducing it](https://en.wikipedia.org/w/index.php?title=Virtual_class&diff=606371800&oldid=606368146) and also the Talk page. It was adjusted to be valid C++ later, but either the original author didn't intend it to be C++ or had no idea how C++ works (there are no virtual classes in this sense in the language). See also [stackoverflow question on the previous version of the example](https://stackoverflow.com/questions/50684046/calling-methods-of-a-virtual-member-class). – walnut Jan 16 '20 at 14:09
  • 1
    @Taron they aren't (in C++) – Caleth Jan 16 '20 at 14:30

2 Answers2

2

A simple solution would be to have a member part of you car:

struct Car : Machine {
    struct Parts : Machine::Parts {
        int get_wheels() override {
            std::cout << "A car has 4 wheels." << std::endl;
            return 4;
        }

        std::string get_fuel_type() override {
            std::cout << "A car uses gasoline for fuel." << std::endl;
            return "gasoline";
        }
    } parts; // <---

    // or declare it as a separated member:
    // Parts parts;
};

That way, you can call member functions like this:

Car car;
std::cout << car.parts.get_weels();
Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141
1

No. As it stands, a Car instance does not have any Car::Parts instances, nor any method that returns one.

The text around the example seems to be assuming that there is an instance of Machine::Parts associated with Machine somehow, which magically becomes a Car::Parts in Car. This may be the case in some other language, but it is not the case in C++.

A much more idomatic design would be to have a traits class template that Machine subclasses specialise.

template <typename Machine>
struct MachineParts;

template <>
struct MachineParts<Car> {

    static int get_wheels() {
        std::cout << "A car has 4 wheels." << std::endl;
        return 4;
    }

    static std::string get_fuel_type() {
        std::cout << "A car uses gasoline for fuel." << std::endl;
        return "gasoline";
    }

};

template <>
struct MachineParts<Bicycle> {

    static int get_wheels() {
        std::cout << "A bike has 2 wheels." << std::endl;
        return 2;
    }

    static std::string get_fuel_type() {
        std::cout << "A bike uses muscles for fuel." << std::endl;
        return "muscles";
    }

};
Caleth
  • 52,200
  • 2
  • 44
  • 75