Why is const-correctness specific to C++?

2020-02-16 12:15发布

Disclaimer: I am aware that there are two questions about the usefulness of const-correctness, however, none discussed how const-correctness is necessary in C++ as opposed to other programming languages. Also, I am not satisfied with the answers provided to these questions.

I've used a few programming languages now, and one thing that bugs me in C++ is the notion of const-correctness. There is no such notion in Java, C#, Python, Ruby, Visual Basic, etc., this seems to be very specific to C++.

Before you refer me to the C++ FAQ Lite, I've read it, and it doesn't convince me. Perfectly valid, reliable programs are written in Python all the time, and there is no const keyword or equivalent. In Java and C#, objects can be declared final (or const), but there are no const member functions or const function parameters. If a function doesn't need to modify an object, it can take an interface that only provides read access to the object. That technique can equally be used in C++. On the two real-world C++ systems I've worked on, there was very little use of const anywhere, and everything worked fine. So I'm far from sold on the usefulness of letting const contaminate a codebase.

I am wondering what is it in C++ that makes const necessary, as opposed to other programming languages.

So far, I've seen only one case where const must be used:

#include <iostream>

struct Vector2 {
    int X;
    int Y;
};

void display(/* const */ Vector2& vect) {
    std::cout << vect.X << " " << vect.Y << std::endl;
}

int main() {
    display(Vector2());
}

Compiling this with const commented out is accepted by Visual Studio, but with warning C4239, non-standard extension used. So, if you want the syntactic brevity of passing in temporaries, avoiding copies, and staying standard-compliant, you have to pass by const reference, no way around it. Still, this is more like a quirk than a fundamental reason.

Otherwise, there really is no situation where const has to be used, except when interfacing with other code that uses const. Const seems to me little else than a self-righteous plague that spreads to everything it touches :

The reason that const works in C++ is because you can cast it away. If you couldn't cast it away, then your world would suck. If you declare a method that takes a const Bla, you could pass it a non-const Bla. But if it's the other way around you can't. If you declare a method that takes a non-const Bla, you can't pass it a const Bla. So now you're stuck. So you gradually need a const version of everything that isn't const, and you end up with a shadow world. In C++ you get away with it, because as with anything in C++ it is purely optional whether you want this check or not. You can just whack the constness away if you don't like it.

Anders Hejlsberg (C# architect), CLR Design Choices

14条回答
▲ chillily
2楼-- · 2020-02-16 12:32

Anders Hejlsberg (C# architect): ... If you declare a method that takes a non-const Bla, you can't pass it a const Bla. So now you're stuck. So you gradually need a const version of everything that isn't const, and you end up with a shadow world.

So again: if you started to use "const" for some methods you usually forced to use this in most of your code. But the time spent for maintaining (typing, recompiling when some const is missing, etc.) of const-correctness in code seems greater than for fixing of possible (very rare) problems caused by not using of const-correctness at all. Thus lack of const-correctness support in modern languages (like Java, C#, Go, etc.) might result in slightly reduced development time for the same code quality.

An enhancement request ticket for implementing const correctness existed in the Java Community Process since 1999, but was closed in 2005 due to above mentioned "const pollution" and also compatibility reasons: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4211070

Although C# language has no const correctness construct but similar functionality possibly will appear soon in "Microsoft Code Contracts" (library + static analysis tools) for .NET Framework by using [Pure] and [Immutable] attributes: Pure functions in C#

查看更多
趁早两清
3楼-- · 2020-02-16 12:33

This talk and video from Herb Sutter explains the new connotations of const with regards to thread-safety.

Constness might not have been something you had to worry about too much before but with C++11 if you want to write thread-safe code you need to understand the significance of const and mutable

查看更多
戒情不戒烟
4楼-- · 2020-02-16 12:37

In C, Java and C# you can tell by looking at the call site if a passed object can be modified by a function:

  • in Java you know it definitely can be.
  • in C you know it only can be if there is a '&', or equivalent.
  • in c# you need to say 'ref' at the call site too.

In C++ in general you can't tell this, as a non-const reference call looks identical to pass-by-value. Having const references allows you to set up and enforce the C convention.

This can make a fairly major difference in readability of any code that calls functions. Which is probably enough to justify a language feature.

查看更多
戒情不戒烟
5楼-- · 2020-02-16 12:40

const is a way for you to express something. It would be useful in any language, if you thought it was important to express it. They don't have the feature, because the language designers didn't find them useful. If the feature was there, it would be about as useful, I think.

I kind of think of it like throw specifications in Java. If you like them, you would probably like them in other languages. But the designers of the other languages didn't think it was that important.

查看更多
该账号已被封号
6楼-- · 2020-02-16 12:42

Well, it will have taken me 6 years to really understand, but now I can finally answer my own question.

The reason C++ has "const-correctness" and that Java, C#, etc. don't, is that C++ only supports value types, and these other languages only support or at least default to reference types.

Let's see how C#, a language that defaults to reference types, deals with immutability when value types are involved. Let's say you have a mutable value type, and another type that has a readonly field of that type:

struct Vector {
    public int X { get; private set; }
    public int Y { get; private set; }
    public void Add(int x, int y) {
        X += x;
        Y += y;
    }
}

class Foo {
    readonly Vector _v;
    public void Add(int x, int y) => _v.Add(x, y);
    public override string ToString() => $"{_v.X} {_v.Y}";
}

void Main()
{
    var f = new Foo();
    f.Add(3, 4);
    Console.WriteLine(f);
}

What should this code do?

  1. fail to compile
  2. print "3, 4"
  3. print "0, 0"

The answer is #3. C# tries to honor your "readonly" keyword by invoking the method Add on a throw-away copy of the object. That's weird, yes, but what other options does it have? If it invokes the method on the original Vector, the object will change, violating the "readonly"-ness of the field. If it fails to compile, then readonly value type members are pretty useless, because you can't invoke any methods on them, out of fear they might change the object.

If only we could label which methods are safe to call on readonly instances... Wait, that's exactly what const methods are in C++!

C# doesn't bother with const methods because we don't use value types that much in C#; we just avoid mutable value types (and declare them "evil", see 1, 2).

Also, reference types don't suffer from this problem, because when you mark a reference type variable as readonly, what's readonly is the reference, not the object itself. That's very easy for the compiler to enforce, it can mark any assignment as a compilation error except at initialization. If all you use is reference types and all your fields and variables are readonly, you get immutability everywhere at little syntactic cost. F# works entirely like this. Java avoids the issue by just not supporting user-defined value types.

C++ doesn't have the concept of "reference types", only "value types" (in C#-lingo); some of these value types can be pointers or references, but like value types in C#, none of them own their storage. If C++ treated "const" on its types the way C# treats "readonly" on value types, it would be very confusing as the example above demonstrates, nevermind the nasty interaction with copy constructors.

So C++ doesn't create a throw-away copy, because that would create endless pain. It doesn't forbid you to call any methods on members either, because, well, the language wouldn't be very useful then. But it still wants to have some notion of "readonly" or "const-ness".

C++ attempts to find a middle way by making you label which methods are safe to call on const members, and then it trusts you to have been faithful and accurate in your labeling and calls methods on the original objects directly. This is not perfect - it's verbose, and you're allowed to violate const-ness as much as you please - but it's arguably better than all the other options.

查看更多
别忘想泡老子
7楼-- · 2020-02-16 12:44

I don't think anybody claims const-correctness is "necessary". But again, classes are not really necessary either, are they? The same goes for namespaces, exceptions,... you get the picture.

Const-correctness helps catch errors at compile time, and that's why it is useful.

查看更多
登录 后发表回答