Search code examples
c++oopaggregationcompositiontraits

Composition: using traits to avoid forwarding functions?


Let's say we have two classes, A and B. When using composition to model a "has-a" or "is-implemented-in-terms-of" relationship (e.g. B has-a A), one of the drawbacks vs. inheritance is that B does not the contain public functionality of A that it requires. In order to gain access to As public functions, it is necessary to provide forwarding functions (as opposed to inheritance, where B would inherit all of As public functions).

To give a more concrete example, let's say we have a Person which has-a ContactInfo:

using namespace std;

class ContactInfo
{
public:
   ContactInfo();
   void updateAddress(string address);
   void updatePhone(string phone);
   void updateEmail(string email);
private:
   string address;
   string phone;
   string email;
};

class Person
{
public:
   Person();
   // Forwarding functions:
   void updateAddress(string address){contactInfo.updateAddress(address)};
   void updatePhone(string phone){contactInfo.updatePhone(phone)};
   void updateEmail(string email){contactInfo.updateEmail(email)};
private:
   ContactInfo contactInfo;
};

Ignoring any deficiencies in the design (it is just a contrived example to demonstrate my question below), I have had to tediously replicate the exact function signatures from ContactInfo in Person. In a more complex example there could be many such functions, and many layers of composed classes, leading to much code duplication with all the usual problems of maintenance and being error-prone, etc.

Nonetheless, this is the recommended practice for modelling "has-a" or "is-implemented-in-terms-of" according to sources such as Item 38 of Meyers' Effective C++, and Item 24 of Sutter's Exceptional C++ (link).

Whilst researching this, I came across this Wikipedia article which discusses the same topic. At the bottom of the article, it suggests the following:

One drawback to using composition in place of inheritance is that all of the methods being provided by the composed classes must be implemented in the derived class, even if they are only forwarding methods. [...] This drawback can be avoided by using traits.

I am fairly new to the concept of traits and with everything I have read, I am finding it hard to relate to the above statement. My question therefore is: How would one go about using traits to avoid forwarding functions with composition? An answer based on my example (Person and ContactInfo) would be ideal.

EDIT: Just to clarify, in response to some of the answers, I am aware of private inheritance as an alternative to composition for modelling "is-implemented-in-terms-of". My question is not about that, it is specifically about the meaning of Wikipedia's statement relating to traits. I am not asking for alternatives to composition. I've bolded my question to make it clearer that this is what I'm asking.


Solution

  • First of all I should mention that traits are different things in C++/STL and languages like PHP, Lasso etc. It looks like the article from wikipedia refers to PHP-like traits because C++/STL traits are not designed for polymorphic reuse (we are speaking about code reuse with polymorphic behavior, right?). They designed to simplify declaration of template classes.

    Traits are used in some languages that don't support multiple inheritance (PHP, Lasso etc). Traits allow to "emulate" multiple inheritance (but multiple inheritance and traits are not exactly the same).

    In contrast C++ doesn't support PHP-traits but supports multiple inheritance. So if speaking about C++ then trait-like solution will be something like this:

    class VisibleTrait
    {
        public:
            virtual void draw();
    };
    
    class SolidTrait
    {
        public:
            virtual void collide(Object objects[]);
    };
    
    class MovableTrait
    {
        public:
            virtual void update();
    };
    
    
    // A player is visible, movable, and solid
    class Player : public VisibleTrait, MovableTrait, SolidTrait
    {
    };
    
    // Smoke is visible and movable but not solid 
    class Smoke : public VisibleTrait, MovableTrait
    {
    };
    
    // A hause is visible and solid but not movable
    class House : public VisibleTrait, SolidTrait
    {
    };
    

    So to answer your question

    How would one go about using traits to avoid forwarding functions with composition?

    1) Traits don't avoid forwarding functions with composition because traits work independently from composition. (The article from Wikipadia is misleading a little regarding the relationship between traits and composition)
    2) PHP/Lasso-like traits can be partially emulated in C++ with multiple inheritance.