Composition: using traits to avoid forwarding func

2019-06-16 03:15发布

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.

4条回答
Viruses.
2楼-- · 2019-06-16 03:55

The article talk about inheritance with Interface,

so in fact it tells that the object have to respect some signatures.

type traits can be used to check if signature is correct and dispatch to appropriate function

For example some STL algorithms wait for type Iterator, but those iterators don't inherit from a class Iterator but must provide some contract (operator ++(), operator !=(rhs), operator*()).

with the article example:

  • class Player - which can Move
  • class Building - which can not Move

And code:

#if 1
// simple type traits which tells if class has method update_position
template <typename T> struct can_move;

// Hardcode the values
template <> struct can_move<Player> { static const bool value = true; };
template <> struct can_move<Building> { static const bool value = false; };

#else
// or even better, but need a has_update_position. (see how to check if member exist in a class)
template <typename T> struct can_move{ static const bool value = has_update_position<T>::value };
#endif

template <typename T, bool> struct position_updater;

// specialization for object which can move
template <typename T> struct position_updater<T, true>
{
    static void update(T& object) { object.update_position(); }
};

// specialization for object which can NOT move
template <typename T> struct position_updater<T, false>
{
    static void update(T& object) { /* Do nothing, it can not move */ }
};


template <typename T>
void update_position(T& object)
{
    // statically dispatch to the correct method
    // No need of interface or inheritance
    position_updater<T, can_move<T>::value>::update(object);
}
查看更多
该账号已被封号
3楼-- · 2019-06-16 04:04

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.

查看更多
唯我独甜
4楼-- · 2019-06-16 04:12

AFAIK a trait class is something like the following:

#include <iostream>
using namespace std;

class ContactInfo
{
public:
   void updateAddress() { cout << "update address"; };
   void updatePhone() {};
   void updateEmail() {};
};

template<class T> class TraitClass
{
  public:

  private:
    T obj;
};

template<> class TraitClass<ContactInfo>
{
  public:
        void updateAddress() {obj.updateAddress();};
        void updatePhone() {obj.updatePhone();};
        void updateEmail() {obj.updateEmail();};
  private:
    ContactInfo obj;
};

class Person
{
public:
        void updateAddress() {obj.updateAddress();};
        void updatePhone() {obj.updatePhone();};
        void updateEmail() {obj.updateEmail();};
private:
    TraitClass<ContactInfo> obj;
};

int main() {

    Person myPerson;

    myPerson.updateAddress();

    return 0;
}

That is: a compile-time templated class where you can implement (and/or specialize it) your delegate methods in order to forward whatever you need without (for whatever reason you have) recurring to inheritance.

http://en.wikipedia.org/wiki/Trait_(computer_programming)

查看更多
成全新的幸福
5楼-- · 2019-06-16 04:20

Maybe you can try private inheritance:

class Person : private ContactInfo
{
public:
   Person() { }
   using ContactInfo::updateAddress;
   using ContactInfo::updatePhone;
   using ContactInfo::updateEmail;
};

int main()
{
    Person person;
    person.updateAddress("hi");
    return 0;
}

Although you may want to watch out for the caveats listed in this FAQ:

There are also several distinctions:

  • The simple-composition variant is needed if you want to contain several Engines per Car
  • The private-inheritance variant can introduce unnecessary multiple inheritance
  • The private-inheritance variant allows members of Car to convert a Car* to an Engine*
  • The private-inheritance variant allows access to the protected members of the base class
  • The private-inheritance variant allows Car to override Engine's virtual functions
  • The private-inheritance variant makes it slightly simpler (20 characters compared to 28 characters) to give Car a start() method that simply calls through to the Engine's start() method

Otherwise the example of composition provided seems identical to yours. Clicking on the traits link in the wikipedia article didn't provide any C++ articles, and the link in the references seems to be about type traits. I couldn't find how type traits has anything to do with your scenario.

查看更多
登录 后发表回答