Elements of Modern C++ Style

image

As I’m getting ready to resume writing a few new (or updated) Guru of the Week Items for the C++11 era, I’ve been looking through the wonderful features of C++11 and analyzing just which ones will affect the baseline style of how I write modern C++ code, both for myself and for publication.

I’ve gathered the results in a short page. Here’s the intro:

Elements of Modern C++ Style

“C++11 feels like a new language.” – Bjarne Stroustrup

The C++11 standard offers many useful new features. This page focuses specifically and only on those features that make C++11 really feel like a new language compared to C++98, because:

  • They change the styles and idioms you’ll use when writing C++ code, often including the way you’ll design C++ libraries. For example, you’ll see more smart pointers, and functions that return big objects by value.
  • They will be used so pervasively that you’ll probably see them in most code examples. For example, virtually every five-line modern C++ code example will say “auto” somewhere.

Use the other great C++11 features too. But get used to these ones first, because these are the pervasive ones that show why C++11 code is clean, safe, and fast – just as clean and safe as code written in any other modern mainstream language, and with C++’s traditional to-the-metal performance as strong as ever.

Like Strunk & White, this page is deliberately focused on brief summary guidance. It is not intended to provide exhaustive rationale and pro/con analysis; that will go into other articles.

I hope you find it useful.

Apologies in advance if some of the code snippets are odd or missing template argument lists. Let me know and I’ll fix any I missed. I think I restored them all (again), but am still fighting my tools, which keep sporadically eating angle-bracket lists. Someday someone will integrate good code authoring in a good editor for a good blogging platform; today’s tools are at best “adequate.”

32 thoughts on “Elements of Modern C++ Style

  1. Granted, not everybody writes lots of nested templates, but the template bracket fix will change how I write code tremendously. I’ve despised all those unnecessary spaces for years.

  2. Out of interest, in the pimpl/unique_ptr example, would widget require a user-defined destructor to ensure the unique_ptr deletes a pointer to a complete type, or does unique_ptr play a similar trick to shared_ptr and instantiate the deletion mechanism at construction time?

  3. Nice summary, thanks Herb. FYI, there’s a very C++11 looking ‘auto’ in the C++98 code of the Lambda section. Seems that new habits die hard ;)

  4. @Edd: unique_ptr can indeed take an object of incomplete type. From the Standard:

    20.7.1.1 Default deleters [unique.ptr.dltr]
    20.7.1.1.1 In general [unique.ptr.dltr.general]
    1 The class template default_delete serves as the default deleter (destruction policy) for the class template unique_ptr.
    2 The template parameter T of default_delete may be an incomplete type.

    @Andrew: Thanks, fixed. I cut-and-pasted from a slide where that example was labeled “C++11 without lambdas” and didn’t notice it for the reason you said — auto feels so natural so quickly that most people take to it like a duck to water and then hardly notice it. It’s just long past time we had that.

    Obligatory auto note: As Bjarne is always quick to point out, “auto” is the oldest C++11 feature, which he first implemented in Cfront in 1983. They made him take it out. What a shame.

  5. I have to say that I’m disappointed (though not altogether surprised) in your list. Because those are all features that Visual Studio supports.

    What I mean by that is that, if you wanted a list of features that will “change the styles and idioms you’ll use when writing C++ code, often including the way you’ll design C++ libraries,” there is no way that Uniform Initialization and Initializer Lists should not be on that list. The list in incomplete so long as they’re not on it.

    Not having to type typenames nearly so much, being able to initialize std::vectors like they were a regular array, these are all crucial things to a C++ programmer. A C++11 programmer will use these things *constantly*.

    But Visual Studio 2010 doesn’t support them, and we’ve already been told that they’re not on tap for VCNext. So even though these features certainly fit that mark, you don’t talk about them. My disappointment is that you’re so focused on what VS supports that you look at C++11 as nothing more than “What Visual Studio supports”, rather than seeing VS’s support as being sub-standard and in dire need of improvement.

    I hope that future discussion about C++11 features that aren’t in Visual Studio won’t have to wait until Microsoft bothers to actually implement them. I would have hoped that you could use C++11 articles to get the community pressuring Microsoft to speed up development of major C++11 features. But I guess not.

  6. Herb, Edd has a point. Your widget example misses a declaration for a destructor. The destructor would have to be defined in the cpp as well (with an empty pair of braces). But it needs to be there because otherwise the compiler might be tempted to instantiate the unique_ptr’s dtor in some place where the impl type is still incomplete.

  7. I’d like to use ‘auto’, but then Visual Assist X loses IntelliSense for class members of the ‘auto’-ed variable. Until the tooling properly supports auto, I’m not using it.

  8. > Use C++11 auto wherever possible
    Really *that* often? The examples with iterators and lambdas are no-brainers, but the rest? Almost every type definition could be replaced by auto. In general I think there is still quite some semantical knowledge included in a type which should interest the reader: Before adding 1234 I’ll want to see I’m not working on a signed char, when dealing with file sizes I want to see the type is 64 bit (and not an int disguised by auto).
    Maybe the rule should be rather: Whereever it saves writing useless clutter. (like for iterator types). In cases where the type is short, like “int a = 1”, there doesn’t seeem to be much to save (but quite something to loose.)

  9. Just to make sure everything’s been said:
    The C98 sort goes out of bounds. :)
    sort( &a[0], &a[0]+sizeof(a) )

  10. In your C++98 example, you should use
    sort( &a[0], &a[0]+sizeof(a)/sizeof(a[0]) )

    Unless you’re on a system with int size guaranteed to equal char size.

  11. All the things that you are writting about are definitely on my list too. One more for your consideration is:

    constexpr

    With C++2011’s constexpr, C++1998’s template meta-programming is replaced by something vastly easier … and makes sense to everyday programmers. If a programmer specifies a function as a constexpr function (by just adding the constexpr specifier to a normal function), then if that function is called with constexpr arguments (like literals or the return of another constexpr function call), then that constexpr is evaluated and expanded at compile-time. If the arguments are not constexpr, then the function is just a normal function … and is evaluate at run-time.

    See N2235 and N2976 for the C++ Working Group papers … and Section 5.19 in the C++ Working Draft Standard (N3291 … I don’t have a copy of the final standard yet ;-) ).

    And then there’s constexpr constructors …

    I initially overlooked this new keyword, but it seems like I’m going use constexpr in many places … maybe also where I previously used const … and probably as much or more than I currently use auto.

    Very Respectfully,
    Joshua Burkholder

  12. @Alfonse: Actually my list does (embarrassingly) include at least some things that are not in Visual C++ — for example, our compiler still doesn’t have range-for, which is sad. We need to fix that, but I didn’t keep range-for off the list just because our compiler doesn’t have it yet. My goal for this list was to be independent of what any particular compiler supports. I’m happy that it turns out we do have most of at least these core features, but (a) we don’t have even all of these which is bad, and (b) this is just the core set and we are woefully behind on other important C++11 conformance and we absolutely need to fix that.

    @Joshua: I have a longer list including initializer lists, uniform initialization, constexpr, =default/=delete, override/final, and more, and I originally had those written up on this page. I kept trimming and removing because I wanted this list to be the true “core” that will be used pervasively, that you can hardly look at a piece of code without seeing, and as I wrote up examples I saw that some of the good and useful features were not ones that were going to be used pervasively. Initializer lists and uniform initialization came closest, but unlike auto which you’ll see virtually everywhere, as I wrote examples it seemed like you won’t see the new { } initialization everywhere; in particular “auto i = value;”, which does not have { } syntax, seems like it will still be the default simplest way to initialize variables. Perhaps this will change as we get more experience, and I’ll maintain the page to reflect current experience.

    @ZenJu: Can you clarify why the sort would go out of bounds? std::sort takes [begin,end) where the second is a one-past-the end iterator, and C and C++ allow you to form (but not dereference) a pointer to an element one past the end of an array.

    @krzaq: Fixed, thanks.

  13. > @ZenJu: Can you clarify why the sort would go out of bounds?
    I mentioned the same issue krzaq posted right after me.

  14. I don’t have an experience with auto in C++ but I have with var in C# with is the same.

    I find it to be a lazy shortcut for developers and reducing readability of code in every situation except for the basic T a = new T(…) lines.

    I find Java solution (to omit the type on the right side, not on the left) much better than C# and C++.

    The first thing I want to see out of a variable is the type, and auto/var hide me the most precious information of all.

  15. Hi, I believe that the list of the most typical C++11 usage idioms should include the brace initialization syntax. It is difficult to judge about the trends of the thing so new as C++11, but the power of brace initialization syntax appears really huge, not only in the basic examples like range constructors for STL containers, but in the complex examples of nested data structures and the ability to avoid repeating yourself similar to that of keyword ‘auto’, as well as overcoming some syntactic limitations of initialization in C++03 that we had to deal with for long time. I have compiled some examples here:
    http://akrzemi1.wordpress.com/2011/06/29/brace-brace/

    Regards,
    &rzej

  16. Herb, I remember at one of the windows 8 build sessions you suggested the for_each algorithm as a preferent to handwritten for loops, as the compiler may somehow optimize the function call. Does that still apply here, or does for(auto & : container) offer some performance benefits over for_each and for(int i = 0, i < max; ++i)?

  17. @Brian: The range-for hardwired into the language does have a convenience advantage for the most common kind of visit-each-range-element-in-order loop, where the syntactic difference is small but desirable — for( auto& elem : coll ) { … } vs. for_each( begin(coll), end(coll), [&]( element_type& elem ) { … } );.

    Note that, like anything hardwired into the language, it’s great when what you want is exactly what it’s specified to do. If you want something even slightly different, such as the same algorithm but with a “step” to visit every nth element, you already need to write a different algorithm like a for_each_nth. It’s easy to do, but it shows how flexible and extensible the algorithm approach is compared to the language feature.

    Sometime in the future when we get polymorphic lambdas and range-based algorithms, the syntactic difference favoring range-for will be much less — for( auto& elem : coll ) { … } vs. something like for_each( coll, (elem){ … } );. But range-for will be entrenched by then, and will still be a tiny bit nicer for the specific case it’s designed to support.

  18. I think that we should prefer the new alias typedefs to the C-style typedefs, especially for more complex types. I definitely would prefer to see

    using callback = int (*)(Widget);

    to
    typedef int (*callback)(Widget);

  19. Herb, I think you should definitely write a small book (kind of “compendium”) listing all new C++11 features and providing examples of usage. It would be important to boost the adoption of C++11 in the industry… I and the company I work for would certainly buy a few copies. The title could be “Exceptional C++11”, of course. :)

  20. This has probably been discussed to death elsewhere, but why is it “auto” and not “var”? I may be ignorant of the history here, but it seems like “var” is an established keyword in other languages, and “auto” is something that was just made up.

  21. @Clayton H

    I don’t know but guess: auto was already a keyword in C and C++ and so no valid program could declare a variable called auto, or whatever else. In this way, no previously valid program is broken, while if they used var they would have broken every program using “var” as a variable, method, class name, etc. Not only that, but it can be argued that in many contexts “var” could be a quite common variable name.

  22. Two notes:
    – “always use shared_ptr” = bad advice, imho, shared_ptr is much more expensive than plain pointer/reference and should not be used indiscriminately when you need only indirection (without ownership management). Even if you do need it — hand-written refcounted pointer is often better (if you do not need thread safety, which happens quite regularly in my experience)
    – (N)RVO solved problem of returning by-value long ago, recently introduced “semi-“move semantic adds nothing new to this area

  23. Herb, since you’re in the C++ standard’s committee, I’ll ask you this question.

    Are there any plans to add networking capabilities(perhaps based on boost.asio) to the C++ standard library?

  24. @Andrzej: Okay, you’ve convinced me. I’ve added uniform initialization to Elements — applied it throughout (except on simple variable initializations, see below) and added back a section on it that I had already written in earlier drafts of the Elements page.

    Background: Like I said, for weeks now I’ve been on the edge of recommending pervasive uniform initialization, and actually had it in the original draft of Elements and partly added it to Elements several times, but I kept getting stuck on the simple cases:

    
    
      auto i = begin(v);  // 1
    

    It just seemed not beneficial, and cluttered, to tell people to write:

    
    
      auto i { begin(v) };  // 2
    

    or

    
    
      auto i = { begin(v) };  // 3
    

    #2 is legal, but just doesn’t look like code I’d want to write. I kept choking on it after a few lines like that. (How about “unique_ptr p { new int { } };”? Maybe not horrible, but it just didn’t feel right to me.)

    #3 means the same as #2 and is visually better, at least to me; I think the “=” helps. But when you compare it to #1, exactly what are we telling the programmer he’s getting in return for writing those braces? Even in non-auto cases, when often he’ll know there won’t be a narrowing conversion, or an accidental non-initialization of a POD?

    So for now I’m recommending pervasive use of { } except in cases like this of simple initialization of a non-POD variable with a single value. We’ll see how it goes; our C++11 style is still settling down, but this seems like a reasonable place to be.

  25. Herb, looking for commentary on this. Before C++11, I used this as my pimpl idiom:

    // .h
    class Widget
    {
    public:
    Widget();
    ~Widget();
    private:
    class Impl;
    Impl &m_impl;
    };

    // .cpp
    class Widget::Impl {…}

    Widget::Widget() :
    m_impl(*new Impl)
    {}
    Widget::~Widget()
    {
    delete &m_impl;
    }

    because it prevented the Impl pointer from ever being reseated accidentally, other than by a memory overwrite. Even though I still need a user defined dstr, I think I prefer this idiom to your unique_ptr example. I understand that I’m giving up move, but I do this mostly for classes that are big and not designed to be copied. Any thoughts to convince me otherwise? Thanks. (BTW, thanks for all of your great contributions to the community!)

  26. @Kevin: If your concern is that you want a non-reseatable one, wouldn’t const unique_ptr work? However, note that often you do want the impl to be reseatable, for example to move-enable the class (indeed, one of the new benefits of Pimpl is that it makes classes naturally efficiently move-enabled). So do think twice about whether you really want non-reseatability for a given class; I’ll bet that often you do want it.

    Pure style comment: My high-order bit is “clarity and correctness come first” and unusual code is a barrier to reading and maintaining the code. In this case, your code uses two unusual idioms: a reference data member and “delete &anything”, both of which raise flags in the mind of the person reading the code even though you seem to be using them correctly. If you really want that style, and (possibly const) unique_ptr doesn’t satisfy you, then you probably should consider encapsulating the odd code behind an impl_holder helper class that hides these details.

    This could be a good updated GotW 24. :)

  27. Might I suggest that your section on type inference (new meaning of auto keyword) should cover modifiers, such as `auto const x = 5;` and `auto& r = a[i];`?

Comments are closed.