about
12/02/2022
CppBlockingQueue Repo
CppProperties code

CppProperties  Repository

Illustrates how to extend the C++ language using templates

Quick Status Code functions correctly no known defects Demonstration code yes Documentation yes Test cases no Static library no Build requires C++17 option Planned design changes Add demo for custom
class property

1.0 Concept

Properties are a programming language construct that provide encapsulated instances of the property type with setter and getter methods to access that value. C++ does not provide properties, but this code shows that can be done with templates and some specialization magic. There are some important reasons for using properties:
  • Provide thread-safe operations for the encapsulated instances. Property access methods may take and release locks as needed, so users of the class don't have to do that.
  • Provide logging of access to the encapsulated instances. Again, access methods provide whatever logging is needed.
  • Enforce application specific constraints on operations that may be executed on the encapsulated instances, e.g., authorization, styling, security checks, ...
If the Property class provides virtual functions for access, derived classes may simply provide whatever their application needs.
Fig 1. CppProperties Class Diagram
Fig 2. Properties Test Code
Fig 3. CppProperties Test Output

2.0 Design

This design of properties uses an inheritance hierarchy, shown in Figure 1., to implement the various design aspects needed for property behaviors, as described in the concept statement, above. The first base class, PropContainer<T>, holds the encapsulated instance t ε T and provides protected virtual methods:
  • virtual void set(const T& t)
  • virtual T& get()
The purpose of these methods is to manage access to the encapsulated type. The PropertyBase<T> class inherits those protected methods and uses them to implement the user interface methods:
  • PropertyBase(const T& t)
  • PropertyBase& operator=(const T& t)
  • void operator()(const T& t)
  • T operator()()

The first three of these methods call void PropContainer<T>::set(const T& t) and the last calls T& PropContainer<T>::get(). Note that the set(const T& t) method copy assigns t to the encapsulated instance, e.g., makes a copy so users can't modify the inner instance through that reference. Also note that the operator()() method returns a copy of the inner instance, for the same reason. For large types, those copies will have preformance implications, but are necessary to ensure application defined constraints on operations are enforced and any changes, for thread-safe properties, happen only in a locked critical section. Since Property<T> is not a T, it doesn't have T's methods. If Property<T> derived from T, that would provide those methods, but then it would be very difficult (maybe impossible) to enforce application constraints and provide consistent logging and thread safety. So, I elected to take the design route described below.
The class PropertyOps<T> provides methods for frequently occurring types:
  • fundamental type operations
  • operations for STL sequential containers
  • operations for STL associative containers
Each of these types require different interfaces, so the PropertyOps<T> class provides specializations for each of these, based on custom type traits: is_fundamental, is_stl_seq_container, and is_stl_assoc_container. The selection of those specializations uses some template metaprogramming constructs provided by C++14 and C++17. The Property.h and CustomContTypeTraits.h files provide some notes about that. Whenever it can, PropertyOps<T> uses the protected PropContainer<T> reference-based methods for performance, only using instance copy operations when it must to maintain thread-safety or application constraints. For types not included in PropertyBase<T>, e.g., Widget, applications can retreive a copy of the encapsulated type using T PropertyBase<T>::operator()(), use the copy's methods, and set the modified instance with void PropertyBase<T>::operator()(const T& t) method. That, obviously has performance and convenience issues, but would probably be worthwhile for multi-threaded environments or applications needing specific logging operations. For frequently used types, a developer may always elect to add another PropertyOps specialization. That will probably be easier than for STL containers, since those class interfaces are likely to be smaller, simpler, and easier to implement.
Three classes derive from PropertyOps<T>:
  • Property<T> imposes no constraints and does no locking.
  • TS_Property<T> provides locking, by overriding empty public lock() and unlock() methods, inserting calls to methods lock() and unlock() in the PropContainer<T> class.
  • Log_Properties<T> provides logging by overriding protected methods void set(const T& t) and T& get() methods.
The Property<T> and TS_Property<T> classes have been implemented and are part of this repository. Log_Property<T> is planned and should appear soon. A final observation about this design: note how property operations are factored into single-responsibility classes, e.g.:
  • PropContainer provides virtual functions to manage its encapsulated instance tεT, providing the flexibility needed by applications to enforce constraints and thread safety.
  • PropBase<T> defines the primary user interface.
  • PropertyOps<T> adds methods for widely used types.
  • The most derived classes Property<T>, TS_Property<T>, and Log_Property<T> simply override PropContainer<T> methods to suit the current application.
PropContainer<T> sets up the flexibility infrastructure, and Property<T>, TS_Property<T>, and Log_Property<T> use that for their application. The Single-Responsibility Principle is the queen - most important - of all the design principles.

3.0 Build

CppProperties was built with Visual Studio Community Edition - 2019 and tested on Windows 10.

4.0 Status

All of the classes except Log-Property<T> have been implemented. The code has not been used in any major application yet, so there may be some latent undetected errors.
Two of the STL containers, std::stack<T> and std::queue<T> are adapters of other containers. They have a special behavior - provide access only to their end or ends. That is, they cannot be iterated and have no begin() and end() methods. They need their own PropertyOps<T> processing. I have not done that yet, so using them will cause compilation failure. Since you can use the std::deque<T> as a stack or queue, I will not do that for awhile.
This facility needs a better demonstration of typical use. I plan to provide that, but I won't get to that for a while. All of this is fairly complex. I probably would not use this facility except for the use-cases cited in the Concept section.
  Next Prev Pages Sections About Keys