- [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)
Wow that was a good talk, especially in terms of delivery. Does the speaker have more stuff like this?
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.
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).
That's a good content but I cannot judge anything objectively that encourages default arguments.
IMHO explicit >> implicit!