Immutability in C++

The guys who work with me know all too well that I have a bit of a thing for functional programming.  Given the slightest opportunity I can ramble on about the advantages of immutable value types and side-effect free functions. But I find that too many introductions to FP focus on abstract problems like generating to Fibonacci series. Interesting, but I don't think too many developers face that challenge on an everyday basis.

So I thought I'd write about applying some FP principles to everyday languages, such as C++. I chose C++ because it's often considered amongst the least functional languages. Many of the same principles can apply in Java or C#, but we'll start here, and we’ll start with immutable value types.

An type is immutable if its value (and for a class instance that means any values of any members) can’t change after it’s been created. This gives us several advantages:

  • No functions that takes a value type as an input can change it, so it reduces side-effects
  • There’s no need to lock an immutable object for access by multiple threads, since no thread can change the value
  • You can use the value of an immutable object as a key in a hashed data structure, since the hash value can’t ever change after creation
  • You can compare value types using simple value equivalence – if two value type instances have the same value, then for all practical purposes they are the same instance
  • Incidentally, it can be easier for C# developers to understand immutability because the System.String class is a good example. All the methods that "modify" a string return a new string – you can’t actually modify the contents of a string (unless you get very clever with reflection, but that’s just breaking the rules).

    Now let's take a C++ example and think about immutability.

    //A non-immutable Employee class
    
    #include 
    
    class EmployeeNF {
    public:
    	//The constructor body is in EmployeeNF.cpp so that
    	//there's something for the linker to use.
    	EmployeeNF(const std::string& aGivenName,
    		   const std::string& aFamilyName,
    		   const int	aNumber,
    		   const double &aSalary);
    
    	void SetNumber(const int aNumber) {
    		number=aNumber;
    	}
    
    	int Number() const {
    		return number;
    	}
    
    	void SetSalary(const double& aSalary) {
    		salary=aSalary;
    	}
    
    	double Salary() const {
    		return salary;
    	}
    
    	void SetGivenName(const std::string& aGivenName) {
    		givenName=aGivenName;
    	}
    
    	std::string GivenName() const {
    		return givenName;
    	}
    
    	void SetFamilyName(const std::string& aFamilyName) {
    		familyName=aFamilyName;
    	}
    
    	std::string FamilyName() const {
    		return familyName;
    	}
    
    private:
    	std::string	givenName;
    	std::string	familyName;
    	int		number;
    	double		salary;
    };

    Here we have a class that any C++ developer might write. It observes some good OO principles: the members are private and all access is via setters and getters. But it’s fully mutable: any code can change any value.

    Now let’s rewrite it so that it’s immutable. Here’s a first draft:

    //An immutable Employee class
    
    #include 
    
    class EmployeeF {
    public:
    	//The constructor body is in EmployeeF.cpp so that
    	//there's something for the linker to use.
    	EmployeeF(const std::string& aGivenName,
    		  const std::string& aFamilyName,
    		  const int	aNumber,
    		  const double&	aSalary);
    
    	const int Number() const {
    		return number;
    	}
    
    	const double& Salary() const {
    		return salary;
    	}
    
    	const std::string& GivenName() const {
    		return givenName;
    	}
    
    	const std::string& FamilyName() const {
    		return familyName;
    	}
    
    private:
    	const std::string	givenName;
    	const std::string	familyName;
    	const int		number;
    	const double		salary;
    };

    Some C++ points to note:

  • The member variables are now all marked const. This means that they can only be initialised in a constructor, using initialization lists. This is the most fundamental way of making them immutable.
  • The getter methods mostly return const references. There are long debates about whether this is a good idea, or prevents compiler optimizations (RVO or NRVO, if you wanted to Google), but I’ve chosen to do to illustrate that since the member variables don’t change, it’s possible to return references to them.
  • The getters return const values – whether this is appropriate depends on design intentions, but it prevents some common errors (such as use of = rather than == in a comparison). It also reflects the fact that we’re dealing with an immutable type.
  • Okay, this gives us immutability, but what if I need to change the salary for an Employee? To do this efficiently, we can write “setters” that copy most of the values for an instance, such as:

    	const EmployeeF SetNumber(const int aNumber) const {
    		return EmployeeF(givenName,familyName,aNumber,salary);
    	}
    
    	const EmployeeF SetSalary(const double& aSalary) const {
    		return EmployeeF(givenName,familyName,number,aSalary);
    	}
    
    

    (Here I’m assuming the default copy constructor will do a good enough job for us – in fact it will lead to duplication of the strings, but avoiding that would be a whole different problem and require strings with reference counting).

    So a “setter” like this allows me to get a new EmployeeF instance that is the same as the one on which I called the method, but with a new value for the number member.

    Let’s dig a little further. How would we use such an immutable type?

    //Let's create our first EmployeeF
    EmployeeF e("Ben","Last",1,1000.0);
    
    //Great, let's now change the Number
    EmployeeF e2 = e.SetNumber(2);
    
    //Now let's alter the salary too
    e2 = e2.SetSalary(1100.00);

    But if you try this… that line won’t compile. Why? Because you’re trying to modify e2, and e2 is an immutable type. The compiler will try and build you a default operator= and it’ll fail, because all the member fields of e2 are const. Instead, you have to adopt a functional programming pattern and use variables that you don’t modify:

    //Let's create our first EmployeeF
    const EmployeeF e("Ben","Last",1,1000.0);
    
    //Great, let's now change the Number
    const EmployeeF e2 = e.SetNumber(2);
    
    //Now let's alter the salary too
    const EmployeeF e3 = e2.SetSalary(1100.00);

    Every time we make a change to an immutable EmployeeF, we need a new variable to hold the new value. This isn’t actually a bad thing. A basic principle of functional programming is that you don’t modify state. Your code takes values and applies transformations that result in new values – it doesn’t modify the original ones. And that’s exactly what this immutable class forces you to do, and why I declared the variables themselves as const in that example above.

    Okay, so what have I tried to show you here?

  • C++ supports you in defining types that are immutable once created
  • A key principle of immutable types is that functions operating on them return new values and leave the old ones unmodified
  • Variables holding immutable types are also immutable
  • And that’ll do for today…

    Advertisements