Effective Modern C++: 42 Specific Ways to Improve Your Use of C++11 and C++14

Category: Programming
Author: Scott Meyers
4.7
All Stack Overflow 9
This Year Stack Overflow 3
This Month Stack Overflow 1

Comments

by redditEnergy   2019-07-21

Here are several ways to get yourself started.

  • Read about the c++ core guidelines and Scott Meyer's Effective c++ book

    • https://github.com/isocpp/CppCoreGuidelines
    • https://www.amazon.com/Effective-Modern-Specific-Ways-Improve/dp/1491903996
    • https://github.com/AnthonyCalandra/modern-cpp-features
  • The best way to read them without getting really bored or overwhelmed

    • Is 2 ways (Also this is my opinion)
  • Read the table of contents for each

    • Honestly just doing this is a valid tactic. Often when I'm bored I'll crack open the book/guidelines and browse the table of contents and be like "WOAH", "WHAT", "HUH"
  • Turn up ALL the warnings + more

    • I work in Visual Studio almost exclusively so check W4 and then combine it with Resharper (Google "resharper c++")
      You can use clang intelligence thanks to resharper to pick up on even more errors that will tell you that you should be using modern c++ practices. And if you use these flags
    • *,-cppcoreguidelines-*,-cppcoreguidelines-*,-google-*
    • clang -Wall -Wextra -Weverything -Wno-c++98-compat -Weffc++
      • Feel free to look these up / add more
    • You'll slowly start learning some modern c++ via just Visual Studio or Resharper or Clang yelling at you.
    • Also learning this way you can look up with resharper or with google why the thing you are doing is wrong / not modern.
  • Extra advice

    • Often you can just google "how to do X in modern c++"
    • Example "How to do random in modern c++"
      • And the top answer will generally get you someone saying something about <random>
      • From here based on the quality of the answer either trust them, or go find better documentation from either the above resources or from here: https://isocpp.org/
    • Look at other languages like Rust, Go, Jai
      • Example rust generics have better error messages than c++
      • How do you do this in c++?
      • Well the answer is in c++ 20
      • https://en.wikipedia.org/wiki/C%2B%2B20
      • I'll let you explore
      • But my main point is that by looking at new languages you can learn more about modern c++ by asking yourself "Can I do this in c++?". Often the answer is yes but the syntax is gross or in a newer standard. But better that is a heck of a lot better than nothing!
by Hodorgasm   2019-07-21

Effective Modern C++: 42 Specific Ways to Improve Your Use of C++11 and C++14

IMHO, the best book for understanding C++11/14.

by anonymous   2019-07-21

As usual, Scott Meyers offers a great explanation of what they are, why you would want to use them, and roughly what rules they follow, in Effective Modern C++ Item 32: Use init capture to move objects into closures. Here is a short summary:

Since C++11 only has by-value and by-reference capture, by-move capture was missing. Instead of adding it, C++14 introduced the generalized lambda capture, also known as init capture. It allows you to specify

  1. the name of a data member in the closure class generated from the lambda, and
  2. an expression initializing that data member.

With this, an init capture is a new and more general capture mechanism, which covers the three above by-* captures and more (but no default capture mode).

Coming back to the main motivation of by-move capture, it can be implemented by an init capture as follows:

auto pw = std::make_unique<Widget>(); 

// configure *pw

auto func = [pWidget = std::move(pw)] { 
    return pWidget->isValidated() && pWidget->isArchived(); 
};

