blob: e83fe846c7967a7e9622dbbb7e86918e62008dda [file] [log] [blame] [view]
Peter Kasting5e04bd32019-05-21 21:44:101# Using Const Correctly
2
3The **TLDR**, as stated in [C++ Dos and Don'ts](c++-dos-and-donts.md):
4*** promo
5For safety and simplicity, **don't return pointers or references to non-const
6objects from const methods**. Within that constraint, **mark methods as const
7where possible**. **Avoid `const_cast` to remove const**, except when
8implementing non-const getters in terms of const getters.
9***
10
11## A brief primer on const
12
13To the compiler, the `const` qualifier on a method refers to _physical
14constness_: calling this method does not change the bits in this object. What
15we want is _logical constness_, which is only partly overlapping: calling this
16method does not affect the object in ways callers will notice, nor does it give
17you a handle with the ability to do so.
18
19Mismatches between these concepts can occur in both directions. When something
20is logically but not physically const, C++ provides the `mutable` keyword to
21silence compiler complaints. This is valuable for e.g. cached calculations,
22where the cache is an implementation detail callers do not care about. When
23something is physically but not logically const, however, the compiler will
24happily accept it, and there are no tools that will automatically save you.
25This discrepancy usually involves pointers. For example,
26
27```cpp
28void T::Cleanup() const { delete pointer_member_; }
29```
30
31Deleting a member is a change callers are likely to care about, so this is
32probably not logically const. But because `delete` does not affect the pointer
33itself, but only the memory it points to, this code is physically const, so it
34will compile.
35
36Or, more subtly, consider this pseudocode from a node in a tree:
37
38```cpp
39class Node {
40 public:
41 void RemoveSelf() { parent_->RemoveChild(this); }
42 void RemoveChild(Node* node) {
43 if (node == left_child_)
44 left_child_ = nullptr;
45 else if (node == right_child_)
46 right_child_ = nullptr;
47 }
48 Node* left_child() const { return left_child_; }
49 Node* right_child() const { return right_child_; }
50
51 private:
52 Node* parent_;
53 Node* left_child_;
54 Node* right_child_;
55};
56```
57
58The `left_child()` and `right_child()` methods don't change anything about
59`|this|`, so making them `const` seems fine. But they allow code like this:
60
61```cpp
62void SignatureAppearsHarmlessToCallers(const Node& node) {
63 node.left_child()->RemoveSelf();
64 // Now |node| has no |left_child_|, despite having been passed in by const ref.
65}
66```
67
68The original class definition compiles, and looks locally fine, but it's a
69timebomb: a const method returning a handle that can be used to change the
70system in ways that affect the original object. Eventually, someone will
71actually modify the object, potentially far away from where the handle is
72obtained.
73
74These modifications can be difficult to spot in practice. As we see in the
75previous example, splitting related concepts or state (like "a tree") across
76several objects means a change to one object affects the behavior of others.
77And if this tree is in turn referred to by yet more objects (e.g. the DOM of a
78web page, which influences all sorts of other data structures relating to the
79page), then small changes can have visible ripples across the entire system. In
80a codebase as complex as Chromium, it can be almost impossible to reason about
81what sorts of local changes could ultimately impact the behavior of distant
82objects, and vice versa.
83
84"Logically const correct" code assures readers that const methods will not
85change the system, directly or indirectly, nor allow callers to easily do so.
86They make it easier to reason about large-scale behavior. But since the
87compiler verifies physical constness, it will not guarantee that code is
88actually logically const. Hence the recommendations here.
89
90## Classes of const (in)correctness
91
92In a
93[larger discussion of this issue](https://groups.google.com/a/chromium.org/d/topic/platform-architecture-dev/C2Szi07dyQo/discussion),
94Matt Giuca
95[postulated three classes of const(in)correctness](https://groups.google.com/a/chromium.org/d/msg/platform-architecture-dev/C2Szi07dyQo/lbHMUQHMAgAJ):
96
97* **Const correct:** All code marked "const" is logically const; all code that
98 is logically const is marked "const".
99* **Const okay:** All code marked "const" is logically const, but not all code
100 that is logically const is marked "const". (Basically, if you see "const" you
101 can trust it, but sometimes it's missing.)
102* **Const broken:** Some code marked "const" is not logically const.
103
104The Chromium codebase currently varies. A significant amount of Blink code is
105"const broken". A great deal of Chromium code is "const okay". A minority of
106code is "const correct".
107
108While "const correct" is ideal, it can take a great deal of work to achieve.
109Const (in)correctness is viral, so fixing one API often requires a yak shave.
110(On the plus side, this same property helps prevent regressions when people
111actually use const objects to access the const APIs.)
112
113At the least, strive to convert code that is "const broken" to be "const okay".
114A simple rule of thumb that will prevent most cases of "const brokenness" is for
115const methods to never return pointers to non-const objects. This is overly
116conservative, but less than you might think, due to how objects can transitively
117affect distant, seemingly-unrelated parts of the system. The discussion thread
118linked above has more detail, but in short, it's hard for readers and reviewers
119to prove that returning pointers-to-non-const is truly safe, and will stay safe
120through later refactorings and modifications. Following this rule is easier
121than debating about whether individual cases are exceptions.
122
123One way to ensure code is "const okay" would be to never mark anything const.
124This is suboptimal for the same reason we don't choose to "never write comments,
125so they can never be wrong". Marking a method "const" provides the reader
126useful information about the system behavior. Also, despite physical constness
127being different than logical constness, using "const" correctly still does catch
128certain classes of errors at compile time. Accordingly, the
129[Google style guide requests the use of const where possible](http://google.github.io/styleguide/cppguide.html#Use_of_const),
130so mark methods const when they are logically const.
131
132Making code more const correct leads to cases where duplicate const and non-const getters are required:
133
134```cpp
135const T* Foo::GetT() const { return t_; }
136T* Foo::GetT() { return t_; }
137```
138
139If the implementation of GetT() is complex, there's a
140[trick to implement the non-const getter in terms of the const one](https://stackoverflow.com/questions/123758/how-do-i-remove-code-duplication-between-similar-const-and-non-const-member-func/123995#123995),
141courtesy of _Effective C++_:
142
143```cpp
Peter Kastingd79e8862021-04-13 23:34:47144T* Foo::GetT() { return const_cast<T*>(base::as_const(*this).GetT()); }
Peter Kasting5e04bd32019-05-21 21:44:10145```
146
147While this is a mouthful, it does guarantee the implementations won't get out of
148sync and no const-incorrectness will occur. And once you've seen it a few times,
149it's a recognizable pattern.
150
151This is probably the only case where you should see `const_cast` used to remove
152constness. Its use anywhere else is generally indicative of either "const
153broken" code, or a boundary between "const correct" and "const okay" code that
154could change to "const broken" at any future time without warning from the
155compiler. Both cases should be fixed.