@ -600,7 +600,8 @@ Ideally we catch all errors (that are not errors in the programmer's logic) at e
void g(int n)
{
f(new int[n]); // bad: the number of elements is not passed to f()
// bad: the number of elements is not passed to f()
f(new int[n]);
}
Here, a crucial bit of information (the number of elements) has been so thoroughly "obscured" that static analysis is probably rendered infeasible and dynamic checking can be very difficult when `f()` is part of an ABI so that we cannot "instrument" that pointer. We could embed helpful information into the free store, but that requires global changes to a system and maybe to the compiler. What we have here is a design that makes error detection very hard.
@ -625,9 +626,10 @@ Also, it is implicit that `f2()` is supposed to `delete` its argument (or did th
The standard library resource management pointers fail to pass the size when they point to an object:
// NB: this assumes the calling code is ABI-compatible, using a
// compatible C++ compiler and the same stdlib implementation
void g3(int n)
{
vector<int> v(n);
f4(v); // pass a reference, retain ownership
f4(span<int>{v}); // pass a view, retain ownership
f4(span<int>{v}); // pass a view, retain ownership
}
This design carries the number of elements along as an integral part of an object, so that errors are unlikely and dynamic (run-time) checking is always feasible, if not always affordable.
@ -1565,8 +1567,9 @@ By stating the intent in source, implementers and tools can provide better diagn
The assumption that the pointer to `char` pointed to a C-style string (a zero-terminated string of characters) was still implicit, and a potential source of confusion and errors. Use `zstring` in preference to `const char*`.
int length(not_null<zstring> p); // we can assume that p cannot be nullptr
// we can assume that p points to a zero-terminated array of characters
// we can assume that p cannot be nullptr
// we can assume that p points to a zero-terminated array of characters
int length(not_null<zstring> p);
Note: `length()` is, of course, `std::strlen()` in disguise.
@ -2237,10 +2240,11 @@ Passing a shared smart pointer (e.g., `std::shared_ptr`) implies a run-time cost
##### Example
void f(int*); // accepts any int*
void g(unique_ptr<int>); // can only accept ints for which you want to transfer ownership
void g(shared_ptr<int>); // can only accept ints for which you are willing to share ownership
void g(unique_ptr<int>); // accepts ints to transfer ownership
void g(shared_ptr<int>); // accepts ints to share ownership
void h(const unique_ptr<int>&); // doesn’t change ownership, but requires a particular ownership of the caller.
// doesn’t change ownership, but requires a particular ownership of the caller
void h(const unique_ptr<int>&);
void h(int&); // accepts any int
@ -2473,9 +2477,11 @@ If you have multiple values to return, [use a tuple](#Rf-out-multi) or similar m
##### Example
vector<constint*> find_all(const vector<int>&, int x); // OK: return pointers to elements with the value x
// OK: return pointers to elements with the value x
vector<constint*> find_all(const vector<int>&, int x);
void find_all(const vector<int>&, vector<constint*>& out, int x); // Bad: place pointers to elements with value x in out
// Bad: place pointers to elements with value x in out
void find_all(const vector<int>&, vector<constint*>& out, int x);
##### Note
@ -2518,14 +2524,16 @@ And yes, C++ does have multiple return values, by convention of using a `tuple`,
##### Example
int f(const string& input, /*output only*/ string& output_data) // BAD: output-only parameter documented in a comment
// BAD: output-only parameter documented in a comment
int f(const string& input, /*output only*/ string& output_data)
@ -8030,7 +8082,7 @@ In this case, it might be a good idea to factor out the read:
Record load_record(const string& name)
{
string fn = name+".txt";
string fn = name+".txt";
ifstream is {fn};
Record r;
is >> r;
@ -8241,7 +8293,8 @@ or:
or:
double scalbn(double base, int exponent); // better: base * pow(FLT_RADIX, exponent); FLT_RADIX is usually 2
// better: base * pow(FLT_RADIX, exponent); FLT_RADIX is usually 2
double scalbn(double base, int exponent);
##### Enforcement
@ -9225,21 +9278,29 @@ Complicated expressions are error-prone.
##### Example
while ((c = getc()) != -1) // bad: assignment hidden in subexpression
// bad: assignment hidden in subexpression
while ((c = getc()) != -1)
while ((cin >> c1, cin >> c2), c1 == c2) // bad: two non-local variables assigned in a sub-expressions
// bad: two non-local variables assigned in a sub-expressions
while ((cin >> c1, cin >> c2), c1 == c2)
for (char c1, c2; cin >> c1 >> c2 && c1 == c2;) // better, but possibly still too complicated
// better, but possibly still too complicated
for (char c1, c2; cin >> c1 >> c2 && c1 == c2;)
int x = ++i + ++j; // OK: iff i and j are not aliased
// OK: iff i and j are not aliased
int x = ++i + ++j;
v[i] = v[j] + v[k]; // OK: iff i != j and i != k
// OK: iff i != j and i != k
v[i] = v[j] + v[k];
x = a + (b = f()) + (c = g()) * 7; // bad: multiple assignments "hidden" in subexpressions
// bad: multiple assignments "hidden" in subexpressions
x = a + (b = f()) + (c = g()) * 7;
x = a & b + c * d && e ^ f == 7; // bad: relies on commonly misunderstood precedence rules
// bad: relies on commonly misunderstood precedence rules
x = a & b + c * d && e ^ f == 7;
x = x++ + x++ + ++x; // bad: undefined behavior
// bad: undefined behavior
x = x++ + x++ + ++x;
Some of these expressions are unconditionally bad (e.g., they rely on undefined behavior). Others are simply so complicated and/or unusual that even good programmers could misunderstand them or overlook a problem when in a hurry.
@ -9596,10 +9657,15 @@ Explicit `move` is needed to explicitly move an object to another scope, notably
void user()
{
X x;
sink(x); // error: cannot bind an lvalue to a rvalue reference
sink(std::move(x)); // OK: sink takes the contents of x, x must now be assumed to be empty
// error: cannot bind an lvalue to a rvalue reference
sink(x);
// OK: sink takes the contents of x, x must now be assumed to be empty
sink(std::move(x));
// ...
use(x); // probably a mistake
// probably a mistake
use(x);
}
Usually, a `std::move()` is used as an argument to a `&&` parameter.
@ -9611,8 +9677,11 @@ And after you do that, assume the object has been moved from (see [C.64](#Rc-mov
string s2 = s1; // ok, takes a copy
assert(s1 == "supercalifragilisticexpialidocious"); // ok
string s3 = move(s1); // bad, if you want to keep using s1's value
return a/b; // BAD, should be checked (e.g., in a precondition)
// BAD, should be checked (e.g., in a precondition)
return a / b;
}
##### Example; good
double divide(int a, int b) {
Expects(b != 0); // good, address via precondition (and replace with contracts once C++ gets them)
// good, address via precondition (and replace with contracts once C++ gets them)
Expects(b != 0);
return a / b;
}
double divide(int a, int b) {
return b ? a/b : quiet_NaN<double>(); // good, address via check
// good, address via check
return b ? a / b : quiet_NaN<double>();
}
**Alternative**: For critical applications that can afford some overhead, use a range-checked integer and/or floating-point type.
@ -10459,7 +10531,8 @@ C++ implementations tend to be optimized based on the assumption that exceptions
##### Example, don't
int find_index(vector<string>& vec, const string& x) // don't: exception not used for error handling
// don't: exception not used for error handling
int find_index(vector<string>& vec, const string& x)
{
try {
for (int i =0; i <vec.size();++i)
@ -12562,7 +12635,8 @@ There are three major ways to let calling code customize a template.
template<classT>
void test(T t)
{
f(t); // require f(/*T*/) be available in caller's scope or in T's namespace
// require f(/*T*/) be available in caller's scope or in T's namespace
f(t);
}
* Invoke a "trait" -- usually a type alias to compute a type, or a `constexpr` function to compute a value, or in rarer cases a traditional traits template to be specialized on the user's type.
@ -12570,7 +12644,8 @@ There are three major ways to let calling code customize a template.
template<classT>
void test(T t)
{
test_traits<T>::f(t); // require customizing test_traits<> to get non-default functions/types
// require customizing test_traits<> to get non-default functions/types
test_traits<T>::f(t);
test_traits<T>::value_type x;
}
@ -13065,13 +13140,15 @@ Use the least-derived class that has the functionality you need.
void j();
};
void myfunc(derived1& param) // bad, unless there is a specific reason for limiting to derived1 objects only
// bad, unless there is a specific reason for limiting to derived1 objects only
void myfunc(derived1& param)
{
use(param.f());
use(param.g());
}
void myfunc(base& param) // good, uses only base interface so only commit to that
// good, uses only base interface so only commit to that
void myfunc(base& param)
{
use(param.f());
use(param.g());
@ -13844,8 +13921,10 @@ Use of these casts can violate type safety and cause the program to access a var
derived1 d1;
base* p = &d1; // ok, implicit conversion to pointer to base is fine
derived2* p2 = static_cast<derived2*>(p); // BAD, tries to treat d1 as a derived2, which it is not
cout <<p2->get_s(); // tries to access d1's nonexistent string member, instead sees arbitrary bytes near d1
// BAD, tries to treat d1 as a derived2, which it is not
derived2* p2 = static_cast<derived2*>(p);
// tries to access d1's nonexistent string member, instead sees arbitrary bytes near d1
cout <<p2->get_s();
##### Example, bad
@ -13902,18 +13981,29 @@ Sometimes you may be tempted to resort to `const_cast` to avoid code duplication
class foo {
bar mybar;
public: // BAD, duplicates logic
bar& get_bar() { /* complex logic around getting a non-const reference to mybar */ }
const bar& get_bar() const { /* same complex logic around getting a const reference to mybar */ }
public:
// BAD, duplicates logic
bar& get_bar() {
/* complex logic around getting a non-const reference to mybar */
}
const bar& get_bar() const {
/* same complex logic around getting a const reference to mybar */
}
};
Instead, prefer to share implementations. Normally, you can just have the non-`const` function call the `const` function. However, when there is complex logic this can lead to the following pattern that still resorts to a `const_cast`:
class foo {
bar mybar;
public: // not great, non-const calls const version but resorts to const_cast
/* the complex logic around getting a const reference to mybar */
}
};
Although this pattern is safe when applied correctly, because the caller must have had a non-`const` object to begin with, it's not ideal because the safety is hard to enforce automatically as a checker rule.
@ -13963,8 +14053,10 @@ Note that a C-style `(T)expression` cast means to perform the first of the follo
derived1 d1;
base* p = &d1; // ok, implicit conversion to pointer to base is fine
derived2* p2 = (derived2*)(p); // BAD, tries to treat d1 as a derived2, which it is not
cout <<p2->get_s(); // tries to access d1's nonexistent string member, instead sees arbitrary bytes near d1
// BAD, tries to treat d1 as a derived2, which it is not
derived2* p2 = (derived2*)(p);
// tries to access d1's nonexistent string member, instead sees arbitrary bytes near d1
cout <<p2->get_s();
void f(const int& i) {
(int&)(i) = 42; // BAD
@ -15440,15 +15532,18 @@ To avoid extremely hard-to-find errors. Dereferencing such a pointer is undefine
string* bad() // really bad
{
vector<string> v = { "this", "will", "cause" "trouble" };
return &v[0]; // leaking a pointer into a destroyed member of a destroyed object (v)
// leaking a pointer into a destroyed member of a destroyed object (v)
return &v[0];
}
void use()
{
string* p = bad();
vector<int> xx = {7, 8, 9};
string x = *p; // undefined behavior: x may not be "this"
*p = "Evil!"; // undefined behavior: we don't know what (if anything) is allocated a location p
// undefined behavior: x may not be "this"
string x = *p;
// undefined behavior: we don't know what (if anything) is allocated a location p
*p = "Evil!";
}
The `string`s of `v` are destroyed upon exit from `bad()` and so is `v` itself. The returned pointer points to unallocated memory on the free store. This memory (pointed into by `p`) may have been reallocated by the time `*p` is executed. There may be no `string` to read and a write through `p` could easily corrupt objects of unrelated types.