You can simulate an init capture in C++11 with

  • a hand-written class:

    class IsValAndArch {
    public:
        using DataType = std::unique_ptr<Widget>;
    
        explicit IsValAndArch(DataType&& ptr) : pw(std::move(ptr)) {}  
        bool operator()() const { return pw->isValid() && pw->isArchived(); }    
    
    private:
        DataType pw;
    };
    
    auto pw = std::make_unique<Widget>();
    
    // configure *pw;
    
    auto func = IsValAndArch(pw);
    
  • or with std::bind:

    auto pw = std::make_unique<Widget>();
    
    // configure *pw;
    
    auto func = std::bind( [](const std::unique_ptr<Widget>& pWidget)
        { return pWidget->isValidated() && pWidget->isArchived(); },
        std::move(pw) );
    

    Note that the parameter for the lambda is an lvalue reference (because the moved pw within the bind object is an lvalue) and has a const qualifier (to simulate the lambda's constness; so if the original lambda were declared mutable, the const qualifier would not be used).

Some details about init captures is also given in Anthony Calandra's cheatsheet of modern C++ language and library features, for instance that the initializing expression is evaluated when the lambda is created (not when it is invoked).

by anonymous   2019-07-21

Say you have a counter that you want to use to keep track of how many times some operation is completed, incrementing the counter each time.

If you run this operation in multiple threads then unless the counter is std::atomic or protected by a lock then you will get unexpected results, volatile will not help.

Here is a simplified example that reproduces the unpredictable results, at least for me:

#include <future>
#include <iostream>
#include <atomic>

volatile int counter{0};
//std::atomic<int> counter{0};

int main() {
    auto task = []{ 
                      for(int i = 0; i != 1'000'000; ++i) {
                          // do some operation...
                          ++counter;
                      }
                  };
    auto future1 = std::async(std::launch::async, task);
    auto future2 = std::async(std::launch::async, task);
    future1.get();
    future2.get();
    std::cout << counter << "\n";
}

Live demo.

Here we are starting two tasks using std::async using the std::launch::async launch policy to force it to launch asynchronously. Each task simply increments the counter a million times. After the two tasks are complete we expect the counter to be 2 million.

However, an increment is a read and write operation between reading the counter and writing to it another thread may have also written to it and increments may be lost. In theory, because we have entered the realm of undefined behaviour, absolutely anything could happen!

If we change the counter to std::atomic<int> we get the behaviour we expect.

Also, say another thread is using counter to detect if the operation has been completed. Unfortunately, there is nothing stopping the compiler from reordering the code and incrementing the counter before it has done the operation. Again, this is solved by using std::atomic<int> or setting up the necessary memory fences.

See Effective Modern C++ by Scott Meyers for more information.

by anonymous   2019-07-21

First on your "understandings":

As I can see it, they are in principle right but you should be aware of Copy elision which could prevent the program from calling any copy/move Constructor. Depends on your compiler (-settings).

On your Questions:

  1. Yes you have to call foo(std::move(b)) to call an Function which takes an rvalue with an lvalue. std::move will do the cast. Note: std::move itself does not move anything.

  2. Using the move-constructor "might" be more efficient. In truth it only enables programmers to implement some more efficient Constructors. Example consider a vector which is a Class around a pointer to an array which holds the data (similar to std::vector), if you copy it you have to copy the data, if you move it you can just pass the pointer and set the old one to nullptr. But as I read in Effective Modern C++ by Scott Meyers: Do not think your program will be faster only because you use std::move everywere.

  3. That depends on the usage of the input. If you do not need a copy in the function it will in the most cases be more efficient to just pass the object by (const) reference. If you need a copy there are several ways of doing it for example the copy and swap idiom. But as a

by winter_blue   2018-11-10
I strongly prefer text over video[1], and the best book on modern C++ is "Effective Modern C++" by Scott Meyers: https://www.amazon.com/Effective-Modern-Specific-Ways-Improv...

[1] I wish people invested more time/effort in writing good textbooks instead of videos/MOOCs.

by anonymous   2018-03-19

The name returned by std::type_info::name is implementation defined, it's not guaranteed to return "int".

Returns an implementation defined null-terminated character string containing the name of the type. No guarantees are given; in particular, the returned string can be identical for several types and change between invocations of the same program.

On the other hand, you can get the type information at compile-time (with a non-defined class template). It depends on the compiler too but is much more clear in general. (The idea came from Effective Modern C++ (Scott Meyers) Item #4: Know how to view deduced types.)

template <typename>
struct TD;

int main()
{
    auto x{123};
    auto y={1,2};

    TD<decltype(x)> td1;
    TD<decltype(y)> td2;   
}

Then you'll get the type information from the compilation error message, such as Clang:

source_file.cpp:12:21: error: implicit instantiation of undefined template 'TD<int>'
    TD<decltype(x)> td1;
                    ^
source_file.cpp:13:21: error: implicit instantiation of undefined template 'TD<std::initializer_list<int> >'
    TD<decltype(y)> td2;
                    ^
by anonymous   2017-08-20

Note that *first is an lvalue expression, then the result type of decltype(*first) would be const int&, i.e. a reference to const int. The reference is not const itself (it can't be const-qualified, there's no such thing like int& const), using std::remove_const on it will yield the same type, i.e. const int&.

See decltype specifier:

3) If the argument is any other expression of type T, and

b) if the value category of expression is lvalue, then decltype yields T&;

