Search code examples
c++classumlpreprocessorclass-diagram

C++: Represent variable in class diagram with two different possible types depending on preprocessor directive


I have a class that uses one varibale that can have two different types according to a preprocessor directive (#ifdef #else #endif). How should I cleanly represent this variable's type without creating another UML class diagram?


Solution

  • Preprocessor tricks vs. OOP design

    You use the preprocessor to influence your class definition in a way comparable to this example:

    class A {
    #ifdef X 
        V myvar; 
    #else 
        W mywar; 
    #endif 
        ...
    };   
    

    This trick uses the preprocessor, playing with token substitution, instead of using a proper OOP design to achieve the same result.

    The UML side of the problem

    UML does not know the preprocessor and doesn't provide for uch tricks. In UML you'll need to model an equivalent OOP construct to express what you want to do. Two main orientations can be considered:

    • You'd model a generalization U wit specialisations V and W. And you'd model an associatation of A with U. This would cover your desing, although it's much more flexible since it allows to chose U at runtime.
    • If you'd use a language with interfaces, you'd typically define U as in interface, and V and W would show a «realization» dependency (dotted line with triancle arrow head).
    • You'd model the templates with UML templates and bindings for parameterized type U.

    The last option is the closest to what you do with the preprocessor, since it is compile time.

    More on C++ alternatives for the preprocessor

    In C++ or other OOP languages, this kind of design could be implemented using the corresponding C++ implementation to the UML constructs above.

    For class specialization (C++) you would have something like:

    class U {...};           // a more general type that covers some specializations 
    class V:public U {...}; 
    class W:public U {...}; 
    
    class A {
        U myvar;  // ???
        ... 
    };   
    

    Unfortunately, this usually doesn't work so well due to the value semantic of C++ object model and slicing. So to make it work you'd need to go for a reference, a pointer, or petter a smart_pointer, for example:

    class A {
        unique_ptr<U> myvar;     // smart pointer initialized in construtctor or intejected
        ...
    };
    

    This is very powerfull, and allows to decide at run-time with a (very) small performance overhead. But preprocessor is compile time, and there's a powerfull compile-time solution as well with templates:

    template <class U> 
    class TA {
        U myvar; 
        ...
    }; 
    

    You may then in the code instantiate the class with the right type:

    class TA<V> x,y,z; 
    class TA<W> a,b,c;
    class TA<int> d,e,f;  // no need to have a subtype relation 
    

    You may even make the code more readable, using type aliases like:

    using A = class TA<V>;    // instead of #define X
    A x,y,z;