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.

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

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

  • 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. – Matthieu M. Jan 10 '17 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 '17 at 13:20
  • @MSalters: Of course, but C++ has range-for syntax too :) – Matthieu M. Jan 10 '17 at 15:22
  • 4
    @MSalters I would characterise that as binding 10 variables called x with 10 values, not mutating one – Caleth Jan 10 '17 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 '17 at 17:58
9

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)

  • (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? – underscore_d Jan 10 '17 at 15:32
  • And does destroying the object with an explicit destructor call and then re-creating it with placement new count as "mutating"? – Peter Green Jan 10 '17 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 '17 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. – Peter A. Schneider Jan 10 '17 at 16:25
  • 1
    @PeterGreen No, that counts as destroying the object and creating a new one. – immibis Jan 11 '17 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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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