In recent talks (e.g., at Going Native in February and at Lang.Next earlier this month), Herb has offered guidelines for proper usage of C++11. He made it look so easy, I said to myself, “Heck, I could do that.” Besides, people have been pestering me about a new edition of Effective C++, so I figured I’d sit down and try to come up with a few prospective guidelines. “How hard can it be?,” I asked myself.
Easier than expected, actually. In about a half hour, I’d jotted down some 25 candidate guidelines. That’s more than enough for a meaty C&B talk, so I plan to give one. What will be in the talk? I don’t know yet, because what I have are prospective guidelines, and they will evolve as I work on them and as I learn more about C++11 and its effective application. (My knowledge continues to change almost daily.) What follows is a snapshot of the list of candidate guidelines I have right now. The ordering is random, and the wording is tentative, so don’t read too much into them. There are far more here than can be covered in single talk, so there’s no way I’ll cover all of these at C&B. My plan is to focus on the ones I think include material that is particularly important or particularly surprising (e.g., counterintuitive). If you see a topic you’d like to hear about, please post a comment to that effect. If you don’t see a topic you think I should address, please post about that. If you see a guideline you think is bad advice, posting a comment is again the thing to do. If you know of a good guideline you don’t see listed, post, post, post!
Scott
-
Prefer auto to Explicit Type Declarations
-
Distinguish () and {} When Creating Objects
-
Remember that auto + { expr } == std::initializer_list
-
Prefer non-member begin/end to member versions
-
Declare std::thread Members Last in Classes
-
Be Wary of Default Capture Modes in Lambdas Escaping Member Functions
-
Prefer Emplacement to Insertion
-
Pass std::launch::async if Asynchronicity is Essential
-
Minimize use of Weak Atomics
-
Distinguish Rvalue References from Universal References
-
Assume that move operations are neither present nor cheap
-
Prefer Lambdas over Binders
-
Prefer Lambdas over Variadic Arguments to Threading Functions
-
Be Wary of Oversubscription
-
Apply std::forward when Passing Universal References
-
Prefer std::array to Built-in Arrays
-
Use std::make_shared Whenever Possible
-
Prefer Pass-by-Reference-to-const to Pass-by-Value for std::shared_ptrs
-
Pass by Value if You’ll Copy Your Parameter
-
Reserve noexcept for Functions with Wide Interfaces
-
For Copyable Types, View Move as an Optimization of Copy
-
Prefer enum classes to enums
-
Prefer nullptr to NULL and 0
-
Distinguish among std::enable_if, static_assert, and =delete
April 16, 2012 at 5:14 pm
Good ones … My two cents:
“Pass by Value if You’ll Copy Your Parameter”. This advice feels a bit wrong. Whether a function copies its argument or not, is an implementation detail. It should not be visible in the interface. I think its better to fix the interface to const reference and copy it if need be like its noones business.
“Prefer auto to Explicit Type Declarations”. Sometimes it hurts readability when types are not spelled out in code. We cant always rely on insellisense. I imagine Hungarian notation might make a comeback if people start following this advice religiously.
April 16, 2012 at 7:19 pm
From a caller’s perspective, there is no semantic difference (i.e., ignoring performance) between pass by value and pass by ref-to-const (assuming the parameter supports copy construction), so an implementer who chooses pass-by-value is making no more of an implementation decision than an implementer who chooses pass-by-ref-to-const. If an implementer knows that the function will unconditionally make a copy of the parameter, there is a good performance argument to be made for taking that parameter by value in the interface, IMO.
Regarding auto, certainly it might hurt readability in some cases, but there are some real and, IMO, underappreciated advantages to using auto. In practice, for example, how many people declare variables of type Container::size_type for the result of a call to Container::size? People who use int or long have been in for some interesting surprises when porting from 32 to 64 bits, because the int and long would in some cases no longer hold the result of functions like Container::size, though the code would typically continue to compiler with no more complaint than it elicited before the port. I was more ambivalent about using or not using auto when I felt that it was primarily a style issue or something designed to reduce typing drudgery. With a bit more experience under my belt, I think the case for auto has a stronger foundation than that. If I decide to discuss this guideline at C&B, we’ll delve into that stronger foundation. I think there’s more to the issue than readability.
Scott
April 16, 2012 at 5:16 pm
Why “Prefer non-member begin/end to member versions”? My code editor (VS2010) is very helpful in providing a list of members of my container but I’d have to type out the non-member versions long hand. I don’t believe the benefit out weighs the inconvenience.
April 16, 2012 at 7:06 pm
Fair enough. If I discuss this guideline in my talk, I’ll talk about the pros and cons. Foremost among the pros is that it works with arrays and with other libraries (notably from Microsoft) where classes don’t offer begin and end.
Scott
April 23, 2012 at 5:03 pm
Thanks Scott,
If there is a compelling reason to prefer non-member begin/end to member versions, I hope your talk can also convince someone at Microsoft to make it just as easy to use the non-member begin/end as it is to use the member versions now. No matter how good a guideline is, if it is hard to follow it often won’t be followed.
I do see the advantage where the type of the container may change to one that does not have the member versions (template code and the like), but that is not the type of code I usually write. Also, assuming I’m using C++11 and following your other guideline, std::array does have member .begin() and .end(), (and I avoid the non-standard Microsoft containers even though I use Visual Studio).
I look forward to hearing more about this proposed guideline, as well as the others.
CS
April 23, 2012 at 7:45 pm
I use non-member begin and end now with VC++ 10. How is it not as easy?
April 24, 2012 at 2:53 pm
When I last attempted to start using the non-member versions, I quickly released how much I rely on IntelliSense/autocompletion for function names.
For example:
given the code: std::vector v = foo();
These keystrokes: [v][.][b][down-arrow][enter][(][)]
Results in this code: v.begin()
April 17, 2012 at 2:30 am
Maybe something like that:
- provide perfect forwarding constructors and setters where possible (avoidance of temporary objects due to type conversions)
- do not explicitly move function local variable in return statement (it is moved already)
- use copy-and-swap idiom with universal assignment operator (1 assignment implementation instead of 2, cleaner implementation with less code rewrites, strong exception guarantee, no performance hit on self-assignment check, no dangling moved objects with acquired resources ie. lock)
- use explicitly defaulted special member functions and override wherever possible to avoid common implementation problems (base class virtual interface refactoring, virtual function spelling errors, forgetting about calling appropriate base class constructors from child constructor)
- create missing make_unique implementation for your code and use it everywhere (better exception safety)
- STD lib clocks implementation is poor for now (gcc 4.7 on Ubuntu - no steady clock & 1us precision; VS2011 - 1ms precision), create your own clocks but keep with STD interface (really easy to wrap QPC or clock_gettime()) and wait until better STD implementations arrive.
- Use Meyer’s Singleton for easy and robust singleton implementation while being thread-safe at the same time (6.7.4) ;-)
Change proposal:
- “Declare std::thread Members Last in Classes” - maybe it should also be extended to std::future (30.6.8_5) as the execution of a lambda (that may capture by references) can be deferred until destruction?
April 17, 2012 at 3:40 pm
Thanks for your comments. Regarding the use of copy-and-swap to implement assignment, I suggest you review the March 2012 comments on this topic at a blog post of mine where the topic arose. I believe that copy-and-swap is a poor choice for a “default” move assignment operator implementation.
Scott
April 18, 2012 at 3:42 am
You are welcome.
Regarding copy-and-swap. Yes, there is probably no one-size-fits-all implementation for that problem. For cheap to move classes copy-and-swap idiom with universal assignment is a great solution but for others (like your mentioned std::array) it may be a poor choice :-(.
May 2, 2012 at 5:47 am
I look forward to reading your “Items” on C++11 in printed form.
You always summarize it well and you bring issues to the point.
I am also glad that the german book “C++11 programmieren” at least
mentions some of your early items — http://cpp11.generisch.de/effective-c11-scotts-erste-ideen/
That is of not the least because I had your
great C++11 slides at hand to guide me…
May 2, 2012 at 7:58 am
[…] Session Topic: Initial Thoughts on Effective C++11 « C++ and Beyond […]
June 1, 2012 at 5:09 am
Regarding the guideline “Pass by Value if You’ll Copy Your Parameter”, this is something that I use(d) to do (e.g. in the assignment operator implemented in terms of swap). However, there’s a subtle point regarding overload resolution that must be considered. If both of these are declared
void f(Thing);
void f(Thing&& x);
then the call to f is ambigouous when a rvalue is passed to f (e.g., in f(g()), where function g returns a Thing.)
This is better explained by Andrew Koenig in http://www.drdobbs.com/blogs/cpp/232900202
June 7, 2012 at 3:55 pm
@Cassio Neri: This is a good point, thanks for mentioning it.
Scott
June 22, 2012 at 4:08 am
Another two cents. Following the guideline “Prefer auto to Explicit Type Declarations”, I believe, make your code more adaptable to changes and this is a strong reason to use auto. There is, however, an obvious exception that is worth mentioning: when the initializer is a pointer coming from a call to new:
auto p = new Foo;
Depending on what comes after, this line can be (and probably is) unsafe and the following are preferable:
1) std::unique_ptr p = new Foo;
2) std::shared_ptr p = new Foo;
Now, applying “Use std::make_shared Whenever Possible”, option 2 should be replaced by
2′) auto p = std::make_shared();
Unfortunately, there’s no equivalent for option 1 unless a make_unique function — the analogous of std::make_shared but for unique_ptr — is available.
Now, pushing the guideline a step further. Do you think that
for (decltype(v.size()) i = 0; i < v.size(); ++i)
is also a good thing or it is a bit too much? (I'm ignoring iterators for this discussion.)
June 22, 2012 at 4:14 am
For some, I guess, HTML technicality the angle brackets used to delimit the template parameter Foo disappeared from my post in std::unique_ptr, std::shared_ptr and std::make_shared.
June 29, 2012 at 12:37 pm
[…] in C++11 (Scott) You Don’t Know [keyword] and [keyword] (Herb) Convincing Your Colleagues (Panel) Initial Thoughts on Effective C++11 (Scott) Modern C++ = Clean, Safe, and Faster Than Ever (Panel) Error Resilience in C++11 (Andrei) […]
June 29, 2012 at 12:43 pm
[…] in C++11 (Scott) You Don’t Know [keyword] and [keyword] (Herb) Convincing Your Colleagues (Panel) Initial Thoughts on Effective C++11 (Scott) Modern C++ = Clean, Safe, and Faster Than Ever (Panel) Error Resilience in C++11 (Andrei) […]
June 30, 2012 at 1:00 pm
[…] http://cppandbeyond.com/2012/04/16/session-topic-initial-thoughts-on-effective-c11/ Share this:TwitterFacebookLike this:LikeBe the first to like this. Tags: BOOKS, C++ […]
July 10, 2012 at 1:35 pm
My item suggestion would be “throw away your coding rules and start again”. Many C++ Coding rulebooks (including Ellemtel and Misra) are really out of date and need thorough cleanups in order to allow the full power of C++11 to be used…
July 10, 2012 at 11:37 pm
Frankly, the Ellemtel guidelines were already out of date to some degree as of the final standard in 1998, and the restricted scope of the software targeted by Misra (safety-critical systems) has meant that those guidelines were never designed for general use.
All sets of coding rules developed for C++98 will have to be reviewed for C++11, but a surprisingly large number of the ones in Effective C++, for example, continue to be valid, IMO. For details, consult my blog entry on this topic.
My advice would not be to throw things out and start over. It would be to review your guidelines with C++11 in mind, then revise and augment them as appropriate. C++11 is an expanded language compared to C++98, not a completely new one.
Scott