You could use std::remove_const with std::remove_reference together:

std::remove_const<std::remove_reference<deref>::type>::type // -> int
                                        ~~~~~               // -> const int &
                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~        // -> const int

BTW:

Note that I use std::thread just to get readable types in the errors:

Note that it doesn't give the correct type for this case. Here's a class template helper for this from the Effective Modern C++ (Scott Meyers):

template<typename T>
class TD;

and use it as

TD<deref> td;

You'll get the error message containing the type of deref, e.g. from clang:

prog.cc:16:11: error: implicit instantiation of undefined template 'TD<const int &>'
TD<deref> td;
          ^
by litb   2017-08-20

Beginner

Introductory, no previous programming experience

Introductory, with previous programming experience

* Not to be confused with C++ Primer Plus (Stephen Prata), with a significantly less favorable review.

Best practices


Intermediate


Advanced


Reference Style - All Levels

C++11/14 References:

  • The C++ Standard (INCITS/ISO/IEC 14882-2011) This, of course, is the final arbiter of all that is or isn't C++. Be aware, however, that it is intended purely as a reference for experienced users willing to devote considerable time and effort to its understanding. As usual, the first release was quite expensive ($300+ US), but it has now been released in electronic form for $60US.

  • The C++14 standard is available, but seemingly not in an economical form – directly from the ISO it costs 198 Swiss Francs (about $200 US). For most people, the final draft before standardization is more than adequate (and free). Many will prefer an even newer draft, documenting new features that are likely to be included in C++17.

  • Overview of the New C++ (C++11/14) (PDF only) (Scott Meyers) (updated for C++1y/C++14) These are the presentation materials (slides and some lecture notes) of a three-day training course offered by Scott Meyers, who's a highly respected author on C++. Even though the list of items is short, the quality is high.

  • The C++ Core Guidelines (C++11/14/17/…) (edited by Bjarne Stroustrup and Herb Sutter) is an evolving online document consisting of a set of guidelines for using modern C++ well. The guidelines are focused on relatively higher-level issues, such as interfaces, resource management, memory management and concurrency affecting application architecture and library design. The project was announced at CppCon'15 by Bjarne Stroustrup and others and welcomes contributions from the community. Most guidelines are supplemented with a rationale and examples as well as discussions of possible tool support. Many rules are designed specifically to be automatically checkable by static analysis tools.

  • The C++ Super-FAQ (Marshall Cline, Bjarne Stroustrup and others) is an effort by the Standard C++ Foundation to unify the C++ FAQs previously maintained individually by Marshall Cline and Bjarne Stroustrup and also incorporating new contributions. The items mostly address issues at an intermediate level and are often written with a humorous tone. Not all items might be fully up to date with the latest edition of the C++ standard yet.

  • cppreference.com (C++03/11/14/17/…) (initiated by Nate Kohl) is a wiki that summarizes the basic core-language features and has extensive documentation of the C++ standard library. The documentation is very precise but is easier to read than the official standard document and provides better navigation due to its wiki nature. The project documents all versions of the C++ standard and the site allows filtering the display for a specific version. The project was presented by Nate Kohl at CppCon'14.


