As an optimization, you may want to reuse a buffer as a scratch pad, but even then prefer to limit the variable's scope as much as possible and be careful not to cause bugs from data left in a recycled buffer as this is a common source of security bugs.
void write_to_file() {
std::string buffer; // to avoid reallocations on every loop iteration
for (auto& o : objects)
void write_to_file()
{
std::string buffer; // to avoid reallocations on every loop iteration
for (auto& o : objects) {
// First part of the work.
generate_first_string(buffer, o);
write_to_file(buffer);
@ -11804,11 +11811,13 @@ Sometimes, you may be tempted to resort to `const_cast` to avoid code duplicatio
class Foo {
public:
// BAD, duplicates logic
Bar& get_bar() {
Bar& get_bar()
{
/* complex logic around getting a non-const reference to my_bar */
}
const Bar& get_bar() const {
const Bar& get_bar() const
{
/* same complex logic around getting a const reference to my_bar */
}
private:
@ -11820,10 +11829,12 @@ Instead, prefer to share implementations. Normally, you can just have the non-`c
class Foo {
public:
// not great, non-const calls const version but resorts to const_cast
/* the complex logic around getting a const reference to my_bar */
}
private:
@ -12002,7 +12013,8 @@ Explicit `move` is needed to explicitly move an object to another scope, notably
Usually, a `std::move()` is used as an argument to a `&&` parameter.
And after you do that, assume the object has been moved from (see [C.64](#Rc-move-semantic)) and don't read its state again until you first set it to a new value.
void f() {
void f()
{
string s1 = "supercalifragilisticexpialidocious";
string s2 = s1; // ok, takes a copy
@ -12019,7 +12031,8 @@ And after you do that, assume the object has been moved from (see [C.64](#Rc-mov
void sink(unique_ptr<widget> p); // pass ownership of p to sink()
void f() {
void f()
{
auto w = make_unique<widget>();
// ...
sink(std::move(w)); // ok, give to sink()
@ -12039,7 +12052,8 @@ Never write `std::move()` on a const object, it is silently transformed into a c
##### Example, bad
vector<int> make_vector() {
vector<int> make_vector()
{
vector<int> result;
// ... load result with data
return std::move(result); // bad; just write "return result;"
@ -12058,14 +12072,16 @@ The language already knows that a returned value is a temporary object that can
##### Example
void mover(X&& x) {
void mover(X&& x)
{
call_something(std::move(x)); // ok
call_something(std::forward<X>(x)); // bad, don't std::forward an rvalue reference
call_something(x); // suspicious, why not std::move?
}
template<classT>
void forwarder(T&& t) {
void forwarder(T&& t)
{
call_something(std::move(t)); // bad, don't std::move a forwarding reference
call_something(std::forward<T>(t)); // ok
call_something(t); // suspicious, why not std::forward?
@ -12174,7 +12190,8 @@ In the rare cases where the slicing was deliberate the code can be surprising.
Shape s {c}; // copy construct only the Shape part of Circle
s = c; // or copy assign only the Shape part of Circle
void assign(const Shape& src, Shape& dest) {
void assign(const Shape& src, Shape& dest)
{
dest = src;
}
Circle c2 {{1, 1}, 43};
@ -12712,7 +12729,8 @@ consider `gsl::finally()` as a cleaner and more reliable alternative to `goto ex
Often, a loop that requires a `break` is a good candidate for a function (algorithm), in which case the `break` becomes a `return`.
//Original code: break inside loop
void use1(){
void use1()
{
std::vector<T> vec = {/* initialized with some values */};
T value;
for (const T item : vec) {
@ -12725,14 +12743,16 @@ Often, a loop that requires a `break` is a good candidate for a function (algori
}
//BETTER: create a function and return inside loop
T search(const std::vector<T>&vec) {
T search(const std::vector<T>&vec)
{
for (const T &item : vec) {
if (/* some condition*/) return item;
}
return T(); //default value
}
void use2() {
void use2()
{
std::vector<T> vec = {/* initialized with some values */};
T value = search(vec);
/* then do something with value */
@ -13251,20 +13271,23 @@ This also applies to `%`.
##### Example, bad
double divide(int a, int b) {
double divide(int a, int b)
{
// BAD, should be checked (e.g., in a precondition)
return a / b;
}
##### Example, good
double divide(int a, int b) {
double divide(int a, int b)
{
// good, address via precondition (and replace with contracts once C++ gets them)
Expects(b != 0);
return a / b;
}
double divide(int a, int b) {
double divide(int a, int b)
{
// good, address via check
return b ? a / b : quiet_NaN<double>();
}
@ -13500,8 +13523,7 @@ Simple code can be very fast. Optimizers sometimes do marvels with simple code
vector<uint8_t> v(100000);
for (size_t i = 0; i <v.size();i+=sizeof(uint64_t))
{
for (size_t i = 0; i <v.size();i+=sizeof(uint64_t)){
@ -19017,7 +19043,8 @@ Doing so takes away an `#include`r's ability to effectively disambiguate and to
bool copy(/*... some parameters ...*/); // some function that happens to be named copy
int main() {
int main()
{
copy(/*...*/); // now overloads local ::copy and std::copy, could be ambiguous
}
@ -21722,7 +21749,8 @@ Here is an example of the last option:
class B {
public:
B() {
B()
{
/* ... */
f(); // BAD: C.82: Don't call virtual functions in constructors and destructors
/* ... */
@ -21935,7 +21963,8 @@ Consider the following advice and requirements found in the C++ Standard:
Deallocation functions, including specifically overloaded `operator delete` and `operator delete[]`, fall into the same category, because they too are used during cleanup in general, and during exception handling in particular, to back out of partial work that needs to be undone.
Besides destructors and deallocation functions, common error-safety techniques rely also on `swap` operations never failing -- in this case, not because they are used to implement a guaranteed rollback, but because they are used to implement a guaranteed commit. For example, here is an idiomatic implementation of `operator=` for a type `T` that performs copy construction followed by a call to a no-fail `swap`: