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:
- 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.
- 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.
- 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:
- Because, to use traits like this, you (temporarily) create a new Light instance, is there memory overhead associated with this?
- Is there a more effective method to document that a particular class "implements" a given trait?
- 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!