Thread: Composition

  1. #1
    Registered User
    Join Date
    Jul 2019
    Posts
    34

    Composition

    So i'm learning about composition and i think i got the idea right, but let me know if im wrong, I need to get the part name so i can output it in the operator<< overload function. Static casting didnt seem to work. Since a vehicle has a part, i did Part engine, in the vehicle class only since the other classes inherit it, or do i need to put it in the other classes too?


    Also, one thing i was wondering, If say I make an airplane class, if i make a Boeing737Max class thats empty, is it even worth creating that class? or is it better to just do Airplane Boeing737Max and create it that way? I guess if the max has special features that other airplanes dont then it would warrant doing that but what if it's empty? should you just create one for future proofing in case you will add stuff to it? or is it just the interface of a specific type of airplane thats worth having?


    Anyway I hope i did this right, i've been following tutorials and courses and guides, just wanted to get some input. I did a little function overloading too along with composition and inheritance.


    Boeing 737 max "is-a" airplane, and an airplane "is-a" vehicle, but part is not, but a vehicle "has-a" part(multiple) so I assume that is correct, thats what I was shown at least. The more you move down the inheritance hierarchy the more specialized or specific the classes get. Just want to make sure I got this stuff down before moving on to polymorphism.


    Any improvements that could be made or things im doing wrong, I would greatly appreciate being pointed out.


    Also one last question, in each class that inherits from vehicle, I have to retype all the vehicle classes constructor argument list stuff, is there a simpler way to just copy it over without having to retype it all and then just type out the variables for that class?


    Something like:


    Code:
    Airplane(Vehicle::Vehicle, float elevationCeiling): Vehicle::Vehicle, mElevationCeiling(elevationCeiling){}

    Something like that. I think if the language doesnt support that it would be great to have as you have to update every class when you add a variable to the base classes argument list. This way it's almost like a variable, you can have it in tons of spots but only need to update one location.


    Code:
    #include <iostream>
    #include <string>
    #include <vector>
    
    
    
    
    using std::cout;
    using std::endl;
    using std::vector;
    using std::string;
    
    
    class Part
    {
    	public:
    		Part() = default;
    		Part(const string& name): mName(name)
    		{}
    
    
    		friend std::ostream& operator<<(std::ostream& os, const Part& part)
    		{
    			os << part.GetPartName() << endl;
    
    
    			return os;
    		}
    
    
    		string GetPartName() const { return mName; }
    
    
    	private:
    		string mName{ "Part Name" };
    };
    
    
    class Vehicle
    {
    public:
    	Vehicle
    	(
    		const string& type,
    		const string& name,
    		float maxSpeed,
    		//In MPH
    		float maxFuelCapacity,
    		//In Gallons
    		float fuelUsedPerMinute,
    		float maxRange,
    		Part& engine
    	) : mType(type),
    		mName(name),
    		mMaxSpeed(maxSpeed),
    		mMaxFuelCapacity(maxFuelCapacity),
    		mFuelUsePerMinute(fuelUsedPerMinute),
    		mMaxRange(maxRange),
    		mEngine(engine)
    	{}
    
    
    	string GetType() const { return mType; }
    	string GetName() const { return mName; }
    	float GetMaxSpeed() const { return mMaxSpeed; }
    	float GetMaxFuelCapacity() const { return mMaxFuelCapacity; }
    	float GetFuelUsedPerMinute() const { return mFuelUsePerMinute; }
    	float GetMaxRange() const { return mMaxRange; }
    
    
    
    
    	void StartVehicle();
    
    
    private:
    	string mType{ "Vehicle Type" }; //Airplane, automobile, etc.
    	string mName{ "Vehicle Name" };
    	float mMaxSpeed{ 0.0 };
    	float mMaxFuelCapacity{ 0.0 }; //In gallons
    	float mFuelUsePerMinute{ 0.0 }; //How much fuel is consumed
    	float mMaxRange{ 0.0 }; //How far this vehicle can travel
    	Part mEngine;
    };
    
    
    void Vehicle::StartVehicle()
    {
    	cout << "Engine Started" << endl;
    }
    
    
    class Airplane : public Vehicle
    {
    public:
    	Airplane
    	(
    		const string& type,
    		const string& name,
    		float maxSpeed,
    		float maxFuelCapacity,
    		float fuelUsedPerMinute,
    		float maxRange,
    		Part& engine,
    		float elevationCeiling
    	)
    		: Vehicle
    		(
    			type,
    			name,
    			maxSpeed,
    			maxFuelCapacity,
    			fuelUsedPerMinute,
    			maxRange,
    			engine
    		), mElevationCeiling(elevationCeiling)
    	{}
    
    
    	using Vehicle::StartVehicle;
    	void StartVehicle();
    	float GetelevationCeiling() const { return mElevationCeiling; }
    
    
    
    
    private:
    	float mElevationCeiling{ 0.0 };
    };
    
    
    void Airplane::StartVehicle()
    {
    	cout << "Perform pre-flight checks" << endl;
    	cout << "Ready for takeoff" << endl;
    }
    
    
    
    
    //If a class like this has no variables is it worth making a class for instead of just doing Airplane Boeing737Max?
    class Boeing737Max final : public Airplane
    {
    public:
    	Boeing737Max
    	(
    		const string& type,
    		const string& name,
    		float maxSpeed,
    		float maxFuelCapacity,
    		float fuelUsedPerMinute,
    		float maxRange,
    		Part& engine,
    		float elevationCeiling
    	)
    		: Airplane
    		(
    			type,
    			name,
    			maxSpeed,
    			maxFuelCapacity,
    			fuelUsedPerMinute,
    			maxRange,
    			engine,
    			elevationCeiling
    		)
    	{}
    	
    	friend std::ostream& operator<<(std::ostream & os, const Boeing737Max& max)
    	{
    		os << "Vehicle Name: " << max.GetName() << endl;
    		os << "Vehicle type: " << max.GetType() << endl;
    		os << "Max Speed: " << max.GetMaxSpeed() << " MPH" << endl;
    		os << "Max Fuel Capacity: " << max.GetMaxFuelCapacity() << " gallons" << endl;
    		os << "Fuel Used Per Minute: " << max.GetFuelUsedPerMinute() << endl;
    		os << "Max Range: " << max.GetMaxRange() << " Miles" << endl;
    		//os << static_cast<const Part&>(max);
    		return os;
    	}
    
    
    	using Airplane::StartVehicle;
    	void StartVehicle();
    };
    
    
    void Boeing737Max::StartVehicle()
    {
    	cout << GetName() << " ready for takeoff" << endl;
    }
    
    
    int main()
    {
    	Part LEAP1B("CFM International LEAP-1B");
    	Boeing737Max Max("Airplane", "Boeing 737 Max", 521.0f, 6875.0f, 0.0f, 3521.0f, LEAP1B, 41000.0f);
    
    
    	cout << Max << endl;
    
    
    	Max.StartVehicle();
    
    
    }

  2. #2
    Registered User
    Join Date
    Dec 2017
    Posts
    1,626
    I feel like I've seen this somewhere else. If you've posted on multiple websites you should mention that fact and give the links to your other posts so that people here don't waste their time answering something that's already been answered.

    One quick point, why should I have to tell an airplane that it's an "Airplane"?
    A little inaccuracy saves tons of explanation. - H.H. Munro

  3. #3
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    If john.c's hunch is right, then yes, you should be linking to the other places where you've asked this.

    A few things stand out for me:
    Code:
    friend std::ostream& operator<<(std::ostream& os, const Part& part)
    {
        os << part.GetPartName() << endl;
    
        return os;
    }
    The above overloaded operator<< makes use of the GetPartName member function of the Part object. Therefore, it does not need to be declared a friend: it already can do what it needs to do using the public interface of the Part class.

    Unless you're designing a base class intended for multiple inheritance and so you're trying to avoid name collisions with multiple base classes, I'd say there is no need to have a member function of the Part class be named GetPartName when you can just name it GetName.

    Let's look at the Vehicle base class now:
    Code:
    void StartVehicle();
    Okay, so there's my nitpick that you could call this Start. But more importantly, let's look at its derived class, Airplane:
    Code:
    using Vehicle::StartVehicle;
    void StartVehicle();
    What you seem to be trying to do here is to override the member function from the base class. This is not the way to do it as you're actually overloading and shadowing Vehicle::StartVehicle. What you should do is to declare this in Vehicle:
    Code:
    virtual void StartVehicle();
    Then in Airplane:
    Code:
    void StartVehicle() override;
    This way, if you have a reference or (smart) pointer to a Vehicle, you can invoke the StartVehicle() method, and the appropriate overriden method for the Vehicle derived class will be invoked. For example:
    Code:
    #include <iostream>
    #include <memory>
    #include <vector>
    
    class Vehicle
    {
    public:
        virtual ~Vehicle() = default;
    
        virtual void Start()
        {
            std::cout << "Starting the vehicle..." << std::endl;
        }
    };
    
    class Airplane : public Vehicle
    {
    public:
        void Start() override
        {
            std::cout << "Starting the airplane..." << std::endl;
        }
    };
    
    int main()
    {
        std::vector<std::unique_ptr<Vehicle>> vehicles;
        vehicles.push_back(std::make_unique<Vehicle>());
        vehicles.push_back(std::make_unique<Airplane>());
    
        for (const auto& vehicle : vehicles)
        {
            vehicle->Start();
        }
    }
    Compile and run the above code, and you will see the output:
    Code:
    Starting the vehicle...
    Starting the airplane...
    Contrast with this example that uses your original approach:
    Code:
    #include <iostream>
    #include <memory>
    #include <vector>
    
    class Vehicle
    {
    public:
        virtual ~Vehicle() = default;
    
        void Start()
        {
            std::cout << "Starting the vehicle..." << std::endl;
        }
    };
    
    class Airplane : public Vehicle
    {
    public:
        using Vehicle::Start;
        void Start()
        {
            std::cout << "Starting the airplane..." << std::endl;
        }
    };
    
    int main()
    {
        std::vector<std::unique_ptr<Vehicle>> vehicles;
        vehicles.push_back(std::make_unique<Vehicle>());
        vehicles.push_back(std::make_unique<Airplane>());
    
        for (const auto& vehicle : vehicles)
        {
            vehicle->Start();
        }
    }
    You will see the output:
    Code:
    Starting the vehicle...
    Starting the vehicle...
    Notice that I have declared the Vehicle destructor to be virtual. This is so that objects of derived classes can be destroyed through a pointer to the Vehicle class, and indeed we need that for my examples because the vehicles vector stores (smart) pointers to Vehicle, and one of these pointers actually points to an Airplane object rather than to a concrete Vehicle object (but then again typically in these kind of classroom examples, "Vehicle" is an abstract base class rather than a concrete class).

    Anyway, you will find that you cannot override the operator<< to achieve this effect because it is not a member function, and therefore cannot be a virtual member function. The typical solution is to have a virtual print function:
    Code:
    #include <iostream>
    
    class Vehicle
    {
    public:
        virtual ~Vehicle() = default;
    
        virtual void print(std::ostream& out) const
        {
            out << "Vehicle @ " << this;
        }
    };
    
    std::ostream& operator<<(std::ostream& out, const Vehicle& vehicle)
    {
        vehicle.print(out);
        return out;
    }
    
    class Airplane : public Vehicle
    {
    public:
        void print(std::ostream& out) const override
        {
            out << "Airplane @ " << this;
        }
    };
    
    int main()
    {
        Airplane airplane;
        std::cout << airplane << std::endl;
    }
    So you can see that even though operator<< was not overloaded for Airplane, you can still use it for Airplane because the overloaded operator invokes the virtual print member function of the Vehicle class that the Airplane class overrides.
    Last edited by laserlight; 02-16-2021 at 04:28 PM.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  4. #4
    Registered User
    Join Date
    Sep 2020
    Posts
    150
    An alternative without virtual functions and pointers is to use std::variant and std::visit - since C++17.
    Code:
    #include <iostream>
    #include <vector>
    #include <variant>
    
    
    class Vehicle
    {
    public:
      void Start()
      {
        std::cout << "Starting the vehicle..." << std::endl;
      }
    };
    
    
    class Airplane : public Vehicle
    {
    public:
      void Start()
      {
        std::cout << "Starting the airplane..." << std::endl;
      }
    };
    
    
    struct Visitor
    {
      void operator()(Vehicle v)
      {
        v.Start();
      }
      void operator()(Airplane v)
      {
        v.Start();
      }
    };
    
    
    int main()
    {
      std::vector<std::variant<Vehicle, Airplane>> vehicles;
      vehicles.push_back(Vehicle());
      vehicles.push_back(Airplane());
    
    
      for (const auto& vehicle : vehicles)
      {
        std::visit(Visitor(), vehicle);
      }
    }
    Output:
    Starting the vehicle...
    Starting the airplane...

  5. #5
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    thmm: I'd say that's more relevant for dispatch among types that don't have a supertype/subtype relationship. In this case, the use of inheritance as in your example becomes useless (unless it was the intent to show adding dispatching that was not anticipated by the base class interface, but maybe that's a design failure hmm).

    EDIT: oh, I initially mentioned double dispatch thinking of the inheritance, but that isn't likely to be as good using variant instead of the polymorphic base class because of the need to change the variant when a new derived class is defined.
    Last edited by laserlight; 02-17-2021 at 05:06 AM.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  6. #6
    Registered User
    Join Date
    Sep 2020
    Posts
    150
    @laserlight,
    I just wanted to show an alternative way to do certain things.
    I don't claim that it is better.
    Not needing inheritance is a just a nice side ffect.
    I guess that not using virtual functions and pointers might give a performance benefit. Maybe someone on Linux can run it under perf to see if it is worth doing.

    ..because of the need to change the variant when a new derived class is defined.
    That's true.

  7. #7
    Registered User
    Join Date
    Jul 2019
    Posts
    34
    No this is the only place it is posted.

    Okay, so there's my nitpick that you could call this Start.
    Yeah I have a tendency to do that, I'll change it.

    Unless you're designing a base class intended for multiple inheritance and so you're trying to avoid name collisions with multiple base classes
    I think part wouldnt have any child classes, but im not sure. If it did have child classes that also had a GetName() function, wouldnt that overwrite it?

    What you seem to be trying to do here is to override the member function from the base class. This is not the way to do it as you're actually overloading and shadowing Vehicle::StartVehicle. What you should do is to declare this in Vehicle:
    Code:
    virtual void StartVehicle();

    Then in Airplane:
    Code:
    void StartVehicle() override;

    Yes I am following some tutorials and wanted to know how to overwrite functions without polymorphism, not sure if I would ever need to do that, im sure polymorphism is whats used instead, but you never know.

    This way, if you have a reference or (smart) pointer to a Vehicle, you can invoke the StartVehicle() method
    Do i need to use pointers with virtual? I've seen virtual overriding examples before and I dont remember them using pointers but i could be wrong, but is it necessary?

    Say I have a vehicle class, and there are four sub categories, Automobile, Locomotive, Watercraft and Aircraft, could I just override start functions of those without a pointer?

    Also is it worth defining a specific vehicle type under one of the categories, like under airplane, the vehicle 747 Max? or should I just use the Airplane class to create the specific plane?

  8. #8
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Do i need to use pointers with virtual? I've seen virtual overriding examples before and I dont remember them using pointers but i could be wrong, but is it necessary?
    I mean, I explicitly wrote "if you have a reference or (smart) pointer to a Vehicle, you can invoke the StartVehicle() method, and the appropriate overriden method for the Vehicle derived class will be invoked". If you suspect that I'm being superfluous, write a program to find out, i.e., test your theory: use a Vehicle object, assign an Airplane object to it, and then call Start and see what happens. You can then post your program here to clarify if you don't understand.

    Say I have a vehicle class, and there are four sub categories, Automobile, Locomotive, Watercraft and Aircraft, could I just override start functions of those without a pointer?
    This question does not make sense. What pointer are you talking about?

    Also is it worth defining a specific vehicle type under one of the categories, like under airplane, the vehicle 747 Max? or should I just use the Airplane class to create the specific plane?
    This is a matter of design. It depends on your requirements.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  9. #9
    Registered User
    Join Date
    Jul 2019
    Posts
    34
    In regards to overriding behaviour, I was trying to do it specifically without using virtual yet. I am following the tutorial for it on LearnCPP.com and through several C++ courses on Udemy.

    17.7 — Calling inherited functions and overriding behavior | Learn C++

    I followed this and I thought that's what I was doing? perhaps I misunderstood the content of the tutorial though. I'm not questioning the validity of your response, I simply don't quite fully understand why its not doing what its supposed to since i'm doing what the tutorial says.

  10. #10
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Quote Originally Posted by ChayHawk
    In regards to overriding behaviour, I was trying to do it specifically without using virtual yet. I am following the tutorial for it on LearnCPP.com and through several C++ courses on Udemy.

    17.7 — Calling inherited functions and overriding behavior | Learn C++

    I followed this and I thought that's what I was doing? perhaps I misunderstood the content of the tutorial though. I'm not questioning the validity of your response, I simply don't quite fully understand why its not doing what its supposed to since i'm doing what the tutorial says.
    Ah, I see. When I was reading through it previously, I was thinking that this was a bad idea to introduce so early in that tutorial, and it appears that my hunch was correct. As much as I am happy to recommend learncpp as a learning resource now that most of the glaring issues have been fixed, a weakness remains in that learncpp tends to be more about C++ syntax and basic semantics, rather than about how C++ programmers would use it effectively to solve programming problems.

    The problem is this: yes, you're arguably "overriding behaviour", but you're doing so in a way that is typically regarded as bad practice where C++ OOP is concerned. In the OO "sub-language" of C++, if a member function in a base class is not declared virtual, you normally would not try to "override behaviour" by "redefining" the member function in the derived class. Your use of a using declaration avoids one pitfall (i.e., hiding of other member functions with the same name inherited from the base class), but the problem remains in that it doesn't actually override the member function from a classical object oriented perspective. When we say "override" in an OO sense, we mean that you can have some kind of reference (used in a more generic sense, so it can refer to C++ references, pointers, or "object references" in various other programming languages that support OOP natively) to the base class that might or might not refer to a derived class object, and based on the specific type of the object referred to, the call to the corresponding method is dispatched. This is why the term "method" in C++ refers specifically to "virtual member function": it borrows from the OO concept of "method". In theory, this is a way to add behaviour to existing code without directly changing it, e.g., you can add a new derived class, and existing code that calls the method will naturally call the method of your derived class without needing to change, should the reference it holds come to refer to an object of this new derived class.

    With this "redefining" that you're doing, you won't get this. Rather, you're just defining a member function that happens to have the same name and signature as one from the base class, thereby hiding it. You still have to directly call that particular member function on an object of the derived class in order to get the new behaviour. So, instead of "overriding behaviour", I could just as well have defined a member function with a different name, and just called that.

    So, from the perspective of following the learncpp tutorial, what you're doing is not wrong. From the perspective of programming in C++ using best practices, what you're doing is wrong.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  11. #11
    Registered User
    Join Date
    Jul 2019
    Posts
    34
    Hmm. Ok, I was afraid of that. Do you know any good sites that teach that type of good oop design? I just want to be able to do game design, idk if that matters but I wont be designing databases or doing server stuff or anything.

  12. #12
    Registered User
    Join Date
    Jul 2019
    Posts
    34
    So maybe I understand now. So still following the LearnCPP tutorial, I have this program that derives functions that are virtual:

    Code:
    #include <iostream>#include <string>
    #include <vector>
    
    
    using std::cout;
    using std::endl;
    using std::string;
    using std::vector;
    
    
    class Animal
    {
        protected:
            std::string m_name;
    
    
            // We're making this constructor protected because
            // we don't want people creating Animal objects directly,
            // but we still want derived classes to be able to use it.
            Animal(const string& name)
                : m_name{ name }
            {
            }
    
    
        public:
            const string& getName() const { return m_name; }
            virtual string speak() const { return "???"; }
    };
    
    
    class Cat : public Animal
    {
        public:
            Cat(const string& name)
                : Animal{ name }
            {
            }
    
    
            virtual string speak() const { return "Meow"; }
    };
    
    
    class Dog : public Animal
    {
        public:
            Dog(const string& name)
                : Animal{ name }
            {
            }
    
    
            virtual string speak() const { return "Woof"; }
    };
    
    
    void report(const Animal& animal)
    {
        cout << animal.getName() << " says " << animal.speak() << '\n';
    }
    
    
    int main()
    {
        Cat cat{ "Fred" };
        Dog dog{ "Garbo" };
    
    
        report(cat);
        report(dog);
    
    
        return 0;
    }
    So anytime you want to override a function in a base class you should always use virtual correct?

    How will I know which functions should be made virtual and which ones should not be? I know that virtual functions are called and matched with the class that calls them dynamically at runtime through dynamic binding, but i'm still a little fuzzy as to when I should use this vs just letting the function be inherited normally, like, I understand that its something you want done at runtime, I just am not sure exactly what.

    I often like to refer to the Doom 3 source code, it's not the greatest example since the code base is old, long before modern C++(2004) but I still like to look at how they do stuff.

    DOOM-3-BFG/Weapon.h at master * id-Software/DOOM-3-BFG * GitHub

    However even looking at virtual function in the code i still don't quite understand why they need to be decided at runtime. I mean I kinda do, but kinda don't at the same time if that makes any sense, like I said it's all hard to make sense of.

    If I was making a game, what kinds of things would need to be made virtual and why? Some examples of usage would probably clear things up.

    Also, in the derived classes, should I put override after the function names even though it has virtual in front of it? do one or the other, or both?
    Last edited by ChayHawk; 02-18-2021 at 10:07 AM.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. class composition
    By kwstas_a in forum C++ Programming
    Replies: 1
    Last Post: 12-22-2014, 04:07 AM
  2. composition relations
    By student111 in forum C++ Programming
    Replies: 5
    Last Post: 06-19-2012, 02:01 PM
  3. Composition
    By Fatima Rizwan in forum C++ Programming
    Replies: 4
    Last Post: 10-31-2009, 09:39 AM
  4. Need help with Composition
    By cuddlez.ini in forum C++ Programming
    Replies: 3
    Last Post: 10-25-2004, 08:28 PM
  5. composition, i think
    By blight2c in forum C++ Programming
    Replies: 3
    Last Post: 05-01-2002, 08:49 PM

Tags for this Thread