CppCon 2017: Kate Gregory “10 Core Guidelines You Need to Start Using Now”

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments

Wow that was a good talk, especially in terms of delivery. Does the speaker have more stuff like this?

👍︎︎ 22 👤︎︎ u/TankorSmash 📅︎︎ Oct 18 2017 🗫︎ replies

At 00:41:32,she talks about "not_null<>" wrapper, a feature that was discussed two days ago here.

Those who were not convinced of the usefulness of the class can listen to her arguments and see if they are convincing or not.

Somebody who doesnt like the feature speaks at 00:53:57.

👍︎︎ 11 👤︎︎ u/muungwana 📅︎︎ Oct 18 2017 🗫︎ replies

Why does the not null slide use a backslash as a path separator in the include statement? That's horribly non-portable and in addition a pain to type on non-english keyboards (on a Finnish keyboard it's either altgr-plus on Windows and Linux or alt-shift-7 on Mac).

👍︎︎ 2 👤︎︎ u/jpakkane 📅︎︎ Oct 18 2017 🗫︎ replies

That's a good content but I cannot judge anything objectively that encourages default arguments.

IMHO explicit >> implicit!

👍︎︎ 2 👤︎︎ u/NotcamelCase 📅︎︎ Oct 18 2017 🗫︎ replies
Captions
- [Kate] Hey, good morning everyone. - [Audience] Good morning. Thank you for waking up and dragging yourself here, something that's going to get harder and harder as the week goes on, I appreciate it. My name is Kate Gregory and I was trying to figure out exactly when I started writing in C++, but I didn't know was going to be like a momentous occasion. But I'm going to declare that it was 1987 'cause then I can say I've being do it for 30 years. And in that time I've learned an awful Lot and I like to tell my children, "The only thing better than learning from your mistakes" "is learning from someone else's." And that's kinda what this is all about. So let's start with a show of hands, not counting the title of this talk, the abstract of this talk, or Monday's keynote, how many of you had heard of the C++ core guidelines? That's pretty good, that's about three quarters. I'm happy about that. How many of you have been to GitHub to this URL just once and looked at it at least once. So that's less hands by a dramatic factor like about a third of you. How many of you have again once changed the way you code something or persuaded someone else to change the way they were going to do something drawing on a guideline? That is really what I call a smattering of hands, okay. So my purpose for this hour is to change that last hand. And I understand it's daunting, right? There's like over 500 guidelines, and memorizing them all seems impossible. Starting at the top and reading also seems impossible. So I thought I'd do is I'd pick out a collection of guidelines and talk to you about them. They look like this. There's a bit of a categorization, this is functions, this is classes, this is expressions, blah, blah, blah, and there's all links and they have letters and numbers in their names. So when I talk to specific guidelines, I'm going to call them out by their letters and numbers and you can find them that way in the markdown. Who's on Slack, the Cpp Slack? Okay, that's not enough hands. You should all be on the Cpp Slack. You know you can ignore it if you're not interested right now. Think of it the way you think about Twitter like to step in for a moment see what's happening and step out, don't try to catch up. But one of the things that happens a lot in Slack is people say, "Should I A or B?" And so one of my hobbies is when I see that I'll say, "Well the guidelines say, 'You should B because reasons.'" And then people say things like, "But there's no speed difference." It's like, "Um, what?" (audience laughs) So let's be clear about what the guidelines are supposed to help you to achieve. Some of them really are because A is faster than B or vice versa. But many of them are for completely non-performance related reasons like readability or preventing bugs or whatever. And I picked up five reasons that I think are pretty prevalent reasons. And then I chose a couple of guidelines for each reason. So I'm not going to show you my top 10 guidelines or the 10 most applicable or the 10 easiest or anything. I just came up with these five reasons and found two guidelines for each reason, so that you understand the motivations of the people who are writing these things up for you. So let's start with the bike shed. For those of you who are unfamiliar with the metaphor, I'm an engineer by trade and when engineers design nuclear reactors or bridges or incredibly complicated things, submarines or whatnot and then they bring their plans for approval by and large all the non-technical people are like, "Oh, that looks good." But as soon as you have something that has very low stakes like a shed to keep bikes out of the rain, suddenly everyone has an opinion, everybody knows everything about it and we'd argument about that for six hours. And so there's a couple of guidelines that are really just about oh, for heaven sake make a decision and move on. So let's start with this one. I need to write a constructor and some of my member variables have sensible default values. How should I make this happen? I could write a default constructor, don't take D parameters and it gives that default values to all the parameters. Or I could use semi-new in-class member initializers. There's also the matter of whether or not you construct in general an initializer list for constructors. So here's a class. It's made up, but it's made up based on reality. When things grow over time and other people commit to do things especially when they don't fit on a slide and these constructors were actually several hundred lines apart in the same file, you get this kind of behavior. So the default constructor for simple here gives c a value of three. So that's obviously the default value for c, right? Except that the three argument constructor if you don't pass a value for c, it'll initialize c to minus one. And the one argument constructor will initialize c to zero. So three different ways to make it simple without specifying what you want for c and three different possible values for c. And you can tell that wildly different people did this, because whoever did the one argument constructor doesn't even know about initializer lists, right? That's a different guideline. Instead you're using class member initializers. Now you're writing less code, yay always good. And you're consistent. A lot of the guidelines it about taking away opportunities to be inconsistent. So if you're a disciplined person and you have a wonderful test suite and you have code review and everything else, you would've caught that inconsistency on the previous slide. Now there's no chance to be inconsistent. The defaults are in the in-class member initializers. And I like to go one step further and instead of empty brace brackets use default for that constructor. If I had no other constructors, this would put me back in the no user-defined constructors world. And according to the guidelines anyway, there's apparently a PERF benefit to this as well. To me the big benefit is we don't argue anymore. Yes they are equivalent. Yes you are disciplined and wonderful and would never become inconsistent, I get it. We can just stop arguing and just do it this way. Some other people argued for a long time let's just do this. And it may prevent some bugs and it may make your code faster. Similar argument, I got two versions of the same function. One's going to use two numbers, one's going to use three. Should I write two different overloads? Or should I write one function with the default argument? So here's the two overloads version. What am I simulating? A nuclear reactor apparently. And I've got, I can take a pair of doubles a and b and a fudge factor or just a and b. And it turns out that because I am a consistent and lovely person, I've implemented the three, the two argument one by calling the three with a hard-coded value. You can read that. You can see the difference between the two. It's very clear when I call the two argument version, fudge factor's one, that's great. But not everybody writes it like this especially when they start out small. Like they start like five lines of code. And one of them multiplies something by the fudge factor. And then we feel sorry for the poor little computer and we're like, "Why should I have a line of code "that's multiplying it by one, like that's dumb." So I'll just take that line away. We also take away things that add zero and that kind of stuff. Then that causes the two versions of the functions to diverge. Even if you're absolutely disciplined and they never do diverge, can anyone else really be sure that they have not? So this is fine, but the "I have two different "700-line versions of the functions "and it's up to you to figure out where they differ," not so fine. When you do it this way, it's very visibly obvious. You can't always do this. Not all two versus three argument overload situations can be resolved with a wave of a default argument. But if you always do it where they can, then when you don't do it you're also communicating, right? Again this is mostly about not arguing. It's mostly about skipping the bike shed. You could argue long and hard about how disciplined and consistent you are or you could just do it this way. It makes the difference clear and you can't get inconsistent later. Now let's get to the parts of C++ that could hurt you. These exist. We try to tell you, "You'll be sorry." So who's met this? It says, "Define and initialize member variables "in order of member declaration." Yeah, that's bringing some pain back for a handful of people and I bet it was quite a long time ago. This particular pain sticks with you for a long time. So here's a class you probably would never write. I am taking an integer parameter and I'm incrementing it as I initialize each variable. It's sort of an interview question. What would the value of x be after this constructor runs. We assume that code runs in order that it's written, so we figure well it'll initialize for my passing in zero. I'll increment the zero up to one and then I'll initialize a to that and then I'll increment it again to two and initialize b to that and then I'll increment it again to three and initialize x to that. And that's not correct. Now some people want it to be undefined behavior because they memorized that if you ever see two plus pluses on the same line, that's undefined behavior. It's not. This is beautifully defined, very clear, well, if you read standardese. We'll initialize a, then x, then b, because that's the order they're declared in a class. So the guideline says don't lie, write your initializers in the order they will execute since you can't control it. But there is a side effect which is, "Oh what, if you mean people rearrange my variables, "my code might break?" Yes. And the plus plus thing is totally fake. But in the wild I have seen pointers being initialized, new fits on the slide, but using a smart pointer would change nothing. And then something being initialized by calling something on the pointer. Again if there's a change order, that's a very badness, and I have seen first name and last name being used to initialize full name. Some of you may know a piece of lore called never use member variables in initializer lists, only ever reuse the parameters over and over again. And if that's a piece of wisdom that's been handed to you, it's for this reason. There are tools out there that will put your variables in alphabetical order. There are newbies who are learning their way around the code base who will decide to put related things with related things. There are an awful lot of reasons why your variable declarations might change order. And ideally just knowing that this exists will cause you to write stuff in a way that you're not vulnerable to it. But if you have to, at least you will have done it right the first time. The order will match reality, not the order you happen to think of writing them down in. And it may be a clue to the other people in your life, don't rearrange the variables without rearranging your initializers. This guideline is a vague one. I mentioned my original training was in chemical engineering and we passed around jokes in those days this was before the Internet on literally mimeographed pieces of paper and there was this big long thing about pipe which is hilarious it starts, a pipe is all a long hole surrounded by metal. Among other things it said that long pipe should be labeled as long pipe if it's over a mile long. But any type over two miles long should also be labeled not just at both ends, but also in the middle so you didn't have to walk away to the end to find out it was a long pipe. So in the same spirit, what does it mean to keep the number function arguments low right? Like 27 is probably not low. We shouldn't have to walk all the way to 27 to find out there was a lot arguments. For the sake of this slide, I'm make four a lot arguments and I understand that it might not be, you might not need to be quite this strict. But you can even see the effect with a relatively small definition of low. It's a function called area that takes four integers. You can guess by their names that sort of an xy, followed by another xy. But when you see them being called out in space 1, 11, 21, it's easy to forget. Is that the top left corner and the height and the width? Is it at the top left and the bottom right or is it the top right and bottom left because people are strange. What's happening, maybe it's both x's followed by both y's. If you make up an abstraction like point which is hardly a difficult abstraction to invent, now you can have your area function take two points. And that tiny change, that's super tiny because I don't even have to type the word point anywhere. It's being initialized from this initializer list of integers. The grouping is obvious. You're not going to think maybe it's a point followed by a height, followed by a width. It's clearly two points. And these abstractions may have value elsewhere in your life or not. Imagine that I have this class and for the purpose our discussion, it really doesn't matter whether details and rep are solid objects or pointers or references. It's not important. I need to make a Customer, which of these two ways of making Customer would you prefer? The one where you pass in the Person part and the Salesrep part or the one where you pass in seven strings. So it's just about reducing that cognitive burden. Maybe you'll invent a useful abstraction like point, that'd be cool. And it's possible you can even make your code more maintainable. So our area code was taking obviously two two-dimensional points. If we move to three-dimensional points, we'd have to take x1, y1, z1, x2, y2, z2 and we can't fix that up with default arguments because that z's in the middle or z for those of you with local locales. But if I made it points and my constructor for point has a default value for the z that I actually wouldn't need to go and change the code. That's a minor one that rarely happens. The big deal is really about not making people remember which of your 13 parameters is which especially if they're all in switch. It often seems to happen for those people who do this. Now that's, no one's going to say don't write functions anymore. I'm just saying don't have a whole ton of arguments to them. No one's going to say don't use constructor initializers anymore, just saying be careful about the order. But there are parts of the language where we really want to say, "Don't do that." So I love this guideline. First of all because it's four words long. You know a lot of things in guideline land and standard land are little on the verbose side. And the other thing, it doesn't say, "Prefer." It doesn't say, "Avoid." It doesn't say, "When there is a choice," like we did with the default arguments. It says, "Don't." Let's take a look at this class. This is a made-up thing. What have I got, I've got two private numbers and a calculation which doesn't change either number. Calc calls me some kind of integer result. I have a constructor, I have two non-const functions and a const getValue. You can guess because this has been written in a way that's const to the things that need to be consted, that Service1 and Service2 change number one or number two or both. In fact it's not outrageous to expect that Service1 change is number one and Service2 is change number two. And I'll tell you that I'll tell you that all get value does is call long complicated calculation. So this class is minding it's own business. This class is const correct. But somebody notices that this class is causing performance problems because of good old long complicated calculation. And so they decide to add some caching. Now we have the cached value and we'll initialize that to zero. And in this universe, the only thing that can change our cached value is to change the values of number one and number two. If you want you could have the constructor actually also figure out the cached value instead of just making it zero. So this code is still telling the truth. Service1 which is non-const is changing number one and it's also changing cached value. Service2 which is non-const is changing number two and it's also changing cached value and getValue is still being good. This is a pattern that works if you have more reads than writes. So for example to choose something that's not a complicated calculation, what day is it today needs to be updated on your display like all the time, but it only changes every 24 hours. But of course the world is full of things that are the other way around. They change all the time, and you only look at them once in a while. So if you have a group of people who are all updating their time sheets every half hour or so throughout the working day and their manager maybe only looks at their time sheets once or twice a week. So you have orders of magnitude more updates than you do reads. So in that case what you do is you add a signal that whether or not your cache is valid. And you just invalidate the cache on a write. So if you want to change number one that means the cached value is no good anymore. You want to change number two that also means the cached value is no good anymore, but we're not going to do the calculation. Then when somebody wants the value, well do I have a good cache? No, okay I'll do the calculation. So that's fine, it's good code. Small problem with it. Anyone see the small problem with this code? (faint speaking) It doesn't compile, yeah. So it's like there's no runtime errors though. Like you've got to give them points for that. We've said the getValue is constant. It's clearly changing some member variables of the class. And this is where people are like, "I know we'll cast away const." Actually there's a solution people try before that. Let's just throw away const correctness. Let's just say getValue isn't const after all. What the heck, it's not apparently compiler says. You know when I was teaching intro to C++ there's this whole thing where people stopped doing any thinking at all and just try to make the compiler happy. Just add stars and ampersands until the compiler shuts up and they don't really think about what they're trying to achieve. And that's sort of the deal with taking off the const. Like, "Oh, am I not allowed const there? "Okay, I'll take it off." And then you think you've made your code good. But of course you've made your code bad, right? Because we had this design that said, "getValue doesn't really change anything." So the old solution then would be to cast away const. And I'm not showing you code because you're not supposed to cast away const. But basically you make a thing called capital T this, that's the tradition. And you you const cast so that it's a non-const. What's our class called, stuff? So non-const stuff and then you could say, "This points to number one, number two or whatever." The problem this, A, it's ugly as all get out. You have to write out all kinds of extra stuff. You have to have indirections, blah, blah, blah. It's hard to read, hard to write. Your header file is now lying. Your header file says, "getValue doesn't change "any member variables of the class." But it does. And it actually has the power to change any of them. Like you could write number one equals zero in the middle of getValue and it would compile. So you have an ugly, oh and slower approach that makes your header fall a lie and could cause weird bugs that no one's ever going to look for. Gee, I don't know why we wouldn't want do this. Oh, I know why, because we can't think of anything else to do. So let me tell you some other things you could. You could take advantage of the fact that in C++ we have pointers, not just only references. You can have a pointer in your class that pointed to the cache and the pointer is const. You can follow it and change the numbers that live at the end of the pointer all you like that does not violate const correctness. That's an okay thing to do. I'm not crazy about it. I'm especially not crazy about it in our new world of if you're typing new and delete, you're doing it wrong because where did I get a pointer from? Clearly it's not the address of something because whatever's the address of would also be const. So it has to be something that I got from their free store. So who's calling new on it? Who's calling delete on it? What happens when you copy a stuff? Let's use a shared point or let's use a unique pointer. Well a unique pointer, huh? What happens when you try to copy a stuff? And a lot of this complication you actually should think about. Like what does it mean to copy one of these things around? And do you copy its cache with it and all that? They're actually valid questions. But they add complexity. So my favorite, just go back into your header and make it till the truth again. Make these two variables mutable. So mutable means I'm exempt from const. And now only those two guys are exempt from const, so getValue is still marked const correctly. And getValue cannot change number one or number two. Nobody needs to read your code to understand where you're telling the truth and where your lying. The whole story is here in the header file and we can just live happily ever after. And that's why they're able to say, "Don't cast away const." Find another way around your problem. I never liked reading const-casted code because of all the capital this points to stuff. It's harder to maintain. And then you have to explain to whoever's going to take over for you, why you're lying here. I never felt good about that either. You want to stay const correct especially in the world of now. We've got all kinds of performance issues and threading issues and things like that where being const correct has real everyday value in your code. Do not say, "Oh, I'll just make "the compiler happy and take the const off." Don't do that. Never transfer ownership by a raw pointer. Can I show you what this doesn't say? It doesn't say never use a raw pointer. People ask me how to do things and sometimes there's a really obvious use case for a pointer and they say, "But I was told we don't do pointers any more." And they're like trying to decide between a shared pointer and a unique pointer for something that very obviously needs a raw pointer. This guideline does not say, "Never use a raw pointer." What it doesn't want you to do is this and I think we've all met this code in our life, this thing news up a policy, whatever that is, and returns the pointer, And then its own local copy of the pointer obviously goes out of scope, so this guy's not cleaning this up. And whoever calls it has to know that it's their job to clean it up. Sometimes if you are lucky, the person who wrote this function has put words like alec or create or new in the name of the function. So it might be called alec policy and price it, something like that. But very often not. So you find out by the dreaded comments which no one reads. Or by a person writing you a note on page 72 of a hundred page document. Both of which kind of suck. In this universe, in the 2017 universe, probably the best thing to do is just return it by value. Just create it on the stack and return it. There's a really good chance it will be elited and if it isn't, maybe you don't even care about the copy so much. I keep meeting people who are all worked up about saving the copy and their string is, their class has got like a string and two ints. It's going to be exhausting when we copy those, it's going to kill us. There are places where copies are really expensive. I did some things with simulating oil refineries and collections of numbers representing the chemical composition of a piece of semi-refined petroleum. We might have a couple hundred doubles or floating points representing the fractions of different things, but more importantly, you had to, just think how flashing the stream which is a very expensive calculation about vapor and liquid and you don't just randomly copy those. But your employee with a first name and last name and a salary and a hire date, we can copy that, it'll be okay. And anyway the compilers are getting better and better and better at not copying that. So that should be your first thought. Why am I putting that on the free store at all. Now a thing about me and it's also thing about the guidelines, why my x does not mean don't x? It means tell me why you're doing that. and you may have a reason like it needs to have a longer scope and outlive and blah, blah. That's fine. If you want the caller to explicitly allocate it, you could take it by a non-const ref, and then change it. I don't like it when it's not in/out though, like if it were to be a pure out, I don't like pure outs. So let's use a smart pointer. So now the function will still be causing something to be allocated out on the heap, but it's going to give you back a unique pointer or a smart pointer or a shared pointer or whatever's appropriate. It's okay. It's probably the best choice if you have control over what the function does. You now have to argue about when to use which pointer. The correct answer is not shared pointer's easier so use that by the way. There are guidelines about choosing between unique and shared. If you can't change the API, you can annotate the API. This isn't going to change anything's behavior, but it's going to change your head. There's a template called owner. I will show you the entirety of the code for owner. It's not exactly computationally intensive. You can only make a owner of a pointer that's what that enable if stuff is. Don't worry about sveening. So you can't make an owner of an int or an owner of stuff. But you can make an owner of stuff* or policy* or employee*. And then its' just an alias. So if you're calling something on someone else's API and you need to declare that it returns, what were we, a policy*? You can declare that it returns a owner of policy* and that will collapse down to being a policy*. So you're fine, your declarations will match. But this tells the person who's making the call you own what you get back from that. It also sets up the possibility for checkers to say you have an owner of policy* here on line 73 of this file. And you never ever delete that and you let it go out of scope. And the the checker can tell you that you've messed up. Whereas an ordinary raw pointer, the checker has no way of the distinguishing false positives and telling you, "Hey, you never deleted that." It's like I don't need to delete that. I don't own for lifetime, I'm just looking at it. So this way you're marking everything that you actually own for lifetime. When Java first came out, we used to say, "Java people think that memory management "is too important to be left to the people." And we think it's too important to be left to the computer, ha, ha, ha. I think it's too important to be left to people. It's too hard, okay. So when you are calling a function that gives you a pointer and you have to remember to clean it up, that is too hard. Let's not go shopping though. Let's don't return a pointer, return a smart pointer or at the very least, leave yourself an actual in-code compilable real note that you have to clean it up. Just parts of the language. We don't go there anymore, that's great. Is anybody getting new standard fatigue? Well I see you're slow to put your hands up because like, "Do I want to admit that?" When 11 came out it was so exciting. Oh, look at the toys we get, this is great. And 14, it's like that's a bug fix to 11. That's good, we forgot some stuff, that's fantastic. 17 it's like, "Wait, I have to learn more new things? "I'm not sure, I've completely learned "all the old things yet." So some of the guidelines are telling you to use some things that you maybe didn't know were there. Or you've heard of them, but you don't know what they're for. So I picked up a couple of those. And this one, I'm going to slightly disagree with the guideline. The guideline says, "To return multiple out values, "prefer returning a tuple or struct." I now I totally agree that you shouldn't have multiple out values. It's just the order. I don't want to say tuple first. So here is something with an out value. It takes one real parameter and one for writing an answer into and it returns one and it writes one out. It's not, I'm going to have to fit my code on the slides. It's not exciting. Insert whatever legacy nightmare you currently live with, I'm sure you all have one. So you know if I want to call it, I have to allocate the out param first. I've given it a value because otherwise my tools will yell at me and say, "Oh, it's uninitialized, blah, blah. So the value doesn't really mean anything. And then I call the function. Meh. Oh. There's a design issue here, right? Who decides when they're not called one and two? Which things really are the out value and which are the return value? Or which seven things are out values and which is the return value? So I was prepping these slides, I went and asked a few people. Three different answers and all three answers started with it's simple, it's obvious, you just, so that's good (audience laughs). So for future reference, not that I ever want you to have out params, it's simple, it's obvious, you must, if you have a success/fail, if you have a bool, that's what you gotta return. So that you can say, "If foo," and then inside the if you can unpack the outs. Or it's simple, it's obvious, you just gotta whatever the name of the function is like if it's called price, it must return the price. And everything else is success/fail. The error message whatever, that's just baggage, that should just be out params. And then presumably from the people who had multiple children and didn't want to pick a favorite, return void and everything is out param and that way no one will forget to unpack anything. I hate them all. So my number one choice would be your own struct. You can only return one thing from a function, that's true. It doesn't mean there's only one value you can return. Return a struct. How hard is this? So here's a fascinating and useful struct which you can see took me hours to write. And that's the only flaw in this, okay? If the struct you're going to return could be called employee or purchase order or nuclear reactor condition, then this is a great solution. But when it's going to called two numbers or three ints in a string, it's not so good. Come on, who's written something called three ints in a string? I'm not the only one, right? Yeah, thank you. Anyway, the way you call it you can construct on the return line if you want. The only down side is having to have these things to pull the struct back apart into afterwards. Or else you have to go around saying, "Dot," like when you're using pair and you have to say dot first and dot second, right? So you have to know the names of the pieces of the struct. Again, it's pretty easy to know the names of the pieces of the struct when it's an employee or a nuclear reactor condition. and it's harder when it's called two numbers. (audience member laughs) Someone's having more fun than me (laughs). So I want take a side trip to stood optional because very, very common pattern is that the two values you want to return are a bool success/fail and a thingy. And the real purpose is to return the thingy but you also need to indicate whether that thingy is usable or not. A lot of people that's why they want to return a pointer because pointers can be null, not for anything related to lifetime. Stood optional is a great drop-in for that. People who are not me and are not you have slavishly created all of the things that you would've eventually gotten around to adding to your own struct for this. So if you just cast it to a bool like you say, "If foo at stuff," that's going to cast it, right? The bool that you get is that usable or not true or false part of the optional. If you try to cast it to a, what were we dealing with originally? I think a policy maybe or a stuff for a nuclear reactor. The operator equals will go get the value part and put it in the right place. You can even talk to it as though it were a pointer and dereference it. It is not a pointer. It is not on the free store, it's not the address of anything. But you can call star on it or points to on it and you'll get things out of the value as opposed to out of the optional. Probably my favorite value_or. Call some function that might give you a string . If it gave me a string, I want the string. If it didn't give me the string, I want quote-quote. That's what value_or does. Or it might give me an integer. If it didn't give me an integer, I what zero or minus one or something. That's what value_or does. So for that particular pattern where the two things you want are a boolean and a thingy, go with stood optional. And if you really want two ints or three strings and an int or whatever, then tuple is your friend. If there's no value to the struct afterwards and if you can't come up with better names for its pieces than indexes, then you want to go tuple, 'cause it's been done. Why do it? So I know that a tuple with two things would be a pair, but I decided I would do a tuple with two things anyways. So here is our version of foo that returns a tuple. And it uses make tuple to well, make a tuple. I'm sorry that the languages become easy to understand and sensible like that. It takes away some of my value you know. It should be called pushback so that I could explain where historically that name came from, but anyway. So we make a tuple and that's what we'll return, life is good. Now once upon a time, there was this thing with get in angle brackets where you put numbers in the int. I'm not even showing it to you, it's horrible. You can use tie to tie to local variables, in this case answer a number. The tuple will get unpacked into those parts, life is good. If you live in 17 land, you can use structured bindings. Now you don't even have to declare them before the call and this is really sweet. Answer and number will have the correct type, in this case they'll both be ints. And they're declared right then and there when I make that call. And I think this beauty is partly why the guidelines says, "Prefer tuple or a struct," because this is so sweet you want to tell people use it. But I still, I'm going to say prefer a struct or tuple because if you have a real name for your struct and real names for your fields then it's an actual useful abstraction that should be your first choice. But if it's called three ints in a string, then go ahead and make it a tuple. I made a tuple of int input for those of you haven't met this before. It's any and all types, no restrictions. So it could be int_string, int or what have you, it doesn't matter. One of the problems with out params is knowing when the function is done. So it's all very well on slides. But in the real world I think my record is a 15,000 line function. I met something with a 1,000 line catch block once. When you have a multiple page function, ans somewhere 17 or 18 lines in, it says, "x equals three." It's like, is that it, is that the answer? Are we touching x ever again? You don't really know. When you have a return statement, it kinda stands out. Even if you have many return statements in the function, they all kinda stand out. So if you have return make tuple at whatever, that's when the values are being sent back and you know that. I find that is a huge benefit just for reusability. It also makes your signature now carry different meaning. So now when I see a non-const ref parameter, I know it's an in-out, I know it's not an out. When I see a const ref parameter, I know it's an in. By taking out params out of the maybe, I can be confident about what I'm seeing in the code. So this is a lovely guideline and also a reason if you wanted one for why you should be using some 17 features. Who's used class enums yet? More than have used an actual guideline, that's good. For everyone else here is an enum class. It looks exactly like an enum except it has the word class in it. And until I define another one and then we pull off this miracle trick of having two values with the same name. You know most of the unreadability of our old code is that all are okay's are called like S okay, F okay, D okay and so on to distinguish them from all the other okays. And then it would look weird if I had Terrible, R OK, Terrific, so they're called R Terrible, R OK, R terrific and so on. And then people complain our code is hard to read. You can mix and match with old-style enums. These are for those of you like me who were not American, American states that happen start with O. And you use them with their full names. So that's why I've called them out in red. With the old style enums, you now can use them with their full names if you want to. It used to be an error to do that if you weren't on Visual Studio. It used to confuse the life out of me when I would take my code to another compiler and go, "Why can't I say what it's part of?" Well now you can if you want. And the most interesting thing about these guys is kind of hidden here in the last line of this slide. You know most functions that took enums didn't take enums at all, right? They took ints. I don't know why, it just became a habit of ours and so the compiler would be like, "Oh, that function wants an int "and you're passing it an error." Okay, terrific, except that it was actually expecting a ratings OK and you just had a mind slip and you passed it a very different numerical value than what it was expecting. So you think you're passing down one and you're actually passing 17 'cause you used a different enum. And then function goes off and does whatever it does for that wildly different enum value. So with the enum class you can get it to an int if you want by static casting which is the, "It is my foot "and I'm pointing this gun here on purpose, operator." But you can't do it accidentally as you can on that last line. I was arguing with someone the other night and we're saying the problem with C++ is our early defaults are mostly wrong. Like what's public and what's private and implicit conversions and that kind of thing. You have to say, "Explicit," if you don't want it to be used for conversions. Implicit conversions back and forth between the enums and ints may have saved us some time occasionally, but if we add up all of the bugs that we chased over our lifetimes, not so much, right? So that's to me the big deal of the enum class. But the immediate benefit is you don't have to call things R OK, S OK, E OK and whatever. You can just have the names you want. Oh and in theory, well I mean I know it's true I just never done it, you can have a different backing type. Like especially you can have a smaller type. I've never had a need for that. But should you, that is also choice. I briefly mention the guideline support library when I showed you owner. This is not the guideline support library, this is a library in Dublin, it's gorgeous though. In writing the guidelines, they got to places where they wanted to show you little snippets like say owner, one, two, three lines long. But rather than putting that textually in the guidelines and say, "Consider writing a template like this." They went off and designed the guideline support library. Anybody used anything from the guideline support library? Well, that's really now my record. That's five hands out of this whole room. What can I tell you, you are not the first developer with this problem. So I thought I would pick out two guidelines related to the GSL so that you understand that it's a library that maybe you want to look at. A pointer that must not be null. Have any of us ever met a pointer that must not be null? Gee, does that ever happen? Imagine some service S, I'm going to take its address and then use that to call DoSomething, a sort of code that only ever appears on slides because why wouldn't you just have said, S.doSomething, I know. But you know this is safe code. There's no way that ps is null, unless of course I were to set it to null pointer, and then immediately try to call DoSomething. What will happen, I will get a null pointer exception. If all our null pointer exceptions look like this, my hair would not have any gray in it. So you need to call if the pointer isn't null, blah, blah and that's really boring so you write like a little helper function. Ask the service to DoSomething which is all fine until you get to the part after the if. Like if I can't return p points to do something, what I can I return? Should I throw an exception? Well that's kind of stupid. I'm doing this so I won't get a null pointer exception. If I was okay with catching exception, I could have caught the null pointer exception. So that's not okay. Well remember we're talking a value_or for stood optional. So maybe it's like, "Hey if the service can't help me, "then the answer is zero," or quote-quote or false or something. And if you have a legit signal value and you don't care that this pointer somehow got null, then that's an okay pattern. But usually that's not the case. And when that's not the case the pointer cannot be null. So this ask pointer to do something is fine if it's okay for the pointer to be null we can deal with that and we'll just say zero. But what about when you can't. So you include the GSL header. The GSL is a spec, right? There is an implementation by Microsoft under Microsoft's GitHub instead of under the isocpp GitHub, and there can be plenty of other implementations as well. It's header only so you can just download it and put it in your code and carry on with your life. So now I'm declaring ps to be a GSL not null of service*. Everything else about ps is utterly unchanged. I'm still initializing it by taking the address of S for example. I'm still dereferencing it with the exact same operator. Everything you think is going to happen is going to happen. But it is not just an annotation like owner. This last line of code on the slide, ps equals null pointer, it's a compile error. Do you know how much I love that? I mean I tell people the compiler is your friend. There is no better time for the compiler to be my friend than on this line of code. Here's a pointer that should never be null and I'm setting it to null. What is wrong with me? So the compiler will actually because they suppressed various constructors, you get a compile error, you can't do this. That's great. But you know you can lie to compilers. You can hide things in different translation units. So I wrote a function called get pointer which could be doing something super clever, but which in fact just returns whatever it's given. And then back in my place where I declared my not null service pointer, ps, I say ps is equal to get pointer at null pointer. And then we all know what happens. The program carries on about its merry way and a year or two later, somebody happens to click some button and you still haven't put a decent value into ps and then we go boom, right? No. I mean you would, except that you went boom on the line that set ps to null pointer. So if it can't be a compiler, it's a runtime error. Not when you use it, but when you foolishly set it to null. That's the line you wanted to find, 'cause people are phoning you and sending you screenshots of their null pointer exceptions and you're like, "Okay, I get it, it's null." But how did it get null, who did that? And you're like searching your code base and setting weird memory base break points and everything else. It's a really hard thing to find. So here you'll get a runtime error when it gets set to null. You will know what line got you there and if you're the one who's running it, you can maybe look at the call stack to see what happened, blah, blah. So you can actually solve the problem and that's all it takes. If the point is not supposed to be null, mark that as not supposed to be null. This will actually make your application faster. Do you know what all those if the pointer isn't null are costing? And you're doing them every single time because you know in your bones that if you leave one out, you will get got. So you can take them all away. Of course you're being expressive. You're saying this is a pointer that is never null. So you don't go boom, your faster and everybody knows what you're saying. Most of the guidelines are like this. It's not a trade-off. It's not like, "Hmm, do I want this or this?" It's like good, good, good, good, good versus what we have now. I'm going to with good, good, good, good, good. Now this guideline is more generic than what I'm going to talk to you about. The guideline in general says try not to do narrowing conversions. You notice unlike, "Don't cost away const," it's a like a void, we're back to the, "You might want to consider, "if it's possible and convenient," right? But within there, there's a little piece of the description that mentions narrow and narrow_cast. First I want to show you this is some of my own code. I wrote it a very, very long time ago, but I was able to find it in a heartbeat. Some of you will recognize some of these types. You'll be like, "Oh, the poor dear thing, "she's on Windows, she's on Visual C++," because you can tell by the warning number that I have to disable. And I got no control over any of this. I'm being a funking layer. I'm getting the pieces of time out of an input time which keeps its bits and pieces in signed ints because if you need to represent a number between zero and 59, a signed int the obvious choice (audience laughs) I'm putting them into system time which wants everything in words. It is possible that some ints won't fit in some words, but it's not possible that zero to 59 are the 23 insolvable knot. So these are narrowing conversions which we are urged to avoid, but which we often cannot. I have pragmaed away the warning because I live with warnings and errors and I want zero warnings so I have to pragma away them. Then it's my personal convention that I tell you what I'm pragmaing away and in this case it's the possible loss of data. And then I'm obviously in real life, the line doesn't wrap. Seconds and whatnot cannot be too large for a word and they will always be positive. But compiler doesn't read comments. So narrow_cast and narrow which are not in the standard, they're in the GSL, they're just like say static_cast. I'm going to narrow_cast to a word. Just seeing the name tells the person who reads your line of code that this is a narrowing conversion as opposed to any other kind of randomly-chosen conversion that you're doing. So that's good. The one that's easier to type is a little slower because it does a little more. Say I have the number 200 and I want to put it in a type that can hold minus 127 to plus 128, a case you may have found yourself in by mistake. I can do that cast, but when I'm done, it doesn't have the value 200 over here. It has minus 70 something. Narrow checks after the cast, if the new value and the old value are equal. So if I'm trying to put 200 into something that can hold zero to 255, afterwards it's still 200, they're equal, we're fine. But if I'm trying to put it into something that holds minus 127 through 128, afterwards they're not equal and it either terminates or throws. You can control it with, I'm sorry, a macro. But I'm going to say it throws. So you get a runtime error. You have attempted a narrowing conversion that didn't succeed, it wasn't okay. All your assertions that this will be a number between zero and 59 and it will totally fit in an unsigned short are not so true after all. Ha, ha on you. Narrow_cast is more work to type. It just trusts you. Okay, it just happens. You might lose data. When you type narrow_cast you are saying I might lose data and I don't care. Do not come to me if I lose data. I'll do that for the times, because if we ever come to second number 73 of a minute, I got bigger problems than whether or not my code is working. But the default one, the one that's easier to type, will check for you and look after you. So again, you're expressing your intent and you may even get looked after and be told actually your reasoning is wrong. It's probably a good idea. So here are my 10. They're not the top 10. They're not the best 10, they're not the easiest 10. But they're the 10 that I chose to illustrate these five reasons. Don't argue about the bike shed. Don't spend your entire code review about whether or not to use default parameters. Don't hurt yourself with the spiky bits and the dangerous dark corners of the language. Forget certain things entirely. Just because we did them in 1990 does not mean that it's okay to do them now. Please do learn the new things. We're adding new things for a reason. They're elegant and they're beautiful and they solve real problems. Use them and then take a look at the guideline support library. It has other things it besides this. There's Span, there's all kinds of fun things happening in there that have real value in that will I am sure bubble up eventually, What I would like you to do, I'm not asking you to read the guidelines, I'm asking you only to bookmark the markdown documents in your browser. That is 10 seconds out of your life. Then someday soon, you have a decision to make. Someone asks you a question and you're like, "Hmm, should I A or B? "I wonder if the guidelines has an opinion?" And go and look and see if it has an opinion. You know like search within the browser or follow the headers or whatever. And if it has an opinion, consider following it. That's all. I believe if you do that two or three times you're going to say, "You know what, "I wonder what's in here that I didn't search for yet." At that point maybe you will read it top to bottom because by that time you will know there's something in it for you. That's really my take on the guidelines. They are going to help you make decisions more quickly and make better decisions which again usually you have to trade off between those two. But if you say, "Okay, I'm to go home "and read the whole thing," don't do that. Get your benefit first. Then once it's a proven, proven situation to you, try reading the rest of it. Now the other thing is there are slowly coming out checkers. So you can just point them at your code base. They'll go, "Blah," and they'll give you guideline numbers and say, "This is breaking F21, "and this is breaking E3 and so on." If you're up to it, consider that. But again maybe after you've had a chance to see that a few of these things really are advocating for what you think is the right thing to advocate for. And with that, yes indeed I have some time for questions. Thank you. (audience applauds) - [Audience Member] Hi, do you want to go back to not null? So you said there's no trade-offs on this and, that's not true. What don't you like? - [Audience Member] I don't like not null at all. I really like owner. - [Audience Member] Owner disappeared. Just a name, yes. - [Audience Member] Not null is, violates sort of one of my basic ideas on type design which is everyone really, really wants there to be transparent types. Like this is not a service star now. No it is not, it is not. It is a service star that cannot be null, yes. - [Audience Member] Right, it overloads specializations, conversions, all behave differently now. And the more that we try to layer things into the type system that are questionably actually types. Like there's a dark and difficult path-- Sure, but a unique pointer is not a service star either. Right. It overloads all that stuff. Right, but-- We've embraced it, because of what it brings us. Yes. Null pointer exceptions are in some ways worse than memory leaks because once they happen, boy you're done. - [Audience Member] True. Whereas you could leak for or quite a while and be okay. Right. So-- - [Audience Member] But now we have not null t. Presumably you also want not null unique putter. (Kate laughs) What's left in that when you move from it? Yeah, okay. That can't maintain its own invariance? Yeah, that's interesting. The intersection of not null and move is an interesting intersection, I'll grant you that. - [Audience Member] Like it's, there are very good reasons to try this, but it is something that should be approached with a little bit of caution and awareness. So I don't think it's fair that there's no trade-offs here. All right, yes, especially if you're as smart as Titus (laughs). - [Audience Member] Since we're bike shedding the rules could we see the tuple versus struct slide? The one in which you showed the tuple in action. Yeah, this one. So basically this should really read, "Return a struct." Return a struct, return a struct only if you're doing like template mega programming that you need to build-- My first choice is the struct. If you can give the struct a good name, return a struct. Yes. The guideline says tuple or struct. I'm like, "No." Struct or tuple. But I don't know that we're gaining anything by filling your program up with structs that are called two ints, three numbers, da, da, da, da, da. I'm okay with something generic there. Hmm? Don't give them names Don't give them names and that's what a tuple is. It doesn't have a name. - [Audience Member] But the tuple, but my concern with this is the tuple doesn't, you keep using the same tuple type everywhere a tuple of two numbers for representing a point, for representing a complex number, for representing a side, that's bad. Yes. Now different things-- Right, because those are abstracts. They are points, they are complex numbers, whatever. That's why I think in contrast to what they say, struct should be your first choice. Tuple should be your fall back, yes. - [Audience Member] And it's just such a shame that tuples, implement a tuple protocol on its own and for doing that slide using a struct one needs to write another three slides of all the-- Correct and that's why it appeals. But if you can give your abstraction in name like point or a complex number, that should be your choice, 100%. - [Audience Member] I would say even if you don't give it a name, just return, write the struct before the return type of the function, that's it. (Kate laughs) Give your struct meaningful names for the members, not for the struct type. And you have that, it's right there. Number one and number two, all right. In this case-- No, answer a number, it's right there. Because when you're going to use a tuple, you're going to decompose it that way, those are your members, right? This time. But when it was the point in the complex number, the pieces had different names then. - [Audience Member] But you're going to decompose it with the structure binding, you're going to call it x and y or whatever. Real imaginary, do that please. Thank you. Okay. - [Audience Member] So I have a question. You suggested immutable instead of const_casting. So maybe I've been doing things wrong my whole life, but it feels like, so I'm sticking immutable on a variable. I usually make an assumption when I see a const where I don't have to worry about things like threading. So doesn't that open a different problem when someone's saying, "Oh, I got const method, "I can just call it on front one and front two. "I don't have to worry about it." But-- Right. So that's why it's good. If you've got this in your design, that in this const function getValue stuff changes, when you tell the truth in your header, then the person who's trying to use this class in a threading situation, sees I've chosen read. So literally sees a red flag. When you don't have immutable in your header, but you just cast away const inside the function, nobody knows how horrible you're being. So I'm not arguing that it's not horrible, I'm saying tell people that you're horrible. Okay. Fair? (audience laughs) - [Audience Member] Well there's just usually you don't see all the headers. Usually you see a library, you get one header. Right, but headers you can at least see compared to the actual implementation you will not see. That's true. So tell the truth, own your stuff. - [Audience Member] So when going to the guidelines at the GitHub, do you have any tips on how to formulate a search string when you have a problem and wonder whether the guidelines have any guides? Because I-- That's actually non-trivial. So I start usually with these sections. So if I'm thinking about constructors, there's a whole section on constructors. There's a whole section on classes. There's a whole section on expressions. And when that works and then I'm just searching, I tend to search mostly for language queue words. Like return. And yes, you'll get a lot of false hits. I get a lot of false hits. Don't search for const in that markdown. You'll be there a long time (laughs). - [Audience Member] Thanks. I'm sorry I didn't have enough slides. - [Audience Member] (laughs) That's okay. So you said never cast away const. Did you really mean never? So I have all kinds of rules. Don't help without asking, right? But if someone's unconscious, you help them. Let's say never and then you really only will if you absolutely have no other choice at all which is very rare. - [Audience Member] Right and the situation I'm talking about is very rare, but if you have something like an element ref and you want to implement it in terms of a const element ref and you want to reuse the same pointer and you know when you construct it. Your own little mini-memory management. - [Audience Member] It's all the same header file and you're just doing stuff because you don't want to use up a user-defined conversion. So if you've proven to me that you have, you're not optimistically optimizing when you didn't need to, that you actually have a reason for having do that, then that might be an exception. - [Audience Member] And there's also a really wicked bug because if you do that kind of thing and you're not really careful about rebinding the references, then you can introduce something you absolutely had no idea you were doing. Right, because you're taking off the safety belts and then hey you might fall off the cliff. But sometimes-- You should probably assume when you do this that you've introduced a bug and you better test the bejesus out of it. Always a good rule (laughs). Hi. Last one. Hi. When we learned about the queue word auto, we started using it everywhere. Almost always auto. - [Audience Member] Soon enough we run into issues. I wondered if you have practical suggestions on when we should use auto, when we should prefer type-def? Oh and we're already over time. That's probably an hour (laughs). We'll talk one-on-one, okay? Sure, okay. Thank you, everyone. Thank you so much. (audience applauds)
Info
Channel: CppCon
Views: 124,888
Rating: undefined out of 5
Keywords: Kate Gregory, CppCon 2017, 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: XkDEzfpdcSg
Channel Id: undefined
Length: 62min 33sec (3753 seconds)
Published: Tue Oct 17 2017
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.