There is a simple yet powerful concept in programming that remains widely underused: immutability.
Basically, an object is immutable if its state doesn’t change once the object has been created. Consequently, a class is immutable if its instances are immutable.
There is one important argument in favor of using immutable objects: it dramatically simplifies concurrent programming. Think about it: why is writing correct multithreaded code so difficult? Because it is hard to synchronize threads’ access to resources (objects or other OS resources). Why is synchronizing this access difficult? Because it is hard to guarantee that there won’t be race conditions between multiple read and write operations performed by multiple threads on multiple objects. What if there were no more write accesses? In other words, what if the state of the objects accessed by threads didn’t change? There would no longer be a need for synchronization!
The class of the referenced object is immutable if it has no public fields, no methods that can change its internal data, and no way for methods of derived classes to change its internal data. And because the value cannot change, it’s possible to reference the same object in all cases. There’s no need for a copy constructor or assignment operator. For this reason, it’s recommended to make the copy constructor and assignment operator private, inherit from boost::noncopyable, or use the new C++11 feature “explicitly defaulted and deleted special member functions”.
Even if a C++ class is not designed to be immutable, adding the const keyword to variables of its type will work. However, this solution has some drawbacks:
- You have to add const to each variable declaration you want to be immutable; forgetting it sometimes will generate unsuspected bugs in a multithreading context.
- Even if a variable is declared as const, it might still be possible to mutate the object within a const method if some fields are declared mutable. In such a case, the object’s state can still be modified, which means the object is mutable.
For example, the String class is well known for being immutable in several other languages, such as C#, Java, and D. Strings are widely used by nature, and making them immutable is particularly suitable for multithreaded environments. In C++, std::string is not immutable, so the alternative is to use the const keyword.
Domain-Driven Design and Immutability
Domain-driven design is a software design approach based on two premises:
- That complex domain designs should be based on a model, and
- That for most software projects, the primary focus should be on the domain and domain logic (as opposed to the particular technology used to implement the system).
In other words, the heart of DDD is the model, and one of the first things to do when starting development is to define the model. The model and the design you create should shape each other. The model should represent knowledge of the business domain.
In general, data shared between threads involves model entities, and making those entities immutable helps eliminate side effects. I couldn’t say it better than Wes Dyer, so I quote him:
We all know that generally, it is not a good idea to use global variables. This is basically the extreme of exposing side-effects (the global scope). Many of the programmers who don’t use global variables don’t realize that the same principles apply to fields, properties, parameters, and variables on a more limited scale: don’t mutate them unless you have a good reason.(…)One way to increase the reliability of a unit is to eliminate the side-effects. This makes composing and integrating units together much easier and more robust. Since they are side-effect free, they always work the same no matter the environment. This is called referential transparency.
Another benefit of immutable classes is that they cannot violate the Liskov Substitution Principle (LSP). Here is a definition of LSP quoted from its Wikipedia page:
Liskov’s notion of a behavioral subtype defines a notion of substitutability for mutable objects; that is, if S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program (e.g., correctness).For immutable objects, the state can’t change, so the properties can’t be altered.
Detect Immutable Classes in C++ Source Code
Immutability is a feature that can be enforced at compile time — in other words, it can be enforced by static analysis tools. CQLinq, which comes with the static analysis tool CppDepend, provides an IsImmutable condition that applies to types. To find out which types in your codebase are immutable:
from t in Types where t.IsImmutable select tBesides the IsImmutable condition on types, we can also use two interesting conditions:
ChangesObjectState and ChangesTypeState. As their names suggest, ChangesObjectState matches methods that assign to an instance field of their class, while ChangesTypeState matches methods that assign to a static field of their class.
The ChangesObjectState condition matches methods that assign to an instance field of their parent type.
The ChangesTypeState condition matches methods that assign to a static field of their parent type.
These two conditions make it easy to identify methods that can change the state of the program — in other words, which methods cause side effects.
A method that does not cause side effects is typically referred to as a pure method. To match pure methods with CQLinq, you can write:
from m in Methods
where !m. ChangesObjectState && !m. ChangesTypeState && !m.IsConstructor
select m
Conclusion
Eliminating side effects makes composing and integrating units together much easier and more robust, and simplifies multithreaded programming. Immutable classes and pure methods are the key to eliminating side effects.
