Kate Gregory speaks to me as an actual coal-face software developer, not some lofty academic on a standards committee. I find her talks very useful in the real world.
Another great talk by Kate Gregory. She does excellent 101 and 102 talks. Her how to teach C++11 talk (titled: CppCon 2015: Kate Gregory “Stop Teaching C") is one of the better explanations of how C++ can become an easier language than C, by explaining the thought process behind abstraction a bit.
I learned C++ in the early 90s, so I do not know of any classes, even if they are a google away, because I have no reason to look for C++ classes. sorry I hear www.amazon.com/C-Programming-Language-4th/dp/0321563840 is good, but have not had a reason to look at it.
So I'm relearning C++, I never really used it seriously and I didn't touch it since C++11 came out. That was one of the "most helpful" video I ever watched about C++. (I didn't even know the core guideline existed)
About the order of member variables, Alexandrescu had a talk explaining they should be sorted by hotness, because adjacent members are adjacent in memory, and they'll go in a CPU cache together.
draguin For optimisation purposes, yes. When you're first writing your application, you tend to start with some other grouping, usually logical groupings or semi-random.
The member variables' order impacts the memory alignment of the struct or the class and you can sometimes shrink your struct's size just by reordering theses members
I think to the second question, immediately used structs/anonymus structs are useful for returning an unnamed struct with named members, but c++ only allows immediately used structs for typedef and variables but not functions... immediately used structs in variable-declarations: static struct { int coins, lives; } player { 10, 3 }; or as typedef: typedef struct { int x, y; } Position; But why not also in function-declarations or just used to directly initialize: struct { int div, rem; } divmod(int a, int b) { return { a/b, a%b }; }; Or auto divmod(int a, int b) -> struct { int div, rem; } { return { a/b, a%b }; }; Or auto divmod(int a, int b) { return (struct { int div, rem; }) { a/b, a%b }; }; Why does the c++-committee not got the idea to allow a struct-definition everywhere as a type-expression like "std::vector" or "unsigned int*"?
Terrific talk, both in content and presentation. I was surprised by the guideline about initializer lists, because I thought compilers always warn if you initialize them in the wrong order. (At least, mine always does, and I had taken it for granted as a language feature.) I think some of the advice about not using member variables in subsequent member initialization becomes obsolete if the compiler enforces the initialization order. Again, though -- this is a minor nitpick for a talk that should be required viewing for C++ engineers.
9:24 I don't remember time when initialization list could be in any other order than the one the variables are listed in class. I'm guessing that has not always been the case but as recently as 2017?
36:30 You don't write multi-page functions in the first place if you care about readability, she should have stressed that. You should see instantly, what a function is doing at a high-level. If you are interested in the details, then you look into the sub-functions. one example: void drawGame() { drawMap(); drawPlayer(); drawEnemies(); // ... } If you are interested what is drawn in a game, then this function explains it to you, you do not need to know any implementation detail. If you have a monster function of thousands of lines, you just have no idea what this function is actually doing and need to read through all the details and then guess what the intent behind this function is.
But if you're looking at the function's body then you do care about its implementation. If those 3 functions are only called in drawGame then you have to go chase their function bodies in other files. Seeing what a function does at the high level is accomplished with a good function name...i.e. drawGame()
@robrick9361 it's to gain an immediate high-level understanding of what the implementation actually does before you get into the specifics. You'd have to read the entirety of the implementation logic to see understand what it does even at a design level.
Very good tipps! @Refactore & avoid const_cast after violating const correctness by introduce cacheValue: I am a little bit proud that I thought directly the presented solution before shown in the video (refactoring "extract member value into own class and replace a pointer reference to it")
Excellent content so far, but I got to 14:38 but there's four words on a slide which wraps to the next line! Oh, never mind, it that's the worst thing in the talk I'll be happy.
@ 24:30 - It was my understanding that, since C++11, movable types only get shallow copied when returned out of their originating scope. That is, they’re *moved*.
Since long before C++11, return value optimization (RVO) has been supported in many cases. And it has been even better than constructing-a-temporary-and-moving-the-temporary-and-destroying-the-remains-of-the-temporary. With RVO, there is no temporary--we are directly constructing the return value residing in the stack frame of the caller. The callee is passed an invisible parameter--a pointer to the stack-allocated-by-caller-but-not-yet-constructed return value, and the callee uses something similar to "placement new" to construct-without-allocating at the specified address. That is part of the reason why writing "return std::move (my_local_variable);" is often a pessimization. It is better to write "return my_local_variable;". I think C++17 has made RVO a requirement.
Careful what you wish for... It might end up in C++23 as narrow_cast throwing an exception, narrow_unsafe_cast doing what narrow_cast is doing currently and narrow will result in a compile time error. Or we will see gsl23::narrow_unsafe_cast, etc. ;)
Yes, you can, and with good reason, at least in the world of Windows. During the same period when support for C++ exceptions was gaining support in compilers (the early 1990's), Windows introduced Structured Exception Handling (SEH). SEH has similar rational/intent as C++ exception handling, and there were some differences. (Similarity: They both support decoupling of error-detection from error-recovery.) (Difference 1: C++ exceptions can transport arbitrarily large pieces of information from the throw-site to the catch-site, while Structured Exceptions have fixed format--which also makes the matching faster than catch-matching-to-thrown-object-type.) (Difference 2: Errors such as integral-division-by-zero and reading-or-writing-via-bad-pointers are undefined behaviour with Standard C++, but the behaviour is precisely defined with Windows SEH--as with POSIX signals, but in a much more useful way--really, in a "structured" way). Both C++exception handling and SEH are good. Programmers have been using both, e.g. in different parts of the same program. Therefore, compilers have added support (e.g. the /EH... switches for Visual C++) for allowing us to specify how the two mechanisms interact: - Do Structured Exceptions unwind the stack like C++ exceptions do (by destroying stack objects until a matching handler is found) ? - Can we use the C++ "catch (...)" clause to match a Structured Exception ? - Can we use the SEH "__raise" clause to catch a C++ exception of a very simple type (e.g. DWORD) ? Also, the _set_se_translator Windows API function can register a callback for the __raise-ing of a Structured Exception, e.g. one which throws a C++ object which carries the fixed pieces of information from the Structured Exception to a C++ catch clause (not just to the C++ "catch (...)" clause, which gets no information). Interestingly, these features, which are not standard in C++, but widely available in the world of Windows, constitute reasons why, for portability, we may want to avoid throwing C++ objects of non-std::exception-derived types.
9:17, this should be a core guideline: never write a sentence that 1 var changes more than 1x. It saves you from reading the standard. Stop arguing, just don't do that. 1:02:05, I had once a bug regarding to the 'auto' keyword. It was on a far, far ancient time, when I was starting to use it, and didn't know it wouldn't grab the reference declaration... Don't remove it from the language, it would be a blunder.
I wonder how many people, Kate Gregory included, know where the 'bikeshed' metaphor comes from. It's from the original book on Parkinson's Law by C. Northcote Parkinson. Beware of other authors trading on the name - go to the master himself.
Exactly. And even though he may have already written about that stuff, the point was to educate about the mentality and usage. Many talks could be considered redundant if you said, "just read this book"... but that's not really the point.
She said the opposite. 0-59 will fit but some int might not fit in like the system time. Better to have unsigned int to have a bigger range of positive integers
I'm sorry, but the very first example about the initializers, the refacored class has a different meaning. In A a,b,c become 1,2,3 with the default constructor. In B they become -1,-1,-1. In A c is optional in the second constructor, in B it is mandatory. I think the first guideline should be: When refactoring, don't change the meaning of the code.
The point was that the old meaning was accidental and organic. People assigned default values without really thinking about it, often assuming they would never really be used. As part of stopping and thinking about the class, you can give consistent default values without relying on the authors of future constructors. So this meaning change was a deliberate correction.
@@gregcons How do you know that the original meaning isn't the correct one? Your assumptions might be wrong. I think you should have shown the 'better form' of the 'original' meaning, because that code has been running just fine for years but recently they have been getting some strange results over in Sector 7-G.
If you have code that doesn’t follow the guidelines but is *benchmarked*, and runs faster: use the faster code, make sure you don’t have ANY UB, and write the guideline compliant code in the function documentation.
The example about casting away const is completely wrong. Taking away "const" from getValue _is_ the correct thing to do, because it clearly is _not_ const anymore. That's the tradeoff you made when you decided to implement that cache. Being explicit about it is not violating const-correctness, it's _enforcing_ it. It doesn't matter that reading from a class is usually const; for this one it's not. This class behaves differently, so beware! Communicating that is good and useful. On the contrary, slapping the keyword "mutable" on the cache is effectively syntactic sugar for... casting away const, precisely the thing that was to be avoided, and it makes the header file a lie, again something to be avoided. Our API types should reflect what our code _is,_ not what we _wish_ it were.
I disagree. The reason we have mutable in there is because we want to cache the value and only recalculate when we want to get the value. We're not really changing anything when we get Value, we're triggering a calculation that would have happened anyway, just in a more computationally friendly manner.
Kate Gregory speaks to me as an actual coal-face software developer, not some lofty academic on a standards committee. I find her talks very useful in the real world.
static_cast: the "it's MY gun and I'm pointing it at MY foot" operator. incredible
I suppose reinterpret_cast would be the "it's MY gun and I'm pointing it at MY head" operator. :D
TO VOID AND BACK AGAIN!
@@TanzinulIslam const_cast is the "it's your gun and I'm pointing it at your family" operator
Another great talk by Kate Gregory. She does excellent 101 and 102 talks. Her how to teach C++11 talk (titled: CppCon 2015: Kate Gregory “Stop Teaching C") is one of the better explanations of how C++ can become an easier language than C, by explaining the thought process behind abstraction a bit.
Yes, her C++11 talk changed the way I approached teaching and mentoring C++ entirely and with some success.
D D are you aware of any books/courses/ or some other materials that teachec c++ the way she promotes? Thanks
I learned C++ in the early 90s, so I do not know of any classes, even if they are a google away, because I have no reason to look for C++ classes. sorry
I hear www.amazon.com/C-Programming-Language-4th/dp/0321563840 is good, but have not had a reason to look at it.
on of the best talks about c++ I have ever watched.
So I'm relearning C++, I never really used it seriously and I didn't touch it since C++11 came out. That was one of the "most helpful" video I ever watched about C++. (I didn't even know the core guideline existed)
Kate is great...her lectures on PluralSight helped me a lot back in the day!
Very nicely presented. A shining example of how to make a clear and accessible talk. Thanks.
About the order of member variables, Alexandrescu had a talk explaining they should be sorted by hotness, because adjacent members are adjacent in memory, and they'll go in a CPU cache together.
draguin For optimisation purposes, yes. When you're first writing your application, you tend to start with some other grouping, usually logical groupings or semi-random.
Late optimization is the root of all evil.
@vogue43 I suspect that if you think you know ahead of time how hot your member variables are, you're probably wrong.
The member variables' order impacts the memory alignment of the struct or the class and you can sometimes shrink your struct's size just by reordering theses members
How calm and confident. Loved it!
I think to the second question, immediately used structs/anonymus structs are useful for returning an unnamed struct with named members, but c++ only allows immediately used structs for typedef and variables but not functions...
immediately used structs in variable-declarations:
static struct {
int coins, lives;
} player { 10, 3 };
or as typedef:
typedef struct {
int x, y;
} Position;
But why not also in function-declarations or just used to directly initialize:
struct {
int div, rem;
} divmod(int a, int b) {
return { a/b, a%b };
};
Or
auto divmod(int a, int b) -> struct {
int div, rem;
} {
return { a/b, a%b };
};
Or
auto divmod(int a, int b) {
return
(struct { int div, rem; }) { a/b, a%b };
};
Why does the c++-committee not got the idea to allow a struct-definition everywhere as a type-expression like "std::vector" or "unsigned int*"?
It may have been impossible to allow that syntax without syntax conflicts
@@mikehebert5489 What about a new keyword?
anon {
int div, rem;
} divmod(int a, int b) {
return { a/b, a%b };
};
Kate when can we expect some more talks from you?
Very good talk, well presented and great info. Thanks for sharing.
18:25
"... and just try to make the compiler happy. Just add starts and ampersands until the compiler shuts up."
Thats my life basically.
ok why the hell did noone tell me about enum class before this is all i ever wanted in my life for gods sake
brb gonna do a string replace over my current project
39:53 “The problem with C++ is, our early defaults are mostly wrong.”
this is what is wrong with a great many languages.
Terrific talk, both in content and presentation.
I was surprised by the guideline about initializer lists, because I thought compilers always warn if you initialize them in the wrong order. (At least, mine always does, and I had taken it for granted as a language feature.) I think some of the advice about not using member variables in subsequent member initialization becomes obsolete if the compiler enforces the initialization order.
Again, though -- this is a minor nitpick for a talk that should be required viewing for C++ engineers.
msvc didn't have a warning for this until Visual Studio 2017 :( Now there's C5038.
9:24 I don't remember time when initialization list could be in any other order than the one the variables are listed in class. I'm guessing that has not always been the case but as recently as 2017?
Great teaching skills on display.
Glad you think so!
Good talk, I learned a lot!
Why c++ doesn't support = {.x = 42, .y=7} kind of initialization?
The fighting with the compiler thing is a really big issue still.
Very well informed and presented.
Can somebody explain the guys' complaint on 54:03? Like for someone who is not that proficient?
I remember the bit about pipes. :)
36:30 You don't write multi-page functions in the first place if you care about readability, she should have stressed that. You should see instantly, what a function is doing at a high-level. If you are interested in the details, then you look into the sub-functions.
one example:
void drawGame()
{
drawMap();
drawPlayer();
drawEnemies();
// ...
}
If you are interested what is drawn in a game, then this function explains it to you, you do not need to know any implementation detail. If you have a monster function of thousands of lines, you just have no idea what this function is actually doing and need to read through all the details and then guess what the intent behind this function is.
But if you're looking at the function's body then you do care about its implementation.
If those 3 functions are only called in drawGame then you have to go chase their function bodies in other files.
Seeing what a function does at the high level is accomplished with a good function name...i.e. drawGame()
@robrick9361 it's to gain an immediate high-level understanding of what the implementation actually does before you get into the specifics. You'd have to read the entirety of the implementation logic to see understand what it does even at a design level.
At 44:25 the include has a bad \ in the file name. At least escape it as \\ or better use / even on MS.
Very good tipps!
@Refactore & avoid const_cast after violating const correctness by introduce cacheValue:
I am a little bit proud that I thought directly the presented solution before shown in the video (refactoring "extract member value into own class and replace a pointer reference to it")
when I saw the scissor I tough is a talk about computer graphics, well I was wrong, seems to be general guidelines as the title says...
she’s made to do this! great talk!!
what if you like bikeshedding?
Amazing talk. Thank you so much.
Excellent content so far, but I got to 14:38 but there's four words on a slide which wraps to the next line!
Oh, never mind, it that's the worst thing in the talk I'll be happy.
Does she have a C++ course somewhere online? If not she should..
There are some in Pluralsight.
@ 24:30 - It was my understanding that, since C++11, movable types only get shallow copied when returned out of their originating scope. That is, they’re *moved*.
Since long before C++11, return value optimization (RVO) has been supported in many cases.
And it has been even better than constructing-a-temporary-and-moving-the-temporary-and-destroying-the-remains-of-the-temporary.
With RVO, there is no temporary--we are directly constructing the return value residing in the stack frame of the caller.
The callee is passed an invisible parameter--a pointer to the stack-allocated-by-caller-but-not-yet-constructed return value, and the callee uses something similar to "placement new" to construct-without-allocating at the specified address.
That is part of the reason why writing "return std::move (my_local_variable);" is often a pessimization. It is better to write "return my_local_variable;".
I think C++17 has made RVO a requirement.
35:57 so pythonic, I love it!
About being expressive: gsl::narrow_cast should be gsl::narrow_unsafe_cast. No need to explain the meaning then.
Careful what you wish for... It might end up in C++23 as narrow_cast throwing an exception, narrow_unsafe_cast doing what narrow_cast is doing currently and narrow will result in a compile time error. Or we will see gsl23::narrow_unsafe_cast, etc. ;)
gsl::non_null is amazing!
i found it very funny how the main point of this talk was being consistent however, every slide kates code style was wildly different,
Argh stop saying null pointer exception.. that won't happen it will just crash, you can't catch(const std::NPE&) {}
Yes, you can, and with good reason, at least in the world of Windows.
During the same period when support for C++ exceptions was gaining support in compilers (the early 1990's), Windows introduced Structured Exception Handling (SEH).
SEH has similar rational/intent as C++ exception handling, and there were some differences.
(Similarity: They both support decoupling of error-detection from error-recovery.)
(Difference 1: C++ exceptions can transport arbitrarily large pieces of information from the throw-site to the catch-site, while Structured Exceptions have fixed format--which also makes the matching faster than catch-matching-to-thrown-object-type.)
(Difference 2: Errors such as integral-division-by-zero and reading-or-writing-via-bad-pointers are undefined behaviour with Standard C++, but the behaviour is precisely defined with Windows SEH--as with POSIX signals, but in a much more useful way--really, in a "structured" way).
Both C++exception handling and SEH are good.
Programmers have been using both, e.g. in different parts of the same program.
Therefore, compilers have added support (e.g. the /EH... switches for Visual C++) for allowing us to specify how the two mechanisms interact:
- Do Structured Exceptions unwind the stack like C++ exceptions do (by destroying stack objects until a matching handler is found) ?
- Can we use the C++ "catch (...)" clause to match a Structured Exception ?
- Can we use the SEH "__raise" clause to catch a C++ exception of a very simple type (e.g. DWORD) ?
Also, the _set_se_translator Windows API function can register a callback for the __raise-ing of a Structured Exception, e.g. one which throws a C++ object which carries the fixed pieces of information from the Structured Exception to a C++ catch clause (not just to the C++ "catch (...)" clause, which gets no information).
Interestingly, these features, which are not standard in C++, but widely available in the world of Windows, constitute reasons why, for portability, we may want to avoid throwing C++ objects of non-std::exception-derived types.
-127 to 128?
I'm more used to -128 to 127
Every code base should have at least 1 const_cast, otherwise it's like playing russian roulette with no bullets.
9:17, this should be a core guideline: never write a sentence that 1 var changes more than 1x. It saves you from reading the standard. Stop arguing, just don't do that.
1:02:05, I had once a bug regarding to the 'auto' keyword. It was on a far, far ancient time, when I was starting to use it, and didn't know it wouldn't grab the reference declaration...
Don't remove it from the language, it would be a blunder.
is there a performance loss by using the suggested style of 13:20 ?
22:30 raw pointer
12:29 the area function taking in two Point killed me just a little bit from the inside
informative.
I wonder how many people, Kate Gregory included, know where the 'bikeshed' metaphor comes from. It's from the original book on Parkinson's Law by C. Northcote Parkinson. Beware of other authors trading on the name - go to the master himself.
Of course I know. But it's not actually important. It's become one of our jargon terms. If you know what it refers to, that's what matters.
Nice Lecture ........
This talk just seems to be straight out of "Effective C++" by Scott Meyers
Electromag[e] Good for you!
?
Exactly. And even though he may have already written about that stuff, the point was to educate about the mentality and usage. Many talks could be considered redundant if you said, "just read this book"... but that's not really the point.
Further, I would not be surprised if the guidelines was inspired by Scott or vice versa.
Nowhere in my wording did I call any of it a bad thing or wrong
47:40 Why is signed int a bad choice for an integer from 0 to 59 ?
She said the opposite. 0-59 will fit but some int might not fit in like the system time. Better to have unsigned int to have a bigger range of positive integers
I'm sorry, but the very first example about the initializers, the refacored class has a different meaning. In A a,b,c become 1,2,3 with the default constructor. In B they become -1,-1,-1. In A c is optional in the second constructor, in B it is mandatory. I think the first guideline should be: When refactoring, don't change the meaning of the code.
That's why you should use test harness when refactoring code (Cfr working with legacy code from michael c. feather)
The point was that the old meaning was accidental and organic. People assigned default values without really thinking about it, often assuming they would never really be used. As part of stopping and thinking about the class, you can give consistent default values without relying on the authors of future constructors. So this meaning change was a deliberate correction.
@@gregcons How do you know that the original meaning isn't the correct one? Your assumptions might be wrong.
I think you should have shown the 'better form' of the 'original' meaning, because that code has been running just fine for years but recently they have been getting some strange results over in Sector 7-G.
Sensei...
She's just like my biology teacher, lol.
One Weird Trick To ...
If you have code that doesn’t follow the guidelines but is *benchmarked*, and runs faster: use the faster code, make sure you don’t have ANY UB, and write the guideline compliant code in the function documentation.
Lol @ three_ints_and_a_string
IIIS :)
*I have never seen a good presentation or a course with Kate Gregory!!!*
Lost me at fudge factors lol
The example about casting away const is completely wrong. Taking away "const" from getValue _is_ the correct thing to do, because it clearly is _not_ const anymore. That's the tradeoff you made when you decided to implement that cache. Being explicit about it is not violating const-correctness, it's _enforcing_ it. It doesn't matter that reading from a class is usually const; for this one it's not. This class behaves differently, so beware! Communicating that is good and useful. On the contrary, slapping the keyword "mutable" on the cache is effectively syntactic sugar for... casting away const, precisely the thing that was to be avoided, and it makes the header file a lie, again something to be avoided.
Our API types should reflect what our code _is,_ not what we _wish_ it were.
I disagree. The reason we have mutable in there is because we want to cache the value and only recalculate when we want to get the value. We're not really changing anything when we get Value, we're triggering a calculation that would have happened anyway, just in a more computationally friendly manner.