ClassIdioms

Value Objects

A value object is a small object whose equality is based on state, but not identity. Typical value objects are money, numbers, or strings.

ClassIdioms

 

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.

Identity

In general, we have two types of equality: reference equality and value equality. For simplicity reasons, I will ignore id-based equality.

Rainer D 6 P2 500x500

 

Rainer D 6 P2 500x500Modernes C++ Mentoring

  • "Fundamentals for C++ Professionals" (open)
  • "Design Patterns and Architectural Patterns with C++" (open)
  • "C++20: Get the Details" (open)
  • "Concurrency with Modern C++" (open)
  • "Generic Programming (Templates) with C++": October 2024
  • "Embedded Programming with Modern C++": October 2024
  • "Clean Code: Best Practices for Modern C++": March 2025
  • Do you want to stay informed: Subscribe.

     

    • 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.

    EqualityPython

    The short example in the Python shell should make the difference between reference equality and value equality clear.

    First, I define two lists list1 and 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 list1 to list3, both lists refer to the same memory location. Consequentially, id(list3) is identical to id(list1), and the call list1 is list3 returns True.

    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.

    // equalityReferenceValue.cpp
    
    #include <iostream>
    
    class Date{
     public:
        Date(int y, int m, int d): year(y), month(m), day(d){}
        bool operator==(const Date&) const = default;
     private:
        int year;
        int month;
        int day;
    };
    
    class Man{
     public:
        Man(const std::string n, int a): name(n), age(a){}
        bool operator==(const Man&) const = default;
     private:
        std::string name;
        int age;
    };
    
    int main() {
    
        std::cout << std::boolalpha << '\n';
    
        Date date1(2022, 10, 31);
        Date date2(2022, 10, 31);
    
        std::cout << "date1 == date2: " << (date1 == date2) << '\n';
        std::cout << "date1 != date2: " << (date1 != date2) << '\n';
    
        std::cout << '\n';
    
        Man man1("Rainer Grimm", 56);
        Man man2("Rainer Grimm", 56);
    
        std::cout << "man1 == man2: " << (man1 == man2) << '\n';
        std::cout << "man1 != man2: " << (man1 != man2) << '\n';
    
        std::cout << '\n';
    
    }
    

     

    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:

    Honestly, the example works as expected but does not seem right.

     equalityReferenceValue

    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.

    Value Objects

    Let me discuss the details about Value Objects.

    Properties

    Value Equality

    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.

    Immutability

    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:

    immutable

    1. You simulate modification, by assigning the new value to the old name: s = s.upper(). The original s and the new s have different addresses.
    2. 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 - 20 are based on the fluent interface. Each operation returns a temporary, on which you can apply the next operation. Of course, numbers are Value Objects.

    Self-Validation

    A Value Object should validate its attributes when created. For simplicity, I skipped this step in my previous Date class.

    What are the Pros and Cons of Value Objects:

    Pros and Cons

    The pros of Value Objects overweight their cons heavily.

    Rich Types

    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:

    Date date1(2022, 10, 5);
    
    std::string date2 = "2022 10 5";  
    

     

    • 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 Date documentation, 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.

    Performance

    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.

    What’s Next?

    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, 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, Stephen Kelley, Kyle Dean, Tusar Palauri, 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, Marco Parri Empoli, Philipp Lenk, Charles-Jianye Chen, Keith Jeffery, Matt Godbolt, and Honey Sukesan.

    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
    My special thanks to SHAVEDYAKS

    Modernes C++ GmbH

    Modernes C++ Mentoring (English)

    Do you want to stay informed about my mentoring programs? Subscribe Here

    Rainer Grimm
    Yalovastraße 20
    72108 Rottenburg

    Mobil: +49 176 5506 5086
    Mail: schulung@ModernesCpp.de
    Mentoring: www.ModernesCpp.org

    Modernes C++ Mentoring,

     

     

    0 replies

    Leave a Reply

    Want to join the discussion?
    Feel free to contribute!

    Leave a Reply

    Your email address will not be published. Required fields are marked *