Classics / Older

Note: Some information contained within these books may not be up-to-date or no longer considered best practice.

by anonymous   2017-08-20

See Item 17 from Scott Meyer's great book "Effective Modern C++". It describes many conditions under which default copy constructors, copy operations, and move operations are generated (or NOT generated).

In other words, the compiler might not "do it anyway". But if the default special member function makes sense, the user could use the "default" keyword to explicitly tell the compiler to generate a default function that otherwise not be generated.

From the Things to Remember at the end of Item 17:

  • Move operations are generated only for classes lacking explicitly declared move operations, copy operations, or a destructor.

  • The copy constructor is generated only for classes lacking an explicitly declared copy constructor, and it’s deleted if a move operation is declared. The copy assignment operator is generated only for classes lacking an explicitly declared copy assignment operator, and it’s deleted if a move operation is declared. Generation of the copy operations in classes with an explicitly declared destructor is deprecated.

by anonymous   2017-08-20

Herb Sutter talks about something similar in a cppcon talk

This can be done but probably shouldn't. You can get the effect out using universal references and templates, but you want to constrain the type to MyBigType and things that are implicitly convertible to MyBigType. With some tmp tricks, you can do this:

class MyClass {
  public:
    template <typename T>
    typename std::enable_if<std::is_convertible<T, MyBigType>::value, void>::type
    f(T&& a, int id);
};

The only template parameter will match against the actual type of the parameter, the enable_if return type disallows incompatible types. I'll take it apart piece by piece

std::is_convertible<T, MyBigType>::value

This compile time expression will evaluate to true if T can be converted implicitly to a MyBigType. For example, if MyBigType were a std::string and T were a char* the expression would be true, but if T were an int it would be false.

typename std::enable_if<..., void>::type // where the ... is the above

this expression will result in void in the case that the is_convertible expression is true. When it's false, the expression will be malformed, so the template will be thrown out.

Inside the body of the function you'll need to use perfect forwarding, if you are planning on copy assigning or move assigning, the body would be something like

{
    this->a_ = std::forward<T>(a);
}

Here's a coliru live example with a using MyBigType = std::string. As Herb says, this function can't be virtual and must be implemented in the header. The error messages you get from calling with a wrong type will be pretty rough compared to the non-templated overloads.


Thanks to Barry's comment for this suggestion, to reduce repetition, it's probably a good idea to create a template alias for the SFINAE mechanism. If you declare in your class

template <typename T>
using EnableIfIsMyBigType = typename std::enable_if<std::is_convertible<T, MyBigType>::value, void>::type;

then you could reduce the declarations to

template <typename T>
EnableIfIsMyBigType<T>
f(T&& a, int id);

However, this assumes all of your overloads have a void return type. If the return type differs you could use a two-argument alias instead

template <typename T, typename R>
using EnableIfIsMyBigType = typename std::enable_if<std::is_convertible<T, MyBigType>::value,R>::type;

Then declare with the return type specified

template <typename T>
EnableIfIsMyBigType<T, void> // void is the return type
f(T&& a, int id);


The slightly slower option is to take the argument by value. If you do

class MyClass {
  public:
    void f(MyBigType a, int id) {
        this->a_ = std::move(a); // move assignment
    } 
};

In the case where f is passed an lvalue, it will copy construct a from its argument, then move assign it into this->a_. In the case that f is passed an rvalue, it will move construct a from the argument and then move assign. A live example of this behavior is here. Note that I use -fno-elide-constructors, without that flag, the rvalue cases elides the move construction and only the move assignment takes place.

If the object is expensive to move (std::array for example) this approach will be noticeably slower than the super-optimized first version. Also, consider watching this part of Herb's talk that Chris Drew links to in the comments to understand when it could be slower than using references. If you have a copy of Effective Modern C++ by Scott Meyers, he discusses the ups and downs in item 41.