A value object is a small object whose equality is based on state, but not identity. Typical value objects are money, numbers, or strings.
The term Value Object goes back to the seminal book Domain-Driven Design (DDD) by Eric Evans. But, what is a value object? Eric gave the answer in his book:
An object that represents a descriptive aspect of the domain with no conceptual identity is called a Value Object. Value Objects are instantiated to represent elements of the design that we care about only for what they are, not who or which they are.
If this is too formal for you, here is a nice example of the author:
When a child is drawing, he cares about the color of the marker he chooses, and he may care about the sharpness of the tip. But if there are two markers of the same color and shape, he probably won’t care which one he uses. If a marker is lost and replaced by another of the same color from a new pack, he can resume his work unconcerned about the switch.
The key term in the formal definition and the example about the Value Object is equality.
In general, we have two types of equality: reference equality and value equality. For simplicity reasons, I will ignore id-based equality.
- Reference equality: two objects are considered to be equal if they reference the same entity in the memory.
- Value equality: two objects are considered to be equal if all their member have the same value.
Let me switch to Python because Python makes it easy to compare both kinds of equality.
The short example in the Python shell should make the difference between reference equality and value equality clear.
First, I define two lists
list2 with the same elements. When I compare their identity (
list1 is list2), they are different. When I compare their values, they are identical. Python uses for equality (and non-equality) comparison of the memory address of the compared objects. The
id operator (
id(liste1)) returns a decimal representation of its hexadecimal memory address. By assigning
list3, both lists refer to the same memory location. Consequentially,
id(list3) is identical to
id(list1), and the call
list3 returns T
What does this mean for modern C++20? In C++20, the compiler can generate the equality operator.
Compiler-Generated Equality Operator
For a user-defined type, you have to choose the appropriate equality semantics.
In C++20, the compiler can auto-generate the equality operator and use it as a fallback for the inequality operator. The auto-generated equality operator applies value equality. To be more precise, the compiler-generated equality operator performs a lexicographical comparison. Lexicographical comparison means that all base classes are compared left to right and all nonstatic members of the class in their declaration order.
I have to add two important points:
- For strings or vectors, there is a shortcut: the compiler-generated == and != operators compare first their lengths and then their content if necessary.
- The compiler-generated three-way comparison operator (
<=>) also applies value equality. You can read more about the three-way comparison operator in my previous posts:
Honestly, the example works as expected but does not seem right.
Two dates with identical values should be regarded as equal but not two men. The equality of two men should be based on their identity and not on their name and age.
Let me discuss the details about Value Objects.
After the last chapter, this should be obvious. The equality of a Value Object should be based on its state and not on its identity.
A Value Object should not be mutable. This makes Value Objects ideal candidates for concurrency. Changing a Value Object means creating a new one with the modified attributes. This property, that an operation on an immutable object returns a new object has two excellent properties. For conciseness, I use Python once more.
In Python, a string is immutable:
- You simulate modification, by assigning the new value to the old name: s = s.upper()
sand the new
- An operation on the string returns a new string. Consequentially, you can chain string operations. In the function domain, this pattern is called a fluent interface. By the way: arithmetic expressions such as
(5 +5) * 10 - 20are based on the fluent interface. Each operation returns a temporary, on which you can apply the next operation. Of course, numbers are Value Objects.
A Value Object should validate its attributes when created. For simplicity, I skipped this step in my previous
What are the Pros and Cons of Value Objects:
Pros and Cons
The pros of Value Objects overweight their cons heavily.
You should use rich types instead of built-in types for dealing with primitive values. This has many implications. Let me compare the following two representations of a date:
- You cannot create an invalid date
Date(2022, 15, 5), because the constructor’s job is it to validate the input. This does not hold for the string value “
2022 15 5", because someone mixed up the month and the day.
- Your program is easier to read. It is crystal clear from class
Datedocumentation, what each component stands for.
- You can overload operators on
Date. E.g.: subtracting two dates returns a time duration. A time duration should also be a Value Object.
- You can extend your Value Objects with user-defined literals for a day, a year, and a month. In this case, it is not necessary because we have them with C++20: std::chrono::duration on cppreference.com.
Value Objects are immutable. Consequentially, they give the optimizer additional guarantees and can be shared between threads without synchronization.
Proliferation of Classes
Only for the sake of arguments: You may end with too many small classes representing Value Objects.
A Null Object encapsulates a do nothing behavior inside an object. Let me show you in my next post the advantages of Null Objects.
Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Matthieu Bolt, Stephen Kelley, Kyle Dean, Tusar Palauri, Dmitry Farberov, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, and Marco Parri Empoli.
Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.
|My special thanks to Embarcadero|
|My special thanks to PVS-Studio|
|My special thanks to Tipi.build|
|My special thanks to Take Up Code|
I’m happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.
- Embedded Programmierung mit modernem C++ 12.12.2023 – 14.12.2023 (Präsenzschulung, Termingarantie)
Standard Seminars (English/German)
Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.
- C++ – The Core Language
- C++ – The Standard Library
- C++ – Compact
- C++11 and C++14
- Concurrency with Modern C++
- Design Pattern and Architectural Pattern with C++
- Embedded Programming with Modern C++
- Generic Programming (Templates) with C++
- Clean Code with Modern C++
- Phone: +49 7472 917441
- Mobil:: +49 176 5506 5086
- Mail: schulung@ModernesCpp.de
- German Seminar Page: www.ModernesCpp.de
- Mentoring Page: www.ModernesCpp.org
Modernes C++ Mentoring,