8

Is every object in C++ mutable if not stated otherwise?

In Python and Javascript I can't change strings, tuple, unicodes. I was wondering if there is something in C++ that is immutable or every object is mutable and I have to use const type qualifier to make it immutable.

6
  • 2
    what do you mean with "by default"? Jan 10, 2017 at 10:02
  • That all the objects in C++ are mutable if not stated differently.
    – G M
    Jan 10, 2017 at 10:07
  • 1
    Yes they are. Note this is not the same as the "mutable" keyword.
    – Olav
    Jan 10, 2017 at 10:20
  • 3
    The question in the question body is a different question from the question in the title?
    – user253751
    Jan 10, 2017 at 10:38
  • @immibis it was only to add a litle bit of contest
    – G M
    Jan 10, 2017 at 10:47

3 Answers 3

20

Immutability has been well-understood for some time. Python, Java, and C++ have different memory models that make direct comparisons difficult. The author of the article you originally cited (permalink) doesn't seem to know C++.

As in Python, Java, and most multi-paradigm languages, C and C++ allow mutability by default. This is what programmers usually want: if I have an String x variable, I want to be able to assign a new value x = "foo".

The const-system in C and C++ allows a great deal of nuanced immutability that is lacking in Python, Java, and even Scala. If a C++ function takes a const std::string& or a const char*, it promises (and the compiler ensures, to some degree), that this function will not (cannot!) change the contents of that string. Given a const object, we can only invoke methods of that object that are also marked as const. If a C++ class only has public members which are const, then the object is effectively immutable.

However, this is sometimes confusing as in C and C++ objects are memory locations, and variables are names for memory locations. In contrast, variables in Python and Java are names for pointers to objects. In a language with reference semantics, x = y means “make x point to the same object as y”. Since we are only copying pointers, this is possible with immutable objects. In a language with value semantics like C++, it means “update the contents of x with the contents of y”. Therefore, if reassignment of a variable is desired in C or C++, the variable may not have a const type. To do this with immutable objects, we would have to use a pointer explicitly.

That Java and Python use immutable string objects is a fundamental design decision, but it is not directly connected to the benefits of immutability in a multithreading environment. One reason is that string literals in the source code can be pooled which reduces the number of objects. This is possible in C/C++ as well. In C++ the literal "foo" has type const char[4] (the 4th char is the terminating '\0'). Another reason is that entries in sets and keys in dicts/maps must not be changed. Since strings are used pervasively as dict keys (most Python objects are a dict), immutability removes a common source of errors. In Java, another reason for immutable strings is the Java security model. All of these reasons are entirely unrelated to multithreading.

If Java were built with immutability in mind, the language would have looked very differently. While it is closely inspired by C++, the designers tried hard to create a much simpler language, getting rid of const is one such step. The equivalent Java thing to a C++ const reference is an adapter or decorator that implements any mutating methods as throws new NotImplementedException(), and forwards non-mutating method calls to the actual collection. The fact that the java.util collection interfaces all imply mutability is a clear sign they didn't strive for an immutability-first language.

The solution Java put forward to solve concurrency problems wasn't immutability, but pervasive locking. Every single object contains a mutex that may be used for synchronized blocks or entire methods. As it turns out, that is not good for performance, does not scale very well, and is quite error prone – you still have to deal with mutable global state.

6
  • 13
    This is what programmers usually want => that's quite the controversial claim. My experience is actually the opposite: most local variables are immutable, with the occasionally mutable one. Languages such as Rust (where mutability requires a mut keyword, and where unnecessary mutability is flagged by the compiler) tend to support my hypothesis: in Rust, you have much more let bindings than you have let mut ones. Jan 10, 2017 at 13:12
  • 1
    @MatthieuM.: That's partially because for x in 1..10 doesn't use let mut to define the loop variable, but obviously it requires some mutability (at loop level only, not inside the loop body).
    – MSalters
    Jan 10, 2017 at 13:20
  • @MSalters: Of course, but C++ has range-for syntax too :) Jan 10, 2017 at 15:22
  • 4
    @MSalters I would characterise that as binding 10 variables called x with 10 values, not mutating one
    – Caleth
    Jan 10, 2017 at 16:02
  • 1
    @MatthieuM. I agree that immutability is nice, but in C++ it's simply not always an option, or at least excessively awkward. E.g. initialising a const variable in multiple steps requires us to do that in a separate function where that object isn't yet const. At least C++11 let's us use immediately-invoked lambdas like const T o = [&]{T o; o.init(...); return o;}(); … yay for APIs that weren't designed with immutability in mind.
    – amon
    Jan 10, 2017 at 17:58
11

Almost. The language itself treats everything as mutable unless you use const, but it's still possible to build immutable objects without telling the compiler they're immutable, by simply not providing a way to mutate them.

For example, take this class:

class MyClass {
    int value;
public:

    MyClass(int v) : value(v) {}
    int getValue() {return value;}

    MyClass &operator=(const MyClass &other) = delete;
};

Instances of MyClass are immutable, because there's no way to mutate them - but nowhere in the code is it actually stated they are immutable. (This is the same way that instances of Java's String class are immutable)

5
  • (I'm getting rusty) Does deleteing the copy-assignment operator here also ensure one cannot assign from an rvalue reference via the move-assignment op? Jan 10, 2017 at 15:32
  • And does destroying the object with an explicit destructor call and then re-creating it with placement new count as "mutating"? Jan 10, 2017 at 16:02
  • 1
    @underscore_d It counts as user-defining the operator for the purposes of not implicitly adding a move assignment, yes
    – Caleth
    Jan 10, 2017 at 16:11
  • I wonder whether a mechanism to let the compiler know that objects of a certain class are always immutable could lead to better optimization. For example it would be unnecessary to refresh cashed objects. In this particular example (and generally in classes following this pattern) it would probably help to declare value const, and to declare getValue() a const function. Jan 10, 2017 at 16:25
  • 1
    @PeterGreen No, that counts as destroying the object and creating a new one.
    – user253751
    Jan 11, 2017 at 0:50
2

No, there is one exception: Lambdas and by extension the objects they capture are immutable by default:

int i = 0;

[i]{//capturing the value i
    i++; //does not compile, i is const
};

[i]() mutable{ //make the lambda mutable
    i++; //compiles fine
};

So instead of adding const to make it immutable you add mutable to make it not const.

4
  • Only capturing lambdas are immutable by default since C++20, non capturing lambdas have defaulted assignment in C++20
    – Caleth
    Mar 25, 2022 at 11:29
  • The values lambdas capture are just copies of the original and are mutable. Function parameters are also just copies of the original and are mutable. It's just that you can't do anything with the copy. Mar 28, 2022 at 5:11
  • "The values lambdas capture are just copies of the original and are mutable." The standard disagrees: "The function call operator or operator template is declared const ([class.mfct.non.static]) if and only if the lambda-expression's parameter-declaration-clause is not followed by mutable and the lambda-declarator does not contain an explicit object parameter."
    – nwp
    Mar 28, 2022 at 10:53
  • I guess you could argue that i is not const and that only the operator() is const which then affects i. So more specifically not the lambda or its members are const by default but the operator().
    – nwp
    Mar 28, 2022 at 10:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.