CppCon 2014: Herb Sutter "Back to the Basics! Essentials of Modern C++ Style"

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments

Herb recommends passing a shared ptr by value if you want to add to the ownership of it - what about a constructor?

class PtrTest {
private:
    std::shared_ptr<std::string> m_ptr;
public:
    PtrTest(std::shared_ptr<std::string> ptr)
        :m_ptr(ptr)
    {}

    //...
}

In this example, does the 'ptr' cause an unwanted second refcount increment and then decrement? In the past, I've used a const ref of the shared pointer in the constructor parameters so that i can copy it to the object's members with only one copy.

Or does it get moved directly into m_ptr as opposed to copying?

👍︎︎ 1 👤︎︎ u/ihcn 📅︎︎ Oct 01 2014 đź—«︎ replies
Captions
Herb Sutter: Hey have you enjoyed the week so far? I'm not sure if both my ears are working right. This half of the room, have you enjoyed cppcon so far? You, guys, what do you think? I don't know. They might have had an extra decibel. Let's hear all of you one more time. Thank you. This has been such an awesome week for C++ and also a really festival atmosphere here. We want to thank you all very much for coming and it's been a blast. We're now at our final plenary session. I'd like to introduce the speaker whose name happens to also be Herb. Please, give a warm welcome. Hi. My name is Herb. You're supposed to say, "Hi, Herb." Audience: Hi, Herb. Herb Sutter: It has been 1 year, 3 months and 18 days since my last template Meta program. Today, I want to talk about the importance of resisting complexity. I never thought it would happen to me. I was introduced to complexity by a friend or at least I thought he was a friend. He showed me a cool programming trick on his computer at home in private and it really worked on his compiler. I started using a little complexity, but just a little bit and only at home, but after a while as I got used to using complexity and something strange happened that the thrill faded. It didn't seem as complex anymore. It seemed simple, even though other people around me told me it was still too complex. I started hanging out with people after work for social complexity on bar napkins, but then it started eating into work time that we started bringing it into the office and checking it in. Some of you were at the template metaprogramming hackathon last night and here's a picture. Seriously, the reason that I want to talk about complexity is people who are under the influence of complexity often think that they're thinking more lucidly and really that's not true, but we get so used to complexity that we should remember that it's fine in moderation. Once you know a lot about anything including C++, it's really hard to forget that you're an expert. There's roughly give or take, it's hard to get number people who include, use C++ on their day jobs is roughly around three million for some time and it's gradually, it was pretty flat in the 2000s. It's increasing a bit now, but compare that to many of the experts that you've heard of. How many of you use new GNU, GCC? Lots. Great. You're using the standard library implementation libstdc++, which has been around for quite a while and just over the last almost 20 years I asked some of the maintainers, "How many people have contributed to this?" I said, "Well-known open source project everybody knows that everybody contributes to open source projects, how many people really have contributed to it say more than just a few lines like have contributed something significant to it?" Answer seems to be about 30 in the world, in the last roughly 20 years. How many of you use Clang? If you're using Xcode that's also Clang so put up your hand. Okay. You know how many people or developers of this standard library that ships with Clang, libc++? The answer until 12 months ago was 2. They were both here at this week. One is now getting on a plane, the other still here in the audience. The answer now is about five to seven who have contributed more say just a couple of lines, have contributed something significant. I was not able to get a number of boost developers that's been going on for almost 20 years now. I figure somewhere in the order of say 300, maybe that's high, I don't know, but let's be generous. I saw WG21 meeting attenders who like ever attended more than one C++ meeting, I didn't actually go through all the minutes and count, but I'd be surprised if it was more than 300 people. Here is a Venn diagram of all the C++ developers in the world and the smaller circle is all of the libstdc++ developers, libstdc++ developers, boost developers and anyone who's attended more than one ISO C++ meeting, and if I had put a border, a line around that circle, you couldn't see the smaller circle. I actually had to turn off the line around the circle otherwise you couldn't see the smaller line. By the way, can you see it? We need to give good guidance to the big circle, while still teaching the advanced tools for people who need advance code including very advanced code, but, A, not everyone needs to know all of it and not everyone who knows it needs to use it all the time. That's a theme of this talk. In fact, if you go back to Bjarne Stroustrup's wonderful keynote on Tuesday, interesting factoid. If you go through slides know how many double ampersands rvalue references are in this entire talk? Nice round number. The value of simple usable defaults is really priceless. By the way, I mentioned this on the panel on Monday night, the most important C++ book you should go out and buy it at the bookstore now is A Tour of C++ by Bjarne Stroustrup. We have lot … I know … I saw … You stole my thunder. People are yelling, "It's sold out." I know so, but the good news is that you can still go to the bookstore and they will order it and ship it to you because they do have free shipping, so go to the bookstore anyway or Amazon or anywhere. In particular, this book is important because it encapsulates in one place what every C++ programmer should be expected to know. Now, that's really important. If somebody's coming new to C++ or their learning modern C++ after you used C++98 they often ask, "Well, okay, where, what's all the stuff you need to know?" "Well, look over here on Stack Overflow and Scott's got a book and to be honest [inaudible 00:06:33] longer book and there's some chapters in there you ought to know." Here is a 180 -pages that some of you will have time to read on your plane home this weekend from cover to cover that tells you the stuff that everyone needs to know about C++ will recommend this book. Let people know. Help get the word out. It's really important. I'm very glad that Bjarne has made that available. In this talk, I want to focus on defaults, basic styles, and idioms. Now some experts have pushed back at me on this. When I say the word default some experts treat it as a bad word. They say, "You're dumbing down the language." No. I'm not. A default is not dumbing down especially a default does not mean, "Oh, just do this blindly and don't think about it." That's not what it's about. You should know your other options too, but the default is about … Unless you have a good reason to do something else, which does involve thinking, don't overthink it. Just do this, unless you have a reason to do something more complex. In particular, this goes right in line with the general good advice including these three items at the bottom of the screen, which are the titles of three of the first eight or nine items in the C++ coding standards, but not coincidentally right for clarity and correctness first, avoid premature optimization. In particular, prefer clear code over optimal code. Now, that's almost swearing to a C++ audience. Like, "We should be optimal by default." No, you shouldn't. You should have good performance by default. If anyone tries to tell you that C++ has been about writing optimal code by default, correct them gently because it has not. C++ has always been about writing efficient code not perfectly efficient or optimally efficient, but good efficiency code by default and always being able to open the hood and take control over memory layouts, over the actual, even down to the instructions that get done, when you need to, you don't always need to open the hood. This talk is not about don't think, it's about, let's not overthink because isn't it true? We revel in complexity. How many of you, we're not going to put a camera on you, and none of your friends here will tell you. How many of you would say, "Yeah, reveling in complexity has described to me at least point in my coding career?" I see all the speakers have their hands up. That's good. This is good. We're acknowledging our problem then we can start to deal with it. We overcome denial, step one. Let's talk about range-based for loops. We can overthink range-based for loops. I think that everything I want to say about range-based for loops pretty much fits on one simple slide, and here it is. Why do this when you can do this? Any questions? Now, some of you may be saying, "Oh, shouldn't I be writing autorefref there?" Maybe, but wait for it, we'll talk a bit more about refref at the end, but this is perfectly good. In fact, no matter whether C is constant or not this does the right thing because autoref also because it goes through the iterators. If you get a concentrator back that references to a const, it will deduce the right thing. It'll be read-only for a read-only collection. This is simple code. If you are traversing every element of a collection prefer to write range for. Now, if you need early break, range for doesn't support that yet, but ranges look like, there's a range proposal coming for the November meeting that I think will address some of that and even enable that. Certainly, if you're writing a for loop that reaches every element of the collection just write range for with autoref in case you want to modify in place. Soon, thanks to Stefan [inaudible 00:10:25]. Stefan, are you here? Wave if you're here. Meow. Okay. If I see one more code example checked in with kitty, I think I will scream. For e:c is going to be a shorthand that the Standards Committee is seriously considering, and therefore some compilers, once this committee will bless it, hopefully, at the next meeting for C++17, will already start implementing. You don't have to wait three years to start using this that basically says, "You know what? We're going to declare a variable e for you and it will be of type deduce whatever so the constants will flow through. The nice thing about this is the first thing you might think is, "But how can C++ do that? Isn't that a breaking change?" How many of you suspect that the last line may be a breaking change that can change the meaning of valid existing C++14 code today? Oh, you're all scared. I thought it was, and then, Stefan kept reminding me, "No. No. You can't actually write that today. You can't use an existing variable, you always have to declare a new one." We'll just drop the type. The default, this sensible default will be essentially the same as before, autorefref or autoref, which will do the right thing. This does not mean that you can't lift the hood and write it yourself if you want a certain type or even write the naked iterator based for loop if you want to do something more like break partway through or skip elements. That's all fine, but defaults matter. You are often influencers of people in your companies, of people who ask questions on Stack Overflow, who participate in the community so please, you teach starting from the bottom of the screen up, not the other way around. Now, let's talk about smart pointers. Use smart pointers effectively, but I still want you to use lots and lots and lots of raw pointers and references. They're great. Yeah. Stunned silence. We hate pointers and references, all pointers and references. Well, why would you recommend those? Of course, you should write those. Those should be your default parameter types and return types still. Let's talk about that. Now, clearly, the code on the left-hand side of the screen is stuff that was perfectly good code in books some time ago, but we tell people don't write that anymore. In fact, basically, all the red stuff shouldn't pass check in anymore in new code. Don't use owning pointers. Don't use explicit new. Don't use delete except if you have a real performance need, encapsulated down deep inside load some low-level data structure, maybe, but FYI, I believe even the entire STL can be implemented with no loss of performance using unique pointer. I'm not using new and delete. We're about to try that, and so, that exception maybe even a much smaller set than you might think because unique pointer is roughly free. It is very razor thin overhead, often none at all over a raw pointer. Modern C++ says use unique pointer, use a shared pointer, use make_unique. If you want to write new to allocate a new object, use make_unique by default. Now, if you know, again, it's a default. If you know the object is going to be shared definitely use make_shared by default. If you don't know, you're creating an object and handing it out. You don't know if it's going to be shared, start with make_unique because you can always move that into a shared pointer that then can be used as a shared pointer group, but if you know it's going to be shared, use make_shared. For delete, don't write anything. This is advice we're giving people. How many of you follow that advice already in your code basis today, at least, for new code? Looks like about half. Okay. How many of you have tried to follow this advice and still found some dangling pointer problems with reference counted smart pointers? A few. I have a slide for you. I know what the problem is. There is a cure. Wait a few slides. A very important qualifier on this. This slide says don't use owning raw pointers and references. What does that mean? Really, a pointer that you should be calling delete [inaudible 00:14:46] at some point. Non-owning pointers and references are awesome, keep writing them especially for parameters and return values. When you talk about structured lifetimes where one function calls another, and that callee executes entirely within the scope of the caller because then you return and resume the caller. Anything that the calling function is keeping alive stays alive for as long as the called function. Now, there is a reentrancy case. We'll talk about that, but generally, this is nice. It's Russian dolls all the way down where the lifetimes nest perfectly. You don't need ownership transfer down to call stack unless you're going to take something out of the call stack. You don't need to talk about ownership. In C++98 classic we would say, "Hey, if you're, if you need to look at a widget and it say, 'It's a required parameter,'" and I'm ignoring const. We'll come to that later. Pass it by reference or if it's optional, pass it by pointer. Are you ready for the modern C++ advice? It's the same. It's the same advice. Now, if you have a unique pointer, a shared pointer then, you want to dereference it or call get to pass a raw reference or a raw pointer, but raw references raw pointers for the win. They still are the preferred parameter type. Think about it this way, if you're not convinced about that and you feel uncomfortable think about it this way, unless you're actually talking about ownership transfer, in which case it is perfectly valid to pass smart pointers and I'll show that in just a second. Unless you, the called function actually is going to participate in the ownership somehow, if you just wants to look at a widget, why on earth does the callee care if you're managing that lifetime by a unique pointer, shared pointer, whether it's global, whether it's on the stack? He should be agnostic to that, and this is how to write that, very simple, and the classic advice is still true. But antipatterns hurt pain pain as STL would likely say, "He's got me in the habit of talking like this." It's viral. The antipattern here is just routinely passing a reference counted pointer even by reference. This is not just about shared pointer it's about [com 00:17:00] pointers or any kind of reference counted pointers, the ones in boost as well or passing it by value, which is even worse if you don't really intend to keep a copy after you return, to participate in the ownership somehow because if you go back to that, because what's the performance of the lower-left code? You're going to do an increment decrement on every call, which isn't the end of the world, but, A, it is an atomic operation so there's synchronization, and it's not that cheap, and second, this is premature pessimization. Premature pessimization is when you have two options that are equally complex, neither is really simpler than the other. Well, you should take the faster one. This is opting for something that is actually a little more difficult and slower. If all you need to do is look at the widget, pass a reference, or raw reference. A related antipattern besides passing function parameters is in loops. When people will frequently make copies of smart pointers and loops and that's even worse. We don't want to do those things. For example, Andrei, when I was discussing this with him some months ago he mentioned that in late 2013, Facebook had an experience with this where their product rocksdb, which was passing shared-ptr by value quite a bit. Change those to pass by raw pointer and raw reference, and observed a 4x speed improvement from 100k to 400k queries per second in one of their benchmarks, and other improvements across the board. This stuff matters. Before asking for needless work, we'll pay for needless work. I wrote a [inaudible 00:18:38] the week about this recently. Reference counted smart pointers as well as unique pointer. Any owning pointer are about managing and owned objects lifetime. Only use it as a parameter type if you actually want to talk about the lifetime, you want to participate in it. The callee is going to keep a copy of it to put in some global registry or something so he's going to participate, keeping it alive. This applies to any reference counted smart pointer, but we have shared pointer so we should know about that. How do we pass smart pointers? If you are writing a factory that produces a new object especially an object that's polymorphic so you're not going to return it by value if it's polymorphic in particular then prefer to return a unique pointer by default. I will show if you know if it's going to be shared, prefer returning a shared pointer and using make_shared, that's in the second half of the slide, but a default, a good default is return a unique pointer. Why? Because if you don't know that something more is needed like shared pointer, anybody who gets that can do whatever they want with it if they ignore the return type, it doesn't fall off the floor. The return value doesn't fall off the floor, it gets correctly cleaned up, although, why are they asking the factory if they're not going to look at it, but whatever, maybe they're passing it as an rvalue to something else that is a temporary expression and cleanup just happens. If they want a shared pointer they can move it into that. If they have their own juicy custom shared pointer that they are using in their shop that's non-standard and gloriously customized they can call that get and move it into that. This is what we should be returning from factories. Similarly, if we're accepting, our function is accepting ownership of the object passed by unique pointer by value that shows that we're consuming the widget, you can't call that with a unique pointer in the caller without saying std move. That helps you see I'm transferring ownership in. We'll come back to that one. If I want to reseat, notice I'm not, it's not about changing the widgets. It's about if I want to reseats the unique pointer itself to point to a different object. Well, that's a typical in/out parameter take that by reference. I have no idea why you would ever legitimately want to write const unique pointer reference, why would you want to simply just look at it? There's nothing useful I know of that you can do to a unique pointer except get the widget in which case you should have just returned, you just taken widget ref. As far as I know, writing that as a parameter type is a think [inaudible 00:21:03]. Now, as I mentioned if you know that the object is going to be shared that a factory returns definitely return a shared pointer, and you're going to use make_shared inside because that gives you a nice optimization. It's one memory location instead of two and some better locality that you'll keep benefiting from. If you're going to have a function that takes a copy of the shared_pointer, pass the shared_pointer by value. Notice this is saying I am going to keep a copy either I'm going to put it in some other data structure or do something else with it, and this will retain a reference count. If I want to reseat a shared pointer that is make it point to something else, pass it by reference that's as if it will or it might receipt the shared pointer, and finally, if you're in the shared case but it's not unconditional. Sometimes you're going to keep a copy sometimes you won't then take const ref, and then, if you take a copy then copy inside if you do actually need a copy that way you're not going to incur the cost of the reference count bump in the cases where you don't actually keep a copy so that's if you conditionally keep a copy if you might share. How many of you are thinking, "Hey, wait a minute for sync, shouldn't that be unique pointer refref?" I'm curious how many people are thinking that? Yes. I have slides for you later. How to do it right? This is partial advice. Never pass smart pointers. By value prefer, unless you actually mean to traffic in ownership. Prefer raw pointers and references. They're still great. Express ownership with a unique pointer by default, but shared pointer if you're going to share and use make_shared in that case. Now, when I post these slides will be available after the conference. I put this yellow Post-it to obscure it on top so when you print it out you can't print out this version. I want you to print out the version two slides from now where I'm going to add one more line. I promise to tell you the pitfall with reference counter shared pointers. This is something that if you've been using reference counted pointers for some time you've probably fallen into. I know that most teams of any significant size, in software of any significant complexity that especially that go over a boundary, and then, get callbacks into themselves they sometimes find they drop a reference count like, "What's going on?" This is not unique to shared pointer, it happens with com pointers, it happens with any other reference counted pointer you might use. If you're an objective-c developer, it will happen with those. They're slightly different with retain and release semantics and so forth an arch, but this principle applies in general. Let's talk about the one case because I believe as a community we now know exactly how to simply articulate it and to simply solve it. First here's the problem. Take a second to absorb that code, and think about what would be a problematic caller. We have some shared pointer that's static or global, non-heap or it's aliased on the stack, non-stack or aliased on the stack, g_p. It's a shared pointer to a widget. Now, I've got a function f that takes a widget by reference then call some other function that uses the widget. Function f looks fine to me. Oh, by the way, function g happens to among other things receipt the global pointer. Maybe it's got a reference to it that's passed through another parameter, maybe it's some globally visible thing on the heap that it can get access to. One way or another it receipts it, and now, in your code, if you happen to call f of star g_p because you're following the advice I just gave you to just pass raw references, right? You're following the advice I gave you, and now, what will happen? Bad stuff. Why? What's wrong with this code? Shout it out. [crosstalk 00:24:57] The assignment to g_p might release the last reference count on the smart pointer, it might not, might pass your unit tests and, but if it does release the last reference count then in f, when I call g, I'm suddenly actually blowing away my w object. This is not obvious, and no, but that's laugh worthy, but that's not obvious. That is so not obvious. Then, I use w and it might or might not be there anymore. Might be the stored memory might even be gone. The antipattern, so what is wrong in the code? Like who do you blame? Don't dereference a non-local possibly aliased shared pointer. That should no longer pass code review. I'll show you in a second what I mean and what to write instead. Pin it using an unaliased local copy. You want a shared pointer that's … You can dereference a shared pointer safely without this re-entrance problem if it's on the stack and nobody else has a pointer or reference to the shared pointer. You haven't effectively made [inaudible 00:26:04] passing out an alias to your stack based shared pointer. In the code at the bottom, you're perfectly immune to this. Why? Because you say, "Before I enter my call tree, which could be hundreds of functions deep, just once, at the top, where I go from knowing that this object is owned by a shared pointer to passing it in at the very top for the first time, converting it to a reference. Only there take my extra add ref, take my extra increment once, and then, for the whole tree. You can statically test for this and do lint-like testing for this to make sure that you only dereference local unaliased reference counted pointers, and if you do that, you will eliminate that entire class of problems and it will only cost you the extra add ref or the extra increment exactly once, exactly where you need it and it's an easy pattern to follow. By the way, when you dereference it you might also get a pointer and one easy way to do that is to call a member function, do the same thing realized there you are also converting to a raw pointer and follow the same advice. Just in case, the member function is re-entrant somehow and might change the location of the object itself that you're executing the member function in. That one happens less often but follow this simple advice, this one simple trick. It's the modern way. I won't make it link bait. I'll tell you the answer and you won't get the problem on the left-hand side of the screen. Now, here's the summary of how to do it right and I'll add just that one green line. Remember take the unaliased and local copy tree call tree and don't pass the direct reference, direct dereference of a heap-based one. That's reference counting. Write make unique by default, make shared when you need to instead of new and delete. Don't use owning pointers or references, but remember that non-owning pointers references are ideal on function parameters and return values because they're stack. They're structured case, the scoped case where they're simply observing and they're wonderfully efficient and good for you just as they always have been. The only time you actually want to copy or assign or pass a reference counted point or any smart pointer is if you actually want to deal in the lifetime. If you actually want to modify the lifetime semantics do it there. It's funny how people are afraid of four little letters. How many of you have had one or more water-cooler conversations, arguments, debates about auto? Yeah. I'm going to tell you the truth and you can tell everybody else that they're wrong. Are we good with that? Yeah. Like we're all … Like even we're all going to agree, but let me offer some guidance. Let me justify why I'm offering the guidance, and then, it's up to you, we're all grown-ups to decide whether you'll follow it yourself, but it's guidance that I think is simple, works well, saves from pitfalls. Therefore, it makes sure code simpler so it's a good default to use, and is part of a broader shift in the syntax and style of modern C++ that as it continues to move to a regular simpler style. Now, our first plenary speaker, Nigel Tufnel. That wasn't one of the options and, oh, it should have been. I was looking … I went to the Scott Myers [inaudible 00:29:40] site because I wanted to see if you could put a write-in response, but, no, you couldn't. Nigel Tufnell also known as Christopher Guest, who has played that character on Spinal Tap. It's okay. It's really simple. Auto is not that hard. In fact, if you get Scott's book, which is almost out, there are two items saying roughly what I'm about to say. Some of the details are different saying roughly what I'm about to say about using auto. Here's the spoiler in one slide. When you're declaring a local variable, this is all about when you're declaring a local variable. If you want the type to track, and I'll give you reasons why you often do, simply deduce it and the easiest way to do that is to have the type of that local variable be auto. Yes, there will be times when you want to have the type stick, and that's perfectly good. The advice is not to use, just deduce everywhere, the advice is to use auto pretty much everywhere because auto does not always mean deduce. You can as the second last line shows, use auto and still explicitly ask for a type. That it makes it concrete. Now, you might say, "Well, why would I do that instead of just the last line?" That's a style point, but I hope to convince you there are at least some reasons to consider even from stylistic grounds, but also on correctness grounds the second last line, but they're both good. If you want to make a type track, which makes your code more robust and maintainable deduce it, you'd be guaranteed don't get conversions and other advantages to make a type stick do say it, but you can still use auto. Consider this code. What does this code do? What would you name the function? Anybody want to throw out any function names? Say again. Add another. Hey, that's a good one. [crosstalk 00:31:33] Add if not there. Okay, that's even better. Add if not there. Anyone else? Everybody in agreement? [crosstalk 00:31:40] How about append unique? We've got a container, it's pretty much the same as what you did. I'm just trying to use the STL style because, hey, it's there. We want to append the unique value. There's a container, a value. We do a fine to c, whether it's in there, if they find returns end, it wasn't there, and we push it back, and we're good movers. Then, we assert it's not empty because that's just a good thing to do. How hard was that? We'll come back to this code very quickly. Why not just deduce the type? There are arguments why you shouldn't deduce the type. This isn't just about using auto, this is about, "Oh, I don't like type deduction. It scares me." Well, the arguments often fall into the category of, at least, the ones you hear the most often. "Well, I can't see the type in my code, therefore, I worry." Occasionally that can be a valid thing. I'm not saying it's not. Most of the time that's overthinking. In particular, saying, "Oh, what is the type of something?" Well, first of all, if you have an IDE, this is the minor argument because it doesn't really matter, but if you have an IDE, even Emacs, then it doesn't matter because you can see the type. Also, it reflects a bias, which is much more important to code against implementations, concrete types rather than capabilities, interfaces. If you look at that example we just saw that was hardly unreadable and yet there was no concrete type mentioned there. Unless you count void as a concrete type for, yeah, yeah, to be pedantic. I call it not a concrete type. It's not a real type. It's void. It's nothing. There's no concrete type mentioned here at all. What type is container or value? "Oh, Well, that's … I don't know, actually. Just some type that I can call push back on and call begin and end on." What's the value? Something I can move into that containers push back. "Oh, the container should also have empty, but when I call dot empty what type does dot empty return?" Well, you might say bool, well, who says? For a long time it returns some weird conversion to something we will like, but who cares? You don't want to know. You don't need to know what it is. There's something testable like a bool, good enough. For templates, for return values, we often don't use those types anyway especially what we call functions and expressions. We don't explicitly type those types in our code anyway. I point this out as an example just maybe to be less afraid because once we realize we're already doing this a lot, maybe that makes it less fearful. Now, let's talk about correctness. With deduction, you always get the exact, the right type. I want a new local variable of his basic type. If I am now going to take v and call begin on it, is this a good line of code? Would that pass check-in? More to the point, would that pass compilation? Why wouldn't it pass compilation? [crosstalk 00:34:44] Const. Right. Well, it should be … Well, it might be const iterator that would be good. I could do that instead, but I'd have to think about it. Now, and if I change the parameter type for any reason like maybe it's const today, but then later I change the parameter to be non-const because my function also does some modification that it didn't do before now I've got to go through and do a ripple to update parameter types if it switches between say const to non-const. If you just say auto, which is a great default, it just finds the right type. It's correct including under maintenance. Let's talk about maintainability. Using deduction because it tracks the type makes your code often more robust under maintenance. It means that we can actually change our code more introducing fewer drive-by bugs or other things that need to be updated while we're updating our code. Let's say for example that I changed the code from line one to line two. I've added a dot zero, which makes the 42, instead of being an integer, be a floating-point value. Now, this code compiles but under maintenance I've changed that to be floating point and I forgot to update that variable type, maybe I didn't intend to have a silent narrowing conversion there because that's what will happen. The code compiles and narrows. If I have a factory that returns a widget by value and if under maintenance the factory changes to return a gadget, which happens to be convertible to a widget then we now are getting that conversion and we're not tracking the type. Now, if we really wanted a widget that's fine. If we want to stay with widget even if the factory changes to gadget that's fine, very often we don't. If I have a map that's iterator and I call begin on the dictionary, but then I realize you know what map? Map is tree-ish and I've heard of this nice hashish. It's got to be bad Washington joke there. Unordered containers, I'll just change my map to an unordered map because most users just work. It's largely a drop-in replacement for common cases and this code compiles, and then, fails to compile. What do I do? Then, I have the ripple. I have to add unordered map there because I have the maintenance ripple. In each case, if I had put auto, I would have avoided this silent narrowing conversion tracked the type. If I had said auto in the second case, I would have continued to use a gadget, which is often usable as a widget, but I would have retained the type in its extra characteristics and with the third auto I would have tracked the container type when I've used a replacement container that's otherwise compatible. These are good features. Now, a third reason to use type deduction is performance. Now, this is one of those ones where if you want to convince a C++ audience, as long as you can make a credible argument that, "Hey, do X. it's good for performance. They'll like jump in a river or a lake, there could be sharks, it doesn't matter because they'll swim faster, they're efficient." Give me performance it's like a will of the wisp. It's a way to draw in C++ developers. I'm going to assert that using auto gives you better performance by default also and the reason is because you're guaranteed no conversions. If you use auto without otherwise as we'll see in the second part talking about a specific type, you are guaranteed there are no conversions means no temporary objects, means no extra work being done that's invisible that otherwise we sometimes hand ring over. There's usability, so finally, yes, there are certain types that are hard to spell. That are inconvenient to spell, lambdas, binders, helpers, and unless you want to get really familiar with that whole type, and I am not suggesting that, auto is your friend. Finally, I know I have to say, "It's less typing." But, you know what? I have to say that because people notice that, but like please don't point to this slide out of the whole talk and say, "That's why Herb is saying use auto. It's less typing." Like that's the last least important and least interesting reason, but, yeah, it's actually less typing too, which is kind of cool. Prefer auto x equals expression by default unless you have a reason to do otherwise, on variable declarations. It gives you correctness, clarity, maintainability, performance, simplicity, and those advantages are something that are worth something. It also shows you're habitually programming against implement interfaces, not implementation, not against concrete types, but against concepts, which you'll be doing more of in C++ of the future. Now, having said that this is all if you don't need to commit to a certain type. Sometimes you absolutely do need to commit to a certain type, then what? You can still use auto, as I already teased. If you look at the left-hand code, which is the old style C++98 code sometimes slightly upgraded for the brace initialization, but it's the old style of put the type first, and then, there's the newer style of start with auto. The first two lines are essentially the same so auto s equals "hello." Auto w equals get widgets. We deduced the type. If you need to commit, that's if you don't need to commit to a type. If you do want to commit to a type try just sticking the type, name on the right. It will feel weird the first couple of times, and I think most of you will probably find that after half an hour you won't even notice it anymore, but let's talk about reasons and advantages of doing it this way because those are pretty much equivalent. I'll have one slide in the extremely rare cases where they're not equivalent. Like of types that you can't even move, but let's talk about the advantage of doing it this way. Notice that we already do this for heap allocation, both old-style heap allocation, new widget, which, of course, you'd never want to write anymore, and also, make unique, make shared of widget just naturally it already appears on the right because that's where the type goes whenever we allocate on the heap. That's a consistency argument. Does this remind you of anything else? It's like new style function declarations, which also put the return type last or just omit it entirely. Thanks to C++14. We're seeing a consistency here and it's not just that the letter A-U-T-O lineup. There's a deeper consistency here. Everybody at this point is probably thinking, "Herb, you are not seriously telling me not to write int i = 42; are you? Why on earth would I write auto x = 42? That's a whole another letter." Also, you could make the argument just looking at those two only side-by-side that the first one, the green one, is green, it's good, its tercer, it's clear, right? Let's talk about this elephant in the room. Here are the similar examples that we started with before and now let's add interest. Int x = 42, auto x = 42. Without even going further I could make a consistency argument here. Some percentage of you wouldn't believe me, wouldn't agree. That's fine experts can disagree, but I could make a consistency argument even going no further, but wait there's more. How many of you enjoy using literal suffixes for the built-in types like floats and shorts? Okay. A few. How many of you enjoyed C++11 user-defined literals? How many of you have started to maybe even use some of the C++14 standard library user-defined literals? Okay. There will be more of you. The more of you there are … Kelly, you implemented them. Yeah, and the more of you there are the more this advice will work. Today and even with C++ 98, well, on the left-hand side I could write float x = 42., and on the right, I can say auto x = … f, which is a floating-point literal. The right side is better because there's no narrowing. There's actually a conversion on the left-hand side that mostly you don't even think about and never see. I'm not saying it's a big deal. I'm not saying it's the end of the world. I'm saying this is the subtle stuff that you can stop having in your forebrain that you still know exists, you're not being dumbed down, but you're avoiding overthinking as you're expressing the logic you actually want to express in your code instead of thinking about the details of a language. Unsigned long has ul, and I could show some of the others. Same principle and notice, again, we're having the type on the right. This exists already for the built-in suffixes, but we have also user-defined literals. In C++14 if you say "42" s that's a std string. A really convenient way to spell a std string literal. I just used auto and it's a std string. Again, those two lines are equivalent the auto one I argue is clearer. Chrono nanoseconds, I don't like writing chrono nanoseconds any more than you do. I don't mind ns at all. Again, remember functions lambdas, aliases, I throw lambdas in there too because I mentioned functions, but of course, lambdas when you have a named lambda, how do you capture it? The way you capture it is auto function name equals my lambda. The signature again is on the right. That is just the way that you do it for named lambdas. Notice going beyond auto a little bit, using, which is the replacement you should be using now for typedef does the same thing, and that works even if it's templated. It does it the same way. Are you seeing a pattern? When we think about this, we are in the, well, through a transition and it still continues, but we're already well enough through it to see what's happening where the C++ world is moving to left to right consistently, at least, by default. You can still write some of the others if you want to, but as a default, there's a lot of consistency across auto variables, literals, user-defined literals, function declarations, named lambdas. That's including the generic lambdas, aliases, template aliases. It's all good. Now, let's talk performance because some of you may have been wondering … Well, let's go back to that first one. If I don't want to deduce the type I want to track the type auto x equals value. Well, since I was a young sprout C++ programmer back in the day and going uphill both ways through drifts of foot high snow, six-foot-high snow. That sounds better. I know to be suspicious of the equal signs because they mean assignment copy. Well, first of all, that's not an assignment, right? Because that's something we've had to learn just a quirk of C++ syntax for a long time. That's an initialization. There's no assignment here. We're constructing x of some type. Now, does this create a temporary copy, a temporary, and then, copy it because in some syntax it would copy? Does it here? Turns out standard says, "Nope." That's a good thing. The reason is if you're some [inaudible 00:46:01], but it basically has the same meaning. Notice that tx = a has the same meaning as tx [inaudible 00:46:07] when a has type t or derived from t. Turns out when you say auto, you always have type t or derived from t. In fact, you always have type t. It deduces it. That is true by definition. It's a tautology. That equals costs to exactly zero according to the standard. Now, if you commit to a type, auto x equals type value so that's the second one where you do commit to a type but you can still use auto. Does that create a temporary and move from it? Yes. That copy can be elided and in practice, many compilers already do elide that temporary, but full disclosure, yes, the basic language semantics are that is a temporary and a move, compilers are getting really good at it. There is one case I know of where you can't use auto style, it's where you're using the explicitly typed version that we just talked about, auto x equals type name initialization expression. You have a type that's not movable or cheap to move. A mute x, an atomic, an array events, such std array. Those are just a handful of types where you can't use this idiom, declare them on the left as always. In the array case, it will compile, but be slow because it doesn't, it's not cheap to move, but most of the time you can use this idiom very well. That's the only corner case I know of. For completeness, a few people have suggested other corner cases. I document them briefly to show you I'm aware of them with some sketches of why I don't view these as a counter examples at all, but let's document the list, and what you should use instead often in the comment. One recent time I was resisting using auto, I was still on the fence selection. This is auto everywhere, maybe this is too much of a change. Every new feature gets overused. I don't want to be that guy. I know I'm going to be that guy sometimes we all are, but at least here I consciously don't want to be that guy, at least, for this particular example. I wrote base star pb = new derived in my original group of the week and when I upgraded it, I left it be unique pointer base pb = make unique derived. I myself when I would go back to this article a week later, readers, when they read the article, would keep on asking, "Why didn't you just write auto on the left?" I actually changed it to auto. "Oh, yeah, that's right. I should have put auto there." I put auto, and then, I realized, "No. That changes the meaning because there's a conversion." It's a make unique derived going to unique pointer base. If I had followed my own advice, which now I feel much better and I do, and simply stick to my guns and put auto first. Yes. That's an unusual construction the first time you see it, and then, you realize, "Oh, it's just auto variable equals type and if there could be a type, caste expression or a constructor explicit type on the right-hand side. Then, it's perfectly clear. Just moving it that much over made it perfectly clear, at least, to me, and a number of actual readers that the code isn't a bug. "Oh, yeah, you're actually doing a conversion there." We just find in practice the middle one was too easy to miss, even though now we're all aware of it come back a week from now and take a quick glance and you might roll the dice on whether you remember. It's just that much distance makes it subtle. Preferred declaring local variables using auto, whether you want to track. It's just auto and bind directly to the type or stick to a specific type, in which case you want to explicitly mention a type but you can still use auto. Here are the two syntaxes. It guarantees no implicit conversions, no narrowing conversions, and there's one thing I mentioned on the slide, but I forgot to say in words so let me add it now. If you follow this style it is impossible to write an uninitialized variable. Why? Because when you say auto x equals you, auto is meaningless without an initializer. It is impossible syntactically to write the bug of uninitialized variable, which is not bad. Consider having some functions in headers also use auto. The new C++14 feature you can have auto be the return type and deduce it from the body just like lambdas can. That's actually a nice thing to do there in headers anyway. You can do that and the bodies have to be available, and you want them to track the type so auto is often a very good return type for those. Since we're talking about auto, I thought I should mention return types as well. It's simple. We can tell Nigel Tufnell and all the other C++ developers in the world, who agrees, that to make a type track deduce, to make a type stick commit to it, in both cases, you can still use auto, try it. You might find that it's consistent, simple, and not complex. Give it a shot. Now, let's turn to a specific area of overthinking. When you think of how much the C++ has evolved since C++ 98, if you look at the 11 and 14 standards, you could point to a few features as being big ones that really change the game in terms of how the type works and how our idioms might work. Probably the biggest of those is rvalue references and move semantics. The idea that we have move semantics, C++ always has be able to deal very well with copyable types, value types, and really move is largely an optimization of copy. Yes, you can also write move only types now, which is a new thing, but largely it's an optimization of copy, which means copyable types that C++ has always been so good at compared to many other languages that are mainstream just got even better, but what this means is the community needed some time to absorb. Okay. Well, what does that mean for my programming style? We needed experience from trying to develop guidelines, seeing which one's worked out which ones didn't. I think that this summer, and in particular, in the last couple of weeks, and in particular, this week at this conference that we're ready to say, "Okay. We have got some pretty good guidance specifically on how does move semantics and the other features that are new in C++11 and 14 effect parameter passing. I'm going to go out on a limb and debunk what I think are some antipatterns and why I think that they're antipatterns, and why the C++ 98 default actually should get a lot of love in C++14. One subtle change that was already happening even before C++11 because compilers now routinely implement the return value optimization, use return value, return by value way more often. You'll see me mention that, but don't overuse pass by value. I'll explain what I mean. Observation, Bjarne is very right. New features tend to get overused and Scott made a very interesting point as we've been having discussions over the last couple of weeks about what are the right parameter passing guidance guidelines because a lot of people have said since we've got move semantics maybe we should pass by value more, maybe we should do other things to try to get those juicy rvalues that now we have access to, right? Like now, we have access to rvalues let's optimize for those. Now, we're mesmerized and we're going off on that. We have had such a focus on rvalues that when Howard Hinnant and others have come up saying, "Oh, by the way here the, if you do that way you're causing these problems," which I'll show you in a moment. One of the realizations Scott put very well is, "Oh, you know what? Those problems are with lvalues. We've been focused so much on optimizing rvalues, we're not noticing how often those things are actually pessimizing normal passing variables to functions. I'll show you what I mean. What's important here is two things. The advice is still simple. In fact, it's exactly the same as C++ 98, the default advice that I'm going to show you, and second, you have to think about all of the things your callers might give you, not just focus on the new thing such rvalues. Just as exception safety is not about writing try and catch everywhere, using move semantics is not about writing move and rvalue references everywhere. In fact, remember I said that in Bjarne's keynote on Tuesday, rvalue reference didn't show up once in his slides, it will also not show up once in our default parameter passing advice. Let's see. First of all, thanks to many, many people including more not listed here for discussions over the summer and leading up to this to firm up. Here's a great opportunity with cppcon. Let's talk about this and talk it through and see if we can converge on new parameter passing advice. I believe that we have time will tell the level of agreement, but I believe we have, and so, I'm very glad to acknowledge these people and more for the input on what's to follow. Here is parameter passing advice in C++98 that is reasonable default advice to give people. On the vertical axis we have, what are you doing with the parameter? Is an out pointer, is it an out, in out, in, and in particular is it in, and then, I'll keep a copy. It turns out to be an interesting case to call out especially. Across, we have, well, is the type cheap to copy because I maybe I'll pass it by value more often. Is it moderate cost a copy or maybe I don't know or is it expensive to copy like some big data structure? For the out case, we've more and more because we have the return value optimization in modern compilers, we can just pass by return by value for moderate cost, and you'll notice the footnote, moderate cost means, yeah, if it's like a k and it's contiguous and hot and cash, yeah, just passed my value or just returned by value. That's a new thing in the last 10 years, but we're about 5, 10 years in now to where you can assume your compiler does that optimization so you can actually return by value in all those cases. If it is expensive to copy like a vector or a bigger array then the C++98 advice was passed it by reference. That was really like the in-out case or footnote returned an x star, allocated on the heap and return it, but that costs you a memory allocation, you have to decide if it's worth it so I mentioned it as a footnote, but generally, it's the reference is what you use. In-out is easy, of course, passed by non-const reference because that way you can modify the original, and finally, for all the in cases, usually passed by const reference, but, hey, if it's an int, pass it by value. None of this should be news except possibly that for the out case we can return larger objects than maybe 10 years ago we would have wanted to do, but this is journeyman. This is not surprising advice anymore C++98 advice. What's the difference in C++14 then? Modern C++ advice. Are you ready for the difference? Don't blink, you might miss it. There it is. Let me go back and forth. See the difference? The main difference is the advice is still the same, but we're move has come in is not changing how you pass your parameters, but the applicability of the advice, the default advice actually becomes even more usable because more types fall to the left and out of the out parameter exception. In particular, if the first column is now, is it cheap or impossible to copy such as unique pointer. It might be impossible to copy. That's a new thing. The middle column is now is it cheap to move or moderate cost to move. Notice vector has now moved into the middle where it used to be on the right-hand side. The defaults have become more usable, and if it's still expensive to move, not copy now, but expensive to move, which is a smaller set of types they're still on the right-hand side, but that exception case has been shrinking, which is kind of nice. Again, back, forward. This is the slide that I would like you to print out and pin to your cubicle wall, not the next slide. The next slide is going to be, and here's the, if you open the hood, here's advanced advice that will be the one you're tempted to save. Well, I want the advanced stuff so I'm going to pin that one to my cubicle wall. Please, pin this one to the wall. The summary, and here this is a direct quote from Bjarne as we were working through this. "You know the defaults just worked better." This is still the right advice. That's Bjarne's opinion, it's my opinion, and I think that even Scott and I agree entirely on this with the exception that he wants that one extra line on the next slide to be on the first slide too. The in and move from and I don't, I still don't think it's worth putting in the default advice because the default advice, we just talked about covers all types including move only types, and includes passing unique pointer by value just like I said earlier to a sync function. Now, if you want to optimize C++ is even better for giving you advanced knobs, and now, this is where you see rvalue references come in because notice the in entertain a copy line in the middle part. Now, on the previous slide that said passed by const reference, right? Reference to const. Now, it's, and by the way, if you want to optimize for rvalues, add an overload of x refref, rvalue reference. For the new case if we want to break out the explicit case of, "Okay. I'm going to pass in a value and move from it then as an advance knob if you want to optimize that, you can pass x refref for unique pointer for example that often won't be an optimization. Actually writing a unique pointer refref as a parameter often will not actually be an optimization and the reason is because if you pass an actual temporary in by value to the unique pointer. That's a move, and then, you move out from it again, and then, inside the function, so you say, "Oh, that's move plus move." If I do unique pointer refref that's a single move, right? Faster, must do it, hold on, wait. Compilers can optimize that. They do. In practice, even by value, it's just a single move, and besides, if you're worried about a unique pointer move that's like moving a raw pointer like copying a raw pointer. I do not believe for a moment that 99.99, maybe 99% of code will ever find that in the hot loop, but hey if you want to optimize it move for other types that are movable, move only, but maybe more expensive the new pointer, do know about that one. Now, a few comments about this. First of all notice, there's a red as in danger, dragons be here, special case. Yes, for those middle cases at the bottom. You can also write perfect forwarding in certain cases. Well, you could write that if you were able to write it. You'll see it mentioned on a later slide, but we'll warn you before it comes up on the screen. Bjarne summary, which I agree with is defaults work better and there are more optimization opportunities than we had before, but remember they're optimizations, therefore, don't reach for them prematurely. That's just good software design. A note here, you might be thinking, "Well, but I have to write refref on my move assignment and move constructors for my class." Herb, how can you get up on this stage and tell me with a straight face here in … that I shouldn't be writing refref in normal code. This is a subtle point, but it's actually a really cool one, I think. Writing refref is nevertheless an optimization, but the advice that we give here in the entertaining copy line is exactly what you write for your constructors. You overload them on const x ref and x refref. It's exactly what your right to express the lvalue in this and rvalue in this for move assignment versus copy assignment. You overload on const ref and at const refref, and don't do that unless it's an optimization. Now, the thing is if you're writing a type that's going to be widely used, now your library writer hat and you will have many users it makes sense to reach for such optimizations by default, but this is actually entirely consistent advice it's just when you're writing a class, you'll reach for optimizations more often because you're supporting a larger audience. That is normal. Of course, and especially in the standard library that's why you see this all the time in a standard library even perfect forwarding in STL implementations because they're so widely used. As you go further down, you reach for optimizations more, but it's the same advice as for any other code, overload on refref if you're going to move from it. Now, there is one drawback to that. Let's talk about these options. Again, more optimization opportunities. Those are the things that are new. When do you write, and I have to say rvalue refref? Again, like I just said, "Write it only to optimize rvalues, which could be move assignment operators, move constructors, but just as the exception safety isn't about writing, trying catch a lot, move semantics, using move semantics isn't about writing, move, and refref a lot just return by value move will kick in, pass by value move will kick in, but wait, let's dig into an example. Because one option that's been discussed quite a bit since 2009 has been, well, hey, move semantics are coming. Now, move semantics are here. Maybe we should pass by value more often and there's a really nice article that does a detailed analysis of a compiler optimization, how they interact, and if you pass by value you can accept lvalues and rvalues, named objects and temporaries, and it will move from the ladder, great. You'll still get a copy for the former, this is for the in and retain a copy case. Should I use f of x? Pass by value here. How many of you have seen the advice to do that? Okay. You will want to pay close attention to this nice reasoning. It's interesting this advice first was as a result as near as I can tell of Howard Hinnant, a very experienced library developer. I did most of the [inaudible 01:04:41] libc++ implementation, and metro works before that and much more, who noticed many of these optimizations, and then, told other people about them, and so, those other people popularized this advice, but Howard himself has been pointing out, but wait there be dragons there too. There are downsides to doing this, and so, I especially want to call out to Howard. Thank you for educating me this summer about the information to follow about why this advice is actually problematic and what you generally don't want to do, but there's one specific case where it's useful. The good news is this can, in fact, be faster than C++98. For the in and I'm going to keep a copy case because I'm going to move from rvalues. I still get to write only one function. This is good, but it can all so be much slower than C++98. Now, here's the reason. If I'm being passed an lvalue, a named object. Remember, this is what made Scott say, "Oh, yeah. It's about the lvalues." We're focusing so much on optimizing rvalues, what's the cost of lvalues? If I'm being passed a named object here, I am going to unconditionally copy it, and if inside the function I'm going to move it then into an existing string that move into the existing string, the move is an assignment, and it means I've unconditionally copied, and then, assigned. Now, what if that string if x is a string or what if that vector if x is a vector is smaller than the capacity of the thing I'm eventually going to move it into. Well, if I had just assigned it directly I would have reused the capacity. It's much more efficient. I would have had not had a memory allocation because the destination already had the capacity, but because I express f of x upfront, especially in cases like large strings and a vector that might have a capacity already. I cannot reuse it because I'm forcing a deep copy and a memory allocation unconditionally, and then, moving in. Let's take a look at examples and numbers. Here's an example that it would be a crime, it would be a horrible thing if we couldn't give a simple answer to this question, and yet I have seen people, I have seen them have long arguments about how do we answer, how do I write set name? I won't share all of the answers with you, but some of them are so long that I'm reminded of Bjarne Stroustrup's words that speaking of how C++ is being taught by some people, and in particular for teaching C++ using C first, but that's not the only one. He says, "If that's C++, I don't like it either." We have to have a simple answer to this. How on earth could we have a modern language where we can't say employee has a string name and I have a set name function? There should be a simple answer, and there is, to take that parameter, to change the names with the new value, but there's been a lot of overthinking going on. Let's step through it slowly not because it's hard, but to dispel some overthinking that has been happening. Option one is the answer. You already saw the slide. It said, in this case, the n plus copy case unless doesn't enter something, pass it by const ref. You already knew the answer from the previous slide, at least, you knew the answer I was going to give. Just pass const string ref, and you know what? That's the same answer as C++98. There ain't nothing new here. Let's analyze it a bit. There's always going to be a copy assignment here, but usually much less than 50% of them will perform an allocation. If it's a small string, which are many systems is 16 or 24 length or 11 or something like that, the std string implementation every major std string implementation already implements the optimization that small strings are stored in the string object itself using a union trick basically, instead of doing a heap allocation, which means that you can use lots of strings in your program and never have a single heap allocation, and if you've wondered why, small string optimization. It turns out this is a good thing to optimize not overly prematurely in the library because there are lots of studies showing that the vast majority of strings in many modern, many mainstream applications are short. It makes perfect sense to optimize for those. Even if it's a large string this still performs a memo allocation less than 50% of the time, right? It's only going to perform an allocation if the new string is longer than the capacity of the current string, which means that if you call this in a loop it's going to allocate a few times, and then, stop allocating. Let me say that again. If you call this in a loop, you call this repeatedly, it's going to allocate a few times, and then, stop allocating ever unless you happen to hit a really long string one, and then, you'll get that capacity, get one more outlier. What if I want to optimize this for rvalues? Because here I'm not optimizing for rvalues, so that's okay. We had advice on the advanced guidance slide just overload on string refref, on the rvalue reference to a string, then, move inside the body of the function. By the way, notice I also added noexcept because it's important to think about exceptional safety. The first function that takes a const string ref it can't be noexcept, why not? [crosstalk 01:10:04] It's good copy. It might actually do memory allocation. It will do fewer of them and not many over time, but it might do some it could throw, but the second one ain't going to throw. Just taking ownership of the buffer already owned and I'm going to delete any buffer I might have already had, but that's okay because that's no throw. This is actually nice I couldn't optimize for rvalues so write this by default and if you aren’t optimized for rvalues, if you have performance data that you should then add the overload for name. Now, a couple of notes. When you pass a name to object it's one copy assignment as before, but now if you passed a temporary it's one move assignment, which is roughly … I actually tested and benchmarked the three common STL implementations and the move assignment is about the same as copying for five ints. It's pretty much dirt cheap. There's no allocation, therefore, we can make it noexcept. Now, I will throw, there is one downside to this that actually in practice almost never happens, but there's one case where it does. I'm going to come back to this, but I do want to be truthful with you and point out the downside. If you do this, overloading on const ref and rvalue reference if you have more than one such a parameter and the N+ copy parameter, is combinatorial. It's like, "Oh, you mean if I have like three I need to write eight function?" Yeah. Yeah. Have a nice day. We don't like that. Now, the good news is that almost never happens except in constructors. Hold on for that. We're constructing an object that has saved many pieces of initial state, but in most set like functions, we're usually sending only one value. This is perfectly good advice, standard library does it all the time. This is pushback. You're looking at pushback basically and others like it. Now, let's talk about that option that seemed like the shiny object and that actually did seem maybe like a good idea, maybe something that we should explore as a new guideline, pass by value. Now, let's think about what happens here. If I pass my value, first of all, absolutely, this is simpler code because I get a single function. I completely agree this is simpler code than option two, okay, and right for simplicity first. I agree with that. I don't think it's simpler than option one, therefore, you should write option one, but let's analyze option three. I passed string by value, I move from it inside, and if I pass a named object I get a copy construction of the string, which means I'm going to unconditionally allocate if that is a long string if it is longer than the short string optimization, and then, move a sign. That means I can never reuse the capacity already inside name. If I passed a temporary it's good because then I just have one move assignment, no allocation, it's noexcept. You notice that noexcept is not green. On the previous slide that noexcept it was green. Green is soft and cuddly. Reminds you of freshly cut lawn in the summertime, butterflies dancing nearby, but this noexcept it's kind of dark in the alley where this noexcept lives. This noexcept is problematic. I'll overstate it, but I'll overstate it just only slightly. This noexcept is a lie. It is technically true, your code will compile, it will never fire. It is actually technically following the rules of noexcept, and I view this as nevertheless a lie. Think about what it's saying. It is saying that that function never throws. Now on what basis can it say that? Well, in the body, nothing can throw. That's perfectly true. Why? Because we have pushed the operation that might throw into the caller. If he has an lvalue and he calls this function a copy will be performed. That copy could perform an allocation. That allocation could fail. That could throw, but by saying no throw, we're saying, "Yeah. Yeah." But that's before you actually got to us. It's your problem. Pity the caller, if we, I want to points out that if we tell people to do this a lot, I am certain you will see people who have, look, "How so hard C++ is. Here's a puzzler for you. I write a program that calls only noexcept function and throws. Hahaha." Don't be that guy or at least understand what that noexcept means. If you're going to do this, and there's one case where I'm going to suggest you do this, I strongly suggest not writing the noexcept even though you legally could, and it would never actually fire because it's alive for the reason we just gave. This is at least problematically. I'll call it mendacity. Finally … Oh, hold on. We're all adults in the room, but this is being recorded and we're going to have an online audience so those of you who have parents, children in the room you might want to get them out of the room. We're going to have some graphic imagery here now. Give you a moment. Okay, those of you in the room you may want to cover your eyes. Are you ready? This could be disturbing to some viewers. Actually, oh, that's cool. I actually heard literal gasp. It if you want to write a perfect forwarder, you can write mostly just the greenish parts, but you still need to template class stirring it, but you could probably ignore the enable if is same decay T and the noexcept but you shouldn't. If you're actually going to write a perfect forwarder, write it reasonably perfectly. Like, write it right. If you were going to write a perfect forwarder then I submit you should be among that small dot of developers in the world that you could barely see on the first slide earlier on because it was only a couple of pixels, compared to the millions of C++ developers, but let's talk about it. It's optimized to steal from rvalues and much more. It's entire, it's for perfectly forwarding whatever you pass it through, you have to make it a template so it can take anything, but then constrain it to strings. We're actually going to name the template parameter string then constrain it, and if you pass a named object, there's one copy assignment as before, which is optimal. Pass a temporary one move assignment, noexcept, it's optimal so we actually expressed the noexcept with the noexcept std is no throw assignable, std string ref to string called cone value. The quote unteachable comes from Bjarne Stroustrup. I agree this is not teachable. This is an option for advanced developers that if you are a very advanced developer, sure, know about this, and if you might see it in your standard library implementation I hope will not see this in much production code because it's for very low level of uses. Notice that some drawbacks, it generates lots of functions because it stamps out the template for everything you might call. It must be in a header, can't be virtual. I don’t usually hear people mention this stuff when they like the … "Look, perfect forwarding. Ooh." I don't normally hear them say, "Oh, but it can't be virtual, has to be in the header." Generates lots of function. I just don't hear that. I'm actually less worried about the generates lots of functions. I'm just pointing it out because it does stamp out, it's a template, but most people over obsess about template stamping out functions. I never actually hear bug reports about that, about code bloat. That seems to be more of an urban myth than a reality that that's an issue, but the fact that the code has to be exposed in the header is an issue. That it can't be virtual is an issue. Now, I already answered this question, but let me, but let me lead up to it with a couple of questions. How many here believe that they could write this code confidently? Okay. There's still some hands up. Quick somebody, who's got his hand up shoutout, does this accept a string literal or not? Male: Does not. Herb Sutter: Does not. Male: [inaudible 01:18:11] Herb Sutter: I've constrained it wrong. This is Howard's code. [crosstalk 01:18:17] I would love to hear why it's constrained wrong because this is Howard's code and if he can't write it, we can't either. By the way, I will mention one thing about this code, I actually asked Bjarne in person. What about the way you think about this? In the same breath that he said, "This is unteachable." He said that in terms of writing this with confidence, he says, "There are very few people in the world who correct this with confidence and he didn't think he was one of them." He could look it up. He's certainly capable of understanding it, but just write it with confidence without thinking, without looking something up. Now, let's talk about performance because the reason we're reaching for this toughest performance, right? Let's measure. Here are options one, two, three, and four passing a const string ref, that's option one. The default that I'm saying you should always do is the first bar. The optimization for rvalues is the second bar. Then, option three, which is that new thing about pass by value and just have the one function that's option three, and then, perfect forwarding is option four. Here is benchmark code for that employee set name function called in a loop so it absolutely is exercising the case where it's reassigned in the variable a number of times so every hundred times through the loop it makes a new employee object, but we are reassigning 99 out of 100 times. Do you notice any performance glitch in this? Notice options one and two are fine for lvalues and four for the char stars, but if you go and use option three, as long as you're in the first case, which is small lvalue so you're having, you're passing a short named string, 1 to 10 characters to make sure you fit in the single, the small string optimization. You're good. In the second case, we're passing larger strings varying from size 1 to 50 because most of those will have to be allocated on the heap somehow. That is the case where you see that spike and only for option three. This is the problem we were talking about where if you just pass by value, you must allocate on every path for a long string and the same is true of a vector, anything else that could reuse capacity, whereas in the other cases we could reuse the capacity. Of course, allocating from a char star and having to do now an allocation costs you more, but notice everything's pretty cheap except here is a big pessimization and actual data for if you use option three, here's a case where it's a pessimization. It's not always going to be, but if you're not sure measure. I did. You might notice that the top of the slide says visual C++. This is probably a Microsoft problem. It's probably STL's fault, right? Actually, it's not. We're going to optimize string some more, but this spike is not STL's fault because Howard also graciously ran this on his own box in clang and lives to the SQL+. Yes, I changed the slide. Let me go back so you can see I change the slide. Same problem with Clang and libc++. STL being no worse than Howard is a really, really high compliment plus this is unavoidable because you must allocate memory for the long strings for that case. Now, how many of you use GCC? I have good news for you, kind of. Here's libc++. Is this good? Warning trick question. [crosstalk 01:21:57] Because it still does the copy-on-write optimization, which is no longer conforming and has other problems that would show up in different benchmarks just not this one, but notice there's no spike like compare, "Oh, look, Clang, GCC, better. Aw." Well, yeah, except it's non-conforming, but that aside and the fact that the other things are slower. I'm just making a little fun. The GCC folks are great. They've known about this problem for a long time. In fact, in the box, they already ship what's going to be the new basic string as soon as they can take an [inaudible 01:22:34] breaking change. They've been deferring it and deferring it, but in the box since something like GCC 4.1 or something like that. I forget exactly, there's something called vstring. How many of you by the way use vstring? Yeah, think about it. It's pretty good. I see one or two hands. Are you ready for vstring from that same current build of GCC? Oh, look, there's our friend again. This is a pessimization that most of the people who were, had a fling with passing the string by value advice didn't take into account and in fairness that wasn't really well understood, and one of the reasons when I mentioned to Howard that I was giving this talk and I wanted to give this advice, he actually thanked me for drawing attention to it because very few people he said seem to know about this. It seemed to be worth sharing some numbers with you here. For vector and large strings, keep in mind that the cost of the special member function is not necessarily what you expect. For many classes likes a shared pointer, which is more expensive to copy construction or a move construction? Copy construction, right? Because you're at an increment, decrement, the reference count, which you don't have to do it with move and, but assignment as well. Many classes have copy and assignment and move have different costs. Some classes they cost more, some classes they cost less. Remember, when you copy, you also have to get rid of the previous state, when you move you don't. Here in this particular case, for vectors and large strings, the cheapest thing you can do, in general, is to move a sign from it. Move assignment is actually cheaper than move construction especially in the cases where you get to reuse the buffer. This is not the case say for shared pointer where being able to, construction costs more than, or assignment costs more than construction because you have to get rid of the old state. Many classes these are reversed, but for vectors and large strings it turns out that the assignment is actually cheaper than the construction, and so, by taking my value we incur that copy construction followed by move assignment. Well, that's the most expensive operation in many cases for vectors and large strings. These numbers don't generalize to all types measure, but it's worth knowing about. Thank you, Howard, for pointing out the construction and assignment do not always have the same costs. Here's a nice quote from him that you can read, and this comes back also to Occam's razor, don't multiply entities. He was talking about logical entities, but it applies also to objects, needlessly, and Scott and Andre have long said, "Hey, don't ask for work you don't need." In particular, Scott is recently, rightly, teaching a lot that it's a bad habit. It just create lots of extra objects. We want to avoid that. Remember this is about default. It's not about not thinking, but it's not overthinking. It's actually not hard right for clarity and correctness first. Now, there is one place I said where you might want to pass by value, one case. That's constructors, and it's for two reasons. Look at class employee, it might have a name and an address in the city and when I construct I have to pass all of those things. That's the case that's bad for the overload const ref and refref because it's combinatorial, but it still avoids the case that's bad for option three because I am constructing those strings, which means there's no capacity that I'm missing reusing. I'm constructing them, not assigning it to them. I don't care about not reusing capacity because this avoids the pessimization in option three in other cases, and it is the case, the one case where you typically do have multiple value parameter setting for constructors do consider option three, but, again, as an optimization. Normally, const ref is fine, but if you do want to optimize for rvalues in that one case, consider this optimization. This is fairly new knowledge. By default, pass const string ref. To optimize at an overload that's noexcept often for rvalue reference. That's general default advice. One more thing, when you have, when you see refref and the type in front of it is a template, what do we call that? Read the slide. What do we call that today? I'm hearing people say universal reference and that is a term that Scott Myers has rightly popularized. He has rightly popularized that we need a name for this and nobody else has stepped up to give it a name. I am going to suggest that we call it a forwarding reference. One of the nice advantages of being here at cppcon is that we've had a chance to have discussions about this. Let me ramp you up on some discussions that have happened here this week. Again, just to motivate this in case you're wondering about the difference, let's say I have class types foo and bar. One is templated and one isn't. Those are very, very different refrefs. In the first case, it takes an rvalue reference to non-const. It only takes rvalues and the reason it's there is to capture temporaries. In the second case, it takes a mumble reference. Okay. They say what we've been calling a universal reference, but I'm now recommending a forwarding reference. It takes it to everything. Notice I didn't say to anything, I said to everything because it will stamp out the right thing. It will take const. It will be volatile. It will be both. It will be neither, it accepts all y objects, lvalues, rvalues, anything you give it, and the reason that it exists is not to optimize anything. It's a very different reason, it's too forward. The reason that it exists is to take whatever you give it, that's why it's so flexible, and just pass it on. I don't care if it's const of all because I'm not going to use it myself. I'm going to pass it on to somebody who is going to use it and he will know or care, whether it's const and so forth. [crosstalk 01:29:05] Forwarding references is … Well, what I'm going to suggest should be the new name for this. Several of us have talked about it this week, Scott has rightly pointed out that this t refref is different from other other refref parameters. It is not an rvalue reference. We do need a name for it. He coined that name and he uses it extensively in his book whose final galleys are due like in like three hours, which is a little inconvenient because you're not supposed to make big changes and you just can't make big changes in the book as it's going to press [inaudible 01:29:41] just can't slip. We talked about it at cppcon because we were all here, it was a great opportunity, and this is one of the big benefits just being able to talk with each other. Scott and I talked, Bjarne, Gaby, others, STL, and we haven't talked to everybody yet so this still has to be vetted by the community and the committee as to whether people agree that this is a good term. But we all agree that the word forwarding reference is better and avoids, and that universal reference, the concern is that it makes people think it's universal, I should use it all the time. It doesn't describe what it's for. Forwarding we believe is exactly the right name because it says exactly what it's for, what you do with it as well as giving it a unique name. We think the right name is forwarding reference, in the meantime, Scott is going to change his book as it goes to galleys. Thank you very much, Scott, for doing this. That is a big imposition and he's being very gracious so that he's not changing it universally, but he's adding a footnote and an index referencing by the way it looks like forwarding reference is a better term that's going to get traction in the community if and when that actually does happen, which I believe it will. I know several people who are going to keep using that from now on. Then, you will find new printings of effective modern C++ will switch to that term instead. FYI, this is, we want to thank Scott very much for popularizing that, hey, putting a shining light on this needs to be taught differently, this works differently, and that has prompted us to say, "Okay, let's try to come up with a good name. The one that we can live with for the next 20 years." Use refref only for parameter return types, rvalue references to optimize rvalues like we said or t refref, there's that term forwarding references. One dessert slide. Did you know that C++ now has multiple return values? Just return a tuple or a tuple if you're Scandinavian. I don't know what that means. That just came out. Sweet realization. We're already doing it. If you in C++98 had a set of string and you insert into it, what does it give you back? It gives you back a pair of an iterator and bool. You would say if result dot second so if it succeeded then do something with result dot first, right? That's just the convention that we used. Now, here's a really cool thing. That code exists in the wild, it's been there for 20 years, right? Ready? Watch this. C++11 gives you auto. It immediately works nicely with this. You don't have to type that big long thing, but wait it gets better. Just hold on one second, but we have this nice backward compatibility with it because we could just accept that return value and say dot first and dot second as before, but in C++11 we also have a new feature, which works well with that old feature. We have tie for tuple and pair. Anything that a tuple can do has generally been added to pair as well as the case of a tuple of length two. They're pretty much consistent. The library group did a great job of that, which means that I can call that old insert function unset, unchanged from C++98 and tie two variables, an iterator, and a bool and it works great. I don't have to say first and second anymore, I tie. Iterator and success equals function call, multiple return values. If success do something with either. The only thing I can't do is declare a variable inside tie and people are writing a proposal for that because that the next thing they want. [crosstalk 01:33:07] That's pretty cool. It works with the existing STL and with your existing code if you have multiple return values express them with a tuple. Remember it's hard to remember you're an expert. Can I just have a show of hands? Like how many of you are students here? We have a number of students here. Actually, can you please stand up? Stand up if you're a student, please, and please stay standing. Yeah. We're glad to have you here. Now, please, please stay standing and anybody who has been using C++ for five years or less, please join them. Please, also stand up. Five years or less using C++. Please, stand up and stay standing. Thank you. Now, if we can get Chandler up here, while you're standing, is it okay if I take a selfie with you, guys? Okay. Chandler where are you? Okay. I'm going to come down there. please, stay standing and I want to get a picture of you guys because this shows we need to keep everyone in mind when we're teaching C++ and welcome all the people who are joining our community. Is this a good place to stand? Male: Where did the [inaudible 01:34:26] go? [crosstalk 01:34:29] Herb Sutter: I'll go where he tells me. This isn't the first time. Thank you very much. Thank you, Chandler. Thank you for accommodating, and welcome all of you new folks to C++. I've decided I want photographic evidence to keep showing people on the committee and other places all the people who are new. We welcome you to C++ as a fresh new language, simpler than ever. Sometimes we are addicted to too much complexity, but there are very simple reasonable defaults simpler than ever for loops, pointers, and references. Smart pointers, never write delete again. Variable declarations, parameter passing, if you have a couple of questions please line up at the mics. We'll take one or two, and then, we'll break for lunch. Then, come back here for two o'clock and we'll have our final panel as well. Here, please. Male: Yeah. In the spirit of simplicity, which I absolutely agree with, are we looking to simplify the algorithms in STL so I don't have to put begin and end everywhere? Herb Sutter: Yes. We are looking at simplifying algorithms, range based algorithms will be wonderful. There's some real proposals coming. Male: I was wondering if you could go back to the benchmark, any of the benchmarks. Well, could you like … Herb Sutter: It's all the way back here. Oh, you know what? I could do this this way. How about GCC? They're good. Male: It's fine. Okay. In the last example in the right, there is something that confuses me basically option four is much faster than option two, which shouldn't [inaudible 01:36:23] like match option two. Herb Sutter: These were the results as measured on that system. Male: I mean it isn't like … Herb Sutter: You can download that code and try the test, run it yourself. I showed you pretty much the code. Check the assembler. I did not inspect the assembler as to why on the right-hand side they were different. This is what I measured. In fact, if you go to the others you'll find the same thing. The perfect forwarding is faster even for characters stars than the others. Male: Yeah, but it makes no sense to me because we know [crosstalk 01:36:55] … Herb Sutter: Goat and Stefan is waving and saying talk to him and he will enlighten you. Male: I have one last question and … Herb Sutter: We're going to switch over to here. Thank you very much. Next. Male: I want to say forwarding a char star two operator equals should actually be fast I think. My question was about at the end, you do this in there std tie and earlier one of your first points was if I say auto x equals something that I no longer have uninitialized variables, but today if I want to use std tie for that purpose, don't I have to have uninitialized variables? Herb Sutter: Well, you don't. If you use std tie … No. No. No. You can't use tie as a reason to have uninitialized variables. Yes, you have to have separately initialized variables. You have to declare them somewhere else. That's why I said the next things that some people want to have for tie is to be able to declare variables in there, right in the tie for the reason you just said so you still have to … Yeah, wouldn't that be great, but you still have … C++ is evolving, man. We're going faster and, but you still put them in the local scope. Before you still have to declare them, but if you don't want to initialize them that's not what I'm telling you. You still use auto x equals whatever and even if it's zero. Male: Okay. Herb Sutter: One more, and then, let's go to lunch. Male: I love that you started with all this avoid complexity and all that and that you started with the quote from I think Bjarne of we have a tendency to use new things just because they're new. I am not sold on your arguments for auto. Your second argument for auto about allowing your code to be more maintainable, I would really like to have examples of a large meaning, not large but meaning full-sized code … trick of changing types of return function or return … return types and the like … Herb Sutter: Let me ask you a question. Male: … without having to do any work. Herb Sutter: Let me ask you question. How many here have ever in their code bases changed a std map, the tree based one to a std unordered map? There's some data for you. Male: Yeah, but you have to [crosstalk 01:39:01] … Herb Sutter: The iterator type changes. Male: Yes. Clearly, but you're going to have to test this while you're doing it. Herb Sutter: Oh, yeah. Sure. The question is not whether you still have to test it, the question is, A, is there a code ripple that you have to do it manually or will the compiler do it for you? Second, the correctness of if you get a type where actually the compiler won't tell you that you need to change it, but now the types don't match, and those that existed I found them in real-world code bases that's why I mentioned the examples. You, however, sir, I know who the … are in a special situation because you own an internal code base that is monolithic, has great tools for it, where you can simply turn a switch and do these things for people. Those of us who don't have those tools would like our compilers to help us with auto. Male: I'm going to have to see like war story, use cases of pulling this off without there being wild unintended consequences. Herb Sutter: All right. Thank you for that question. As we go to lunch, let me ask this, any of you who are listening to [inaudible 01:40:05] and thinking of, know war story, please, tell him, and I will ask him after lunch … Oh, there's … It looks like there's going to be a line, but I would be curious the answer myself after lunch, and let's get back together. Male: Absolutely. Herb Sutter: Thank you for coming. Enjoy lunch. See you back here before two o'clock. Thank you.
Info
Channel: CppCon
Views: 257,155
Rating: 4.9059701 out of 5
Keywords: Herb Sutter, CppCon 2014, Back to the Basics! Essentials of Modern C++ Style, Computer Science (Field of Study), Bash Films, Conference Video Recording, Event Video Recording, Video Conferencing, video conferencing equipment, videoconference, video services
Id: xnqTKD8uD64
Channel Id: undefined
Length: 100min 26sec (6026 seconds)
Published: Mon Sep 29 2014
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.