CppCon 2018: Jason Turner “Surprises in Object Lifetime”

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
- This is Surprises in Object Lifetime. And there's still people straggling in, I know that'll happen for the next few minutes. Now, um, Andrei Alexandrescu is in this room and he was just telling me that his goal is to be loud enough to get our room to complain about the noise. And then I was told that we should make it our goal to get Andrei's room to complain about the noise. But, if you ever been to my talks, I'm not great at giving jokes. When I do, they tend to fall flat. But maybe we can come up with something to cheer at some point. Maybe if we hear them getting loud, we can figure it out. All right. So, my name is Jason Turner. This is my intro slide, if you'd been to my talks, you've probably seen something like this before. Couple of things I'll just call out. I am host of C++ weekly, which is my YouTube channel, and co-host of CppCast, which is a podcast. I will mention, C++ weekly on Friday more. I'll ask that real quick here. Who watches my YouTube channel? Oh, it's pretty good, cool. Okay, um. Yes, I am independent and available for training or contracting if you're interested in having me come as your trainer to your company that, you can usually be worked out. Yes, move to the front. There are people in the back, if you know who I am and have seen my talks before, they are highly interactive. Gasper's getting in the front, good job. We will be doing things in here hopefully that are fun. And also this is approximately what training looks like, so you have an idea of what to expect if you do hire me to come to your company. So, I have a couple of upcoming things I want to mention. This is good as people are straggling in still. CppCon post conference training, it is not too late to sign up for that if you're interested. It's two days, Saturday and Sunday, right here. Well not right here, it's upstairs. I will be giving a one day training at C++ on C, if you're not familiar with it. That's conference that's gonna be in Southern England for the first time in February in 2019. Now, this is a hypothetical. We are working on the concept for this of a three day training thing that I'm considering doing with Matt Godbolt and Charley Bay in the Denver area in the summer. That should be interesting because we have very different perspectives on things. So, you can hear us argue about things if you'd like to. Okay, so, about Surprises In Object Lifetime, I have taught this one day course called, Understanding Object Lifetime about 12 times now. Well-defined object lifetime, and our construction, destruction cycle in C++ is one of the key features of C++. We want to be able to well reason about the lifetime of things. And we're one of just the few languages where we can reason about the lifetime of objects. Sometimes there are surprises however. Sometimes they are for the better, sometimes for the worse. So, let's start with what is an object? This is a key definition if we're gonna talk about object lifetime. And I am cheating the lights at the moment, so I'm off-center. They're not blinding me as much and I can see you some more. Okay, how is this thing, this struct that contains an int, different from an int? Yell stuff out. This is how it works in my talks. It has a label? Somebody said. So there's some sort of type. - [Man] It has a different name. - It has a different name. That's, that's, that's okay, yes. Gasper says it has a different name. So, if we were to look at them from this perspective, they have the same size, using uniform initialization syntax. They can be initialized the same way. We can refer to them both as a reference to an integer, I say don't do this in real code on line six. Please don't do this in real code, but it is technically legal. We can reinterpret_cast a struct is the first element of it. We can assign them the same way and access them the same way. They look pretty similar. So, if we look at some of our type traits, they are both constructable, trivially destructible, trivially copyable, and more to the point, they are both objects. So, what is an object? The standard says, an object type is a possibly const volatile qualified type that is not a function, not a reference, and not void. Pretty much everything is an object. Okay, so, Object Lifetime. Lifetime begins when storage with the proper alignment and size for a type is obtained, and if the object has non-vacuous initialization, its initialization is complete. At some point, I have this slide in a couple of different talks. At some point I should remove the bit about union, because we don't care about that in this exact moment. Although adding union to our surprises could have been an interesting thing to do, I did not do it in this talk. So, if it has non-vacuous initialization, once the initialization is complete. It's lifetime ends when if it is a class with a non-trivial destructor, the destructor call starts, or the storage in which the object as occupied is released or reused, okay? Any questions? We're gonna do examples and interaction and stuff in a minute, yeah? - [Man] Do you have a definition for non-vacuous constructor? - Let's say non-vacuous initialization, let's say non-trivial initialization. Yes? - [Man] Why do they not use that more familiar defined term? - Why did they not use the more familiar term? I honestly do not know. And if there's someone who does know why vacuous is used here? I don't know. But that's a great question. Okay, So, I have my simple utility here, that I've got the struct S, it has something that prints in each of the special member functions, and then I've got main, I am creating an object of type S on line 12, I am creating a scope, I'm creating an another object called s2 on line 15. Alright, what does this code print if I were to execute it? Or probably no one's gonna yell it out. I will say, what is printed on line 12? (audience mumbling) Line three, S. So, it's S with the parens, okay. What is printed on line 14? - [Man] Nothing. - Nothing, nothing, yes. Not everything prints on main. On line 15, what is printed? S, const S ref, so the copy constructor is called on line 15. Now, I have this kinda annotation I personally use, you'll catch on in a second here. What's printed on line 16? (audience mumbling) The destructor. Yes, the destructor of s2 is called on line 16, then was how I just called on line 17. (audience mumbling) The destructor of S. So, we end up with this. Now, just for the record, if we needed to at any moment in our talk we can flip over the Compiler Explorer and actually test these things. So, if we have any questions at all, stop me, we can prove though that this what is prints. S, then the copy-constructor, destructor, destructor. Good? Okay. Okay, now what is printed? What's printed on line 12? Constructor of S. Okay, we already said line 14 doesn't print anything, that's no change there. What's printed in line 15? (audience mumbling) Nothing, nothing is printed. This is perfectly safe. I haven't done any kind of implementation defined, undefined thing, or nothing, nothing is printed. What's that? (audience mumbling) It's a reference. It's a reference, there's no copy to be made. Then, line 16, nothing. Line 17, (audience mumbling) destructor, okay. So, that's what it looks like. So, remember that reference types are not object types. They don't, we don't talk about, they don't affect our lifetime, mostly. We will show something where they do. Okay, alright, now we're gonna get into this. So, what is printed in this code? I have this function called get_data, it is returning this constant ref and then I'm printing it on line 9. What'd you say, 42? (audience mumbling) That's as good a guess as any. Most likely five, okay. (audience mumbling) Alright, so, we are returning a reference to local. It's garbage, this is unknown, this is undefined behavior. What warning do we get from the compiler? (audience mumbling) Returning a reference to local, sorry, it's, for some reason it's easier for me to focus over here. If I am ignoring you, someone start doing jumping jacks or something, burpies, I don't know, something that I'll notice, and plus it's good because you're sitting all week, basically anyhow. Alright, so, yes, we might get a reference to stack memory return, reference to local return, something like that. Are we surprised yet? The title of this talk is Surprises in Object Lifetime, hopefully, we get to some surprises in a minute. Okay, now, what is printed? Now I'm returning a reference_wrapper not a reference. It is still unknown, yes. We are still returning a reference to a local object. So, this reference_wrapper is implicitly taking a reference to i, the local value, and returning it. The question is, now what warning do we get from the compiler? (audience mumbling) Probably nothing, unless it's magic. (audience mumbling) What's that, I'm sorry. (audience mumbling) Oh, oh, you're saying with like the stuff that Herb is working on right now, yes, no. I'm just talking about gcc and clang at the moment. Okay, um, so, I'm compiling this list here, my default's clang, I've got w_all, w everything, w_shadow, and Pitantic. No warnings are printed, we can see that in the lower right hand corner. Gcc makes this fun, because if I change it in gcc. Sorry, wrong gcc. We get this warning. Warning "i" is used uninitialized in this function. (audience mumbling) Is i initialized in this function? I'm pretty sure I is initialized. This has become like a rule for me now, if I see a warning that makes literally no sense at all, I've decided I'm probably invoking some type of undefined behavior. Yeah? (audience mumbling) Um, the only thing, so the question was, you said, it's talking not about it's initialization but it's actually use inside of main. Yes, but the only place that we as humans see an i is in that function. And it has been initialized there. That is why I take issue with this. What's that? Would it go with, I used no end line. I don't know. I can turn off all optimizations. No longer get the warning, yeah. So, the compiler doesn't have enough information to see that here, yes. Okay, alright. So, simple standard library wrappers around references confuse our analysis. That's our first surprise! Opps, sorry. Let's talk about strings. What is printed from main? (audience mumbling) Hello World, does anyone question that Hello World is printed? (audience mumbling) What's that? Global data, okay. Hello World is printed, why is it allowed? The standards specifically says evaluating a string literal results in string literal object with static storage duration, it's effectively a global, as you said. Initialized from the characters specified. Static objects are valid for the entirety of the program. This is fine, this is totally allowed. Okay, we're now returning a string view. What is printed? Still Hello World. Okay, because string view is basically a pair of pointers, to the beginning and end of the string. And we're not surprised right? Alright, now what is printed for main? (audience mumbling) 42, unknown. What's that? It might not get to the point of printing. But, most likely, the answer is Hello World says someone in, who shall remain nameless, in the front row. (audience mumbling) So, with our magic of Compiler Explorer here, we can execute this. Um, Yeah, that's, that's printing nothing, it says that returned zero, it says it successfully executed, but it didn't print any data. And we can (audience mumbling) that is gcc, turn off the optimizer, it doesn't, it doesn't change anything really. Um, Yeah, something like that, that's an interesting unicode character that it decided to pick. Okay, so this is unknown, why? I think most of you already got this. Since the string view is pointing to the beginning and end of the string, the string object that actually stored this data was popped from the stack and our local string has gone out of scope. We already saw it in the Compiler Explorer, what warnings did we get from this code? Not a single thing. Okay. What does this code print? I'm getting confident Hello World answers. (audience mumbling) I'm getting a few undefined. Why, who wants to describe why undefined? Someone, somebody, you wanna go with why, okay, why? (audience mumbling) Yeah, you raised you hand, right? (audience mumbling) And it's a, yes. (audience mumbling) Right so it's not very much of a change from stood string because this is a local array called S that is initialized with the global data that character literal Hello World and then we are returning a string view into that. So, the local array decays to a pointer, which initializes string view and we get no warnings. That's fun. Any questions? So, surprise! This is how I phrased it, strings live longer than you think they will, except when they don't. I have actually, I will take in aside right here, many of you raised you hands saying you watch my YouTube channel. I did say, explicitly in a recent episode that this is the one feature I'd remove to C++. Remove from C++. Implicit conversion from array to a pointer has literally no use in the language what so ever. We could require a static_cast when it is necessary. You might say backwards compatibility is seed, but you can throw the static_cast in there if you really need to. So, that's the feature I'd remove. Similar idea here, we are now initializing a vector and we want to ask what is printed here, when we push back an object of type S, with this constructor? So, you're saying, you're saying the S int constructor and then the copy constructor (audience mumbling) and then destructor (audience mumbling) Okay, how many people say the copy constructor is called? You have to have more confidence than this, raise your hands higher. Okay, who says the move constructor is called? Okay. Alright, so, we'll the votes win, so move constructor is what it's called. And that is because there is an overload for push_back for the move constructor. How many destructors do we see? Okay, who says, how many say one? How many say two? How many say three? Okay, I think two won also. So, we'll go with two. So, yes, we have the S int constructor, the move constructor, destructor, and destructor. So we remember that for non-trivial type, the destructor of the moved-from object must still be called, is the takeaway here. Surprise! Moved-from objects still have to be destroyed. So, this is a surprise for maybe a quarter of you. Yes, and it's often non-trivial often inlining the destructor causes code bloat because it has to decide, for example, with pointer, if there is some resources that need to, excuse me, for example, if stood string, it must decide if there are some resources that need to be freed or whatever. Okay, I have switched this is emplace_back. (audience mumbling) You say it's the same. (audience mumbling) Yes, it is exactly the same. For exactly the same reasons you said. Same thing as push_back, we have been used emplace_back improperly here. The point emplace_back is that we are trying to call the constructor of the object. And so, I will build on that. Now I'm using emplace_back this way. So, now what do we see? What's the first thing we got printed? (audience mumbling) S int and then what's the next thing printed? (audience mumbling) Constructor of S. That's it. This is the proper way to use emplace_back. Any questions? I just see a couple of faces that look confused, but, okay. If you're not going to ask questions, it's your own fault. So, this is the correct way to use emplace_back, if we're gonna use it, we're calling a constructor, is basically what we're doing. Worth pointing out, this line here, we can call it with zero periameters. So, we're calling the default constructor. So, even without a named object we have to think about object lifetime. Now, I do think that understanding object lifetime is probably the most important part of understanding how to write good clean C++ that is efficient. Alright, what is printed here? Yes, what is printed? What is printed on line 12? S (, alright. What's printed on line 13? Hello World. What's printed on line 14? Okay, does it bother anyone that this is a const reference on line 12? Because it is a reference to a temporary. Yeah. (audience mumbling) Oh wait, sorry. Yes, Leslie? No, if you don't have const it won't compile. You can not take a non-const reference to a temporary unless you are compiling in a version of MSVC that is in permissive mode. (audience mumbling) Specifically. Yes. So, S, Hello World, ~ S. So, this is our surprise here. Complex rules allow for lifetime extension of temporaries that are assigned to references. If you want to look this up in the standards, it's in class.temporary. It is a lot of confusing things to read. Yes. Oh, uh, (audience mumbling) we will. Okay, so the question was would there might actually be two different S objects, in this case. I will say, hypothetically, before C++17, yes, but we'll dig into this in a little bit. (audience mumbling) Yes, yes. Actually, no wait, I have to get back to that. Do our value references also expand a lifetime. No, but universal ones do, says Gasper. Or forwarding references if you will. (laughs) Okay, so for the sake of this slide, since someone asked and I had not actually spent time to think about it. Let's run through this real quick. The question was might there actually be more than one S object created. And I will say, by looking at it more closely, definitely no. Because we are value initializing, excuse me, we are initializing on line eight, the return value S. Directly initializing the return value. We're not returning any local object at all. And then we have a const reference to the returned thing on line 12. Under no argument, can you say that there is more than one object S here, I believe, except, Ben's gonna disagree with me. (audience mumbling) Oh, it's unrelated to that. (audience mumbling) Okay, we will not worry about that right now. (audience laughing) That's, I repeated the question for the YouTube video. (audience laughing) Okay, so, complex rules object lifetime. Alright, I am now initializing a const int reference on line two with a temporary, effectively, one, on line six, and putting that in a const reference. What values are returned from main here? (audience mumbling) Okay, lets do it this way, who says one, the two questions, the two options are going to be one and undefined. Who says one? Okay, who says undefined? Ou, it's like 50/50. Okay, since there was no consensus, we have to go to the standard. It says one is returned from main, lifetime extension rules apply recursively to member initializers. I was researching the answer to the last slide when I came upon this example in the standard, and I thought, I have to put this in my talk also. (audience laughing) The question was what and the answer is go read class.temporary. I think it took me four times of reading it. You read the standard more often than I do, you probably get it faster, but, yes. Okay. (audience mumbling) I'm sorry, what that a question. (audience mumbling) Only for what kind of initializers? Is it only for aggregate initializers? Um, no, I don't think it has to be a member initializer, I think I could have actually done a constructor that took an initializer with a (mumbles) reference into this. I'm almost positive, yes. I don't think it has to be, yeah, aggregate. Alright, how many dynamic allocations are in this code? We got a vector of stood strings. Don't worry about the version of the standard, actually, any version of the standard that'll compile this. Okay, we have a couple of guesses being yelled out. How many people say one? How many people say two? Nobody says two, how many people say three? Okay, somebody who said one, make an argument. Who said one, raise your hands again. You. (audience mumbling) Small string optimization. This is not something guaranteed by the standard, but if you want to have a standard library that's able to compete, you have small string optimization. So, almost certainly one, we will have our a and b, we'll fall into small string optimization, we're doing one allocation for the vector. So, stood string is highly optimized, do not underestimate it. For real, if you saw the talk I gave at C++ Anal this year, you will see that stood string messed with a lot of my benchmarks. Alright, now how many dyncamic allocations do we have? (audience mumbling) (audience laughing) The comment was it's not long enough. Let's say with, how many people says there's one dynamic allocation still? No one. Two? Three? Four? One person half heartedly raised his hand for four. Okay, you want to change your vote? Okay, wait, how many says there's five? Exactly one, oh, oh oh, okay, alright, alright, I see who else is raising their hands in the back here, um, okay. You said five, as far as people who can actually interact with me, why five? (audience mumbling) Yes. Okay, so basically, initializer_list can't be moved from is what your effective answer was. We have an initializer, well, the answer is five. Why, I'll back it down. Because the compiler has done this for us, it has created a underlying array of string objects so those must be initialized, that is one and two, and then we must allocate the space for the vector, which is three, and then we must copy those strings into the vector, which is four and five. Like that. So, five allocations here. Who was surprised by that one? Everyone, except for those three people. Yes. (audience mumbling) why is it initializer_list, oh, oh, so, if you look at the this is where I'm making the cameraman's job harder, but, I'm sorry. If you look at the, the constructor that takes an initializer_list for vector, it is an initializer_list of string, therefore, the underlying array that is created for the initializer_list object, is itself, an array of string. That is what the standard requires it to be. That is why it is not an array of const (mumbles). We will have, I think, more satisfying examples for you in just a moment. (audience mumbling) Oh, an array of const strings, and not a, because it's not movable. And if you look at the the accessors on initializer_list it only has constant accessors anyhow. So, it's irrelevant really, We, a lot would have to change. And if I did, well, I did an hour and a half long rant at C++ (mumbles) about that. Called an initializer_list are broken. So, I'm not going to go into it right now, but, in much more. Um, (audience mumbling) Can it be optimized? Not in any way that I'm aware of. No, Hepalison with claying might be able to optimize it but I've never seen claying alide two Heap operations. I've only seen it alide one. Yes? (audience mumbling) Let's keep going. Oh, if you had to do it at runtime? (audience mumbling) Okay, if it were const data and you can't make the, well, do you know how many objects it is? IF you always know the number of string, you can do better. (audience mumbling) Yes? You might get only four. Uh, of the two strings? They both have, no, you can't merge the two of them, they have to be two diff-- (audience mumbling) I don't believe any compiler would do that, because each underlying string object has to have a distinct pointer to it's data that it can manage separately. (audience mumbling) And (mumbles) because, oh, okay, because of the as if rule it's hypothetically possible that some of the (mumbles) could be merged. Okay, (audience mumbling) yeah. (audience laughing) He says I have seen things. Okay, initializer_list and vocations create hidden const arrays. That's our surprise here. Um, how many dynamic allocations does this code have? (audience mumbling) Okay, how many, who says zero? Okay, who says one? Two? Three? Does anyone wanna go as high as five today? (audience laughing) Can I get six? I have to go into like bidding mode here. Okay. It's zero, it is zero. Because we're using C++17's (mumbles) type deduction, it's going to deduce this as a const char * of two elements, no allocations. So, yes, if you know what the strings are you can do perfect. Okay. Um, is this okay? Yes, okay. We know that our character literals are valid for the life of the program. Now how many dynamic allocations do we have? (laughs) One, who says one? Two? Uh, pretty good number of people say two. Three? Four? Hand full of people are saying four. Okay, you're saying four, alright. Yes, I overheard the answer already, it is two because this is not an initializer_list. What is the difference? Well it is, okay, it is an initializer space list if you're looking this up in the standard. It is not an object of type initializer_list. You have to keep those separate when we're talking about initializer_list. I don't have the vocabulary to keep them separate without saying all of those words. (audience mumbling) Oh, I'm missing a pair of curly braces, okay. Clang user. (audience laughing) You know there are situations with clang where it says extra braces suggested and you can put like 75 and it still giving you that warning. Its a little, (audience laughing) um, yes. So, this is why. Standard array has no constructor. We are directly initializing the data of the array. Now, interestingly, you notice this _M_elems, I don't know if it's lib C++ or lib stood C++ that literally uses this name. But, I did look it up. I was reading through the source of array. As one does. And, um, okay, so we, mere mortals are not allowed to create an identifier with this name. This is reserved for the standard, an _ followed by a capital letter. And therefore, we are also not allowed to rely on the name of this thing. It is undefined behavior if we access something with this name as well. So, they are, they, the people who implement the standard library, they're allowed to do this. We're not allowed to do this, they're allowed to do this and then they can punish us if we try to access this thing. They're allowed to. So, this is, anyhow, we're directly initializing the array. Yes, (mumbles). (audience mumbling) Yes. (audience mumbling) Yes. (audience mumbling) Is there a reason why it wasn't characters of array length? Um, if you have multiple, I, I don't know the exact wording, I don't know the exact place in the standard, but I've from my experience if it were one of these, it would be a character array, plausibly, with two of them it has to deduce the common type, which is gonna be char const star star. That's, I'm almost positive that's what's going on. Because you see the same kind of behavior if you've got, a turn array that's returning two different strings of the same length and that kinda thing too. Yes. (audience mumbling) - Oh, you're saying, ohh. Leslie said it would decay to a pointer because it's, template argument by value. I'm not, yeah. I think that's right, you actually have to specialize the pinpoint based on the array. Alright, anyhow, that's a side, okay. So this is literally the most efficient thing possible. This is my surprise, this is how I wanted to phrase this. Standard array has zero constructors for efficiency! Just let that sink in. If you've seen any of the talks, from Neiko, about like an initialization of trivial classes, and he goes through great length to explain how you need all of the different constructors to get things as efficient at possible. I'm not saying you go back to your job and start writing all of your classes with all of your members public and don't have any constructors. I'm just saying, this. (audience laughing) Alright. On to something different. Where ever my water went. What do we have? I think by now we have the rules. A new slide comes up, you tell me what happens. Okay, so we have get_s, which is create an object of type S. Which is then immediately calling an object of type get_data and then we're printing or excuse me calling the function called get_data, which is returning a vector events by const reference. So, what's printed? (audience mumbling) One. (audience mumbling) Undefined, unknown. Alright, we can break this down, it is unknown because if we have a range four loop, this is literally what the standard says that the compiler is doing. (audience mumbling) An auto ref ref-- (audience mumbling) Yes. Auto ref ref would extend the lifetime of the thing returned by get_s. Not the thing returned by get_data from get_s. It is not recursive object lifetime extension. It is unknown because we have a dangling reference to the object of type_s. Oh look, I even put dangling reference on the slide. It's like I knew what was coming up. What warnings do we get from the compiler? (audience mumbling) Sorry, I get ahead of myself. No, um, I'm pretty sure we're at, yeah, no warnings with clang. And no warnings with gcc. Those are the compilers I have at my disposal at the moment. Yes. (audience mumbling) Oh. That's (mumbles) doesn't warn, is that one of the ne-- (audience mumbling) Herb's new extension, okay. (audience mumbling) Well, it's interesting that it's not correctly diagnosing this and I will explain why on the next slide. C++20's for-init. This was added in C++20 specifically for this exact kind of case, which I find is interesting that the lifetime warnings aren't catching it then. But, it's probably, it's difficult. These are difficult problems to catch a static analysis time. At least I have to assume they're difficult because people aren't doing them and I'd never written a static analyzer. Okay. So, C++20 lets us do this. Now, just for the record if you'd try this in your pre C++20 compiler, you'll probably get some weird warning about how your missing the last third part of your four loop because now we're getting into a syntax that looks like as far as the number of semicolons required and stuff, it looks pretty much like it's a regular four loop. But we can see here that the compiler now generates code like this for us. It has a const auto s. We can see that line was just copied in from line 12 to underline 13. And then it is using it's auto ref ref on the range object. Any questions? (audience mumbling) Yeah, go ahead, sorry. (audience mumbling) That was recursive reference on a initialization not on return values. That is the difference as far as the standard is concerned. Wait, there was. Yes. (audience mumbling) Um, well if I had done auto ref s, I would have recreated the exact same problem. I would have created, oh no, it would be const auto. If I had done const auto ref, that would have been allowed here. But it would have been, exactly the same thing. We still would have had exactly one object of type_s created, it's just whether or not you use the const reference lifetime extension rules or you used return value optimization. Yes. (audience mumbling) Yes. No, no, wait, just for the record. I did not suggest having no constructors. (audience laughing) (audience mumbling) Yes, if I had not had a function called get_data here, it would have worked fine. That is correct. It actually took me awhile to carefully craft these slides to make my point. That's, that's like a little secret. People spend a lot of time trying to get the exact example that they want you to comment on. Alright. So, I ranged four loops, create these hidden variables for you that have their own questions of lifetime that we have to be aware of. Alright, time. I am moving too slowly. Okay. What warning might be get from this code? (audience mumbling) Shadow. Shadow warning. We can break it down, and ill start to move a little bit faster. X shadows a previous declaration of x. So, this is the code and we will expand it out one block at a time. So, this, auto const auto x equals get_val, it says if we had done this, we created a new scope. I know the first time I presented this people missed the scope that was created on line five. That is not the opening curly for main, that is a new explicit scope. So there's a new scope where that x is created. And then we break it down further, we've got our inner x, so that's the one that shadows. So our surprise here if-init statements are visible for the else blocks as well. I find teaching this that some people just don't expect that. It's often the first question people ask about C++17 if-init stuff. Okay. This is similar to these things. What is printed here? (audience mumbling) S ( ~ s. Now, what is printed? I've got (audience mumbling) s ( ~ s. Okay, who says same thing? Who says something more? Alright, everyone who voted said same thing. So, I like to point out, this is required in C++17 but every compiler I've gone back to 1995 does this. It's it's been a thing for a very long time. (audience mumbling) The mechanism changed depending on your ABI. (audience mumbling) Yes. As of the, what itanieum IBA if I'm being technically correct. (audience mumbling) Yes. Well, (audience mumbling) okay, well, you know what I'm almost out of time, Gasper's arguing that it's not an optimization because it's required. I would like to point out the mechanism because it actually works on most ABIs. Is the object, little s, on line 16, it's memory is allocated right here. A pointer to it is passed forward, into get_other_s, which is passed forward into get_s, which is initialized. That object never moved. From a compiler standpoint, there was nothing to optimize. It was always right there. Yes, Ben. (audience mumbling) Yes, prior to 17, you still had to have the copy instructor even though it was never going to call it. Yes, which is a little annoying. But, now, you don't have to. (audience mumbling) You're right, so yes. It's not longer an optimization, it's not under the (mumbles) rule. Alright. RVO is super awesome, or illision, whatever. Same, (audience mumbling) dag gummit. Make me change my slides on the fly because if I did this, what changes? (audience mumbling) Yes. The answer was now it is NRVO. Named returned value optimization, I believe I have that correct. It is, it has another letter. We are going to get the exact same output from the compiler. It's good at these things. So, it's easy and I don't like Copyalison, I don't like the frays, Copyalsion. I also don't like return value optimizations since everyone's done it for 30 years. We shall move on. Okay. Where'd my controller go? Alright. This is a little bit more complicated now. I've got this thing called a holder. I am constructing a holder with my function on line 15 called get_Holder. It is initializing the return value and then I'm calling dot_s. So, I'm initializing my local variable s on line 16, with this thing that was returned from a temporary. And then I'm returning that from the function and then on line 21 I'm initializing this. Alright, what's printed here? (audience mumbling) S (. Alright, what's the first thing that's printed? S (, let's get that out. (mumbles) what's the next thing that's printed? (audience mumbling) what's that? (audience mumbling) Move, did you say move constructor? Copy constructor. Okay. Who says copy constructor? Who says move constructor? Okay, no one says move constructor. It is move constructor. It is the move constructor because the thing returned from get_Holder is an R value, therefore, it is a perfect candidate for calling the move constructor. That's the simple way of wording this. So, on line 21, nothing is printed, because as we said, that object little s on line 21, is actually like it's memory was allocated here and it was passed forward, and the things. Okay, so we have an s, a move constructor, and a destructor and destructor. So, moves happen automatically with r-values. Don't try to help the compiler. I've changed this to a structured binding. Now what's printed? Someone says the same thing? Who says move constructor? Now? Oh, now no one wants to raise their hands for move constructor. Who says copy constructor? Okay, the rest of you have just given up. Okay, you're saying copy constructor. It is a copy constructor because the compiler says that this code was magically created for us. This object s, that we thought was a local object is actually a reference to the temporary that was returned by the get_Holder function. (audience mumbling) No, (audience mumbling) it is not a referenced, it is a it is, yes, an alias. It is, no sorry. Which one are we referring to? (audience mumbling) No, no that's definitely a reference. Line 17 is definitely a reference. That is, you can look up the wording of structured bindings and see these kinds of examples. (audience mumbling) I have had conservations with people involved in the standard that say we can't tear apart objects so, no, currently, the as if thing can't do things here. But, that is outside of my depth at the moment. Okay, so our structured bindings mess with our ability to get our moves. So, in this code, is the destructor called? We are creating an object on line 12. Do we have any reason to believe the destructor of s is not called? It is not trivially destructible. It has a destructor right there that puts. So, we expect to see ~ s printed? (audience mumbling) Okay. Now, I have put a throw statement in my constructor. Do we see the constructor called? (audience mumbling) Okay, so I'm getting yes, no, no, yes, yes, no, no, yes. Who says yes it is. I hope I didn't need that. I don't know what that is. Okay. Who says yes the destructor is called? Okay, who says no it's not called. The nos, the nos have more hands, so therefore it's the correct answer. (audience laughing) This constructor call did not complete. The object's lifetime has not begun because it's constructor has not completed. (audience mumbling) (mumbles) Shhh, (clearing throat) okay. I have added a call to the delegating, I've added a delegating constructor call. Is the destructor called now? (audience mumbling) okay, so I get some emphatic yes's from the front row. Yes, it has been called, why? (audience mumbling) Yes, effectively when the first destructor exits is when the object's lifetime has begun. So, when this default constructor on line seven is completed, the object's lifetime has been considered to be have begun, therefore, it's destructor will be called. Now, if you really want to have fun when you want to play with this later, have a bunch of random sub-objects that throw destructors at different points in construction and play with what gets printed and learn all kinds of things about how much work the compiler actually has to do to keep track of which objects properly had their lifetime begin so that it knows which destructors to call. And then, thank your compiler developer for getting it all correct, probably. (audience laughing) As far as I know, there's still an open bug for something about this in gcc that's like old and I forget the details though. Alright, so once the delegating constructor completes, the object lifetime has begun. So, this is from a, I should have had a link to it, a rather popular post by Howard Hnnant on stack overflow. And this is the kind of example he made here is that we can do interesting things to make sure that our sub-objects lifetimes are managed correctly. So, if one of them were to throw an exception. Like if the line eight were to throw an exception, we know that the object that was allocated on line seven would be properly cleaned up. This is valid defined behavior. However, I put it in really big letters. I hope you can read it. Don't do this at home, use unique_ptr. This is, this is, violates all kinds of like never call new, never call delete, don't use raw pointers. Don't manage the lifetime of more than one object. Yes, all kinds of best practices are violated here. So, an object lifetime has begun after any deconstructor is completed. This is my bold title conclusion slide. Anywhere where the specs say the compiler transformed the code for you, there might be a surprise. If you get bored and you're reading through the standard you will see some of these things. Avoiding these issues. We need to think about lifetime. We have our delegating constructor, it's just a waste of code, right. We can just return the first element if that's really what we wanted to do. So, don't name temporaries. This helps us think about object lifetime correctly. If we don't have a name, we don't have to worry about it, pretty much. This is a thought. I haven't tested this in real life yet. Consider requiring that all your structured bindings actually be by reference. Because this then communicates that you are actually dealing with references here. You're going to get the exact same results but now you have this note effectively in your code. I don't know. Who likes that idea? A hand full of people. Who does not like that idea? (audience mumbling) (audience laughing) I will not repeat that comment. (laughs) Using our tools. Warn all the things, thanks Ben. Wshadow catches some of these things. Clang-tidy core guideline checkers would catch many of these things. The good warnings and our guideline checkers check all, any implicit conversion from an array to a pointer is caught. I love that. Sanitizers, every single one of these examples is trivially caught by a sanitizer. Are you using sanitizers on your code? (audience mumbling) Yes? If you're not, when you leave this session go implement sanitizers in your continuous integration environment, which you're all using, right? Yeah, everyone has a CI of some sort? Alright. So, be careful with initializer_list. Effectively, it was really only intended for trivial literal types, keep that in mind. Oh, oh yeah, I can't skip this. Constexpr all the things. This is invalid d reference, we know this right? I have like two minutes left. So, bam! What does this code do? (audience mumbling) It fails to compile, why? (audience mumbling) Because it doesn't allow undefined behavior, it says it right there on the slide. Good job. Okay, taking this example, where we have this where C++20 is four initializer_list, four initializer is supposed to help save us here. This this example caught by a would be caught by a sanitizer. If we make the context perversion of it we're in the exact same place. Compiler will not compile it because it see undefined behavior. I can actually, execute it, or compile it real quick. We get a constexpr variable is not initialized by constexpr. And I love the warnings that you now get in a constexpr context that you can't get from basically any other tool. Read of object outside it's lifetime is not allowed. What! (audience mumbling) An a constant expression. (audience laughing) But it's not allowed in general. If you can build your code in such a way that you can do constexpr testing you're going to catch many more potential bugs. Similarly, string view is constexpr enabled. We can do this, this version now refuses to compile. So, yes, I have 42 seconds left. Yes! I'll leave that up. Any questions? Quick escape! Um, any questions? Yes? (audience mumbling) Do I know the rationale for the, if the init variable is visible in the l statement? Once you actually get used to using the if-inits, it really is the only thing that makes sense. Because you're going to initialize the thing and then you might want to take two different two different actions on that object depending on whether or not it was some other thing evaluated (mumbles). It really is the only thing that makes sense. But it's, you're not necessarily thinking about it when you go to use it. Anything else? Okay, so, a question way, you might have to run up to the microphone or something because, there's one right here. Run faster! You have negative minutes left. Yes. - [Audience Member] I have a question about, well I think the NRVO portion got skipped quite a bit because the compiler can't possibly get all the cases of NRVO unless if you know that there is a unique source. = Right, - [Audience Member] So, - Yes, well if you think about that that pointer that I said that object that's created here and then a pointer is passed forward to it, it clearly, that pointer can only be in one place. It must only be used to initialize one thing. And you're right, if you have multiple well, if you have multiple, - [Audience Member] Return statements. - Return statements, with named objects, then yes. It can't do the return value optimization the same way. One of them will get it and the other one will get a copy or whatever, or move. Yeah. - [Audience Member] Sure, I mean, I just think if you're depending on NRVO, your code's gonna get very brittle. - Oh, yes, if you're depending, oh okay. Well, I don't need to repeat it because you're on the microphone. I agree which is why I also am pretty strong on this don't name temporaries if you don't have a reason to. I try really hard to not name the things that I'm returning from functions, personally. And it makes the code much more modular and makes my functions shorter, and I'm happier in general. Anything else? I know it's time to go, I don't want to keep anyone over who needs to like, do whatever, eat dinner, or something. Alright, thank you. (clapping)
Info
Channel: CppCon
Views: 30,173
Rating: undefined out of 5
Keywords: Jason Turner, CppCon 2018, Computer Science (Field), + C (Programming Language), Bash Films, conference video recording services, conference recording services, nationwide conference recording services, conference videography services, conference video recording, conference filming services, conference services, conference recording, conference live streaming, event videographers, capture presentation slides, record presentation slides, event video recording
Id: uQyT-5iWUow
Channel Id: undefined
Length: 61min 28sec (3688 seconds)
Published: Sun Oct 28 2018
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.