interface – Understanding C++ Traits and Making Them Efficient

I have come across the interesting and powerful concept of “traits” recently and am attempting to understand/implement them in C++. From what I understand, traits provide a way both extend/adapt the functionality of exiting code and define an “interface” for a class without using traditional inheritance (and all the overhead/problems that come with it). I also see that this concept seems to be closely related to the CRTP design pattern in C++.

As an example, my normal thought process for writing an interface in C++ would be to define a class with pure virtual methods. I can then create a subclass of this and pass a pointer to all my generic code. I am discovering that this has some problems however:

  1. Classes that need to inherit from multiple interfaces require the use of multiple inheritance which can become very complex and introduce the “diamond pattern” problem.
  2. A strict “is a” relationship is formed which is not always the intent. For example, if I am describing the interface to a Light, a Simulated Light is NOT really a Light, it merely has the “traits”/acts like a Light. What I mean is, the generic Light interface doesn’t really have commonalities that an implementation needs to inherit, it merely defines how an implementation should behave.
  3. Virtual methods and inheritance allow full dynamic polymorphism which incurs unnecessary overhead. In most of my code, I would only ever be using a single implementation of an interface at a time and thus I don’t need to dynamically choose the correct implementation, I just need to have the “users” of the interface be generic enough to all for different implementations.

Here is an example of a simple, traditional interface for a Light:

class Light {
public:
    virtual void on() = 0;
    virtual void off() = 0;
};

class MyLight : public Light {
public:
    void on() override;
    void off() override;
};

void lightController(Light& l) {
    l.on();
    l.off();
}

And (based upon the article here: https://chrisbranch.co.uk/2015/02/make-your-c-interfaces-trait-forward/) here is what I think is a “traits-based” implementation of the same concept:

template<typename T>
class Light {
public:
    Light(T& self) : _self(self) {}

    void on() { _self.on(); }
    void off() { _self.off(); }

private:
    T& _self;
};

class MyLight {
public:
    void on();
    void off();
};

class OddLight {
public:
    void set(bool state);
};

template<>
class Light<OddLight> {
public:
    Light(OddLight& self) : _self(self) {}
    
    void on() { _self.set(true); }
    void off() { _self.set(false); }
    
private:
    OddLight& _self;
};

template<typename T>
void lightUser1(T& l) {
    Light<T> light(l);

    light.on();
    light.off();
}

template<typename T>
void lightUser2(Light<T>& l) {
    light.on();
    light.off();
}

I have a few questions about this:

  1. Because, to use traits like this, you (temporarily) create a new Light instance, is there memory overhead associated with this?
  2. Is there a more effective method to document that a particular class “implements” a given trait?
  3. The article mentions two methods of defining a “user” for an interface. I have shown both above. lightUser2 seems to be the most well-document (it explicitly states that the function requires some implementation of the Light trait), however it requires that the implementations be explicitly casted into a Light outside of the function. Is there method to both document the intent of the user and all an implementation to be passed in directly?

Thank You!

Product of the Month September 2016

Source link

Leave a Comment

Your email address will not be published. Required fields are marked *