@@mario_luis_dev C++ is an awful language. It’s way too complicated, compilation is incredibly slow, there a many ways of achieving the same thing. ABI instability is a huge problem that basically renders the STL useless for library development. Undefined behavior makes it hard to reason about what your compiled code will actually do. I could go on and on.
I will keep it up! But also don't be afraid to look up some modern C++. The language has actually simplified quite a bit if you avoid a lot of legacy functionality. I'd recommend looking up the C++ core guidelines as a great starting point!
I've used c++ for around 3-4 years for competitive programming and messing around a bit with opengl, but had no idea about anything from 22 onwards! I need to brush up on modern c++!
@@ron0studios modern c++ is bloat and ugly, there's a reason highly performance intensive industries don't use most of that stuff, because only a select few sane enough features of modern c++ are actually useful. I hope you gave up on your journey to "brushing up on your modern c++", because it will only make you a worse programmer. You already be aware of all of this tho, specially if it is true that you've been into competitive programming and doing graphics programming....
12:20 make_shared also allocates the resource and the control block together, making it much faster (and cache friendly) than directly constructing a shared pointer
An addition to the last comment about pointers coming from C libraries: Many C libraries that expect the caller to free the object often also provide their own "free" function for that specific object type. So the solution is to make your custom deleter function call that special "free" function. Also remember that the custom deleter function, which is passed as a template parameter to unique_ptr, can be a lambda too.
Excellent advice! The idea is basically the same as malloc vs free. If you called a C library library_alloc_whatever_handle then you can follow the same pattern as in the video (or use a lambda) to make sure your unique_ptr calls the library_free_whatever_handle (the names of the alloc/dealloc functions should be documented).
Thank you so much, both of you! I moved from being a bare metal C developer to working on C++ Applications recently. I still call C libraries for hardware interfacing and I am racked with guilt for every bare pointer I juggle from the libs. This advice is golden and will save me a lot of lost sleep!
The problem with C++ is that you are spending more time writing and debugging “safe” but messy wrappers for already existing C++ libraries than it would take to write and debug some clean C :)
@@noop9k I am not sure what you're talking about. You do realize that there are excellent C++ libs out there, like Qt and Boost, right? Or are you talking about C libs that have half-baked C++ wrappers? Even in that case, it still makes more sense to use the C api but wrapped in a safe way: raw pointers -> unique_ptr with custom deleter. It is more memory safe that way. You don't need to worry about explicitly freeing the pointer at every possible exit point (when going out of scope).
PLEASE DO NOT STOP MAKING THESE VIDEOS, youtube was really lacking these proper advanced(? at least for me) tutorials on programming. I am learning to code for ~3 years now, and I have been looking too learn more advanced stuff multiple times, you are the holy grail.
I totally read that as "please stop" and then something to do with the youtube algorithm. So I was like, "oh this nutty comment is going to be good" XD
This is really valuable content, thank you! I think I managed to pin down what makes this really nice. This isn't necessarily the kind of advice that you'd get by reading a book (or at least not the books I read). I learned this kind of stuff mostly by "folklore", i.e. some more experienced developer told me to do things this way because it's neater. The value of this stuff is that you're making this advice accessible to everyone, not just those with senior mentors! :D I really appreciate that
Then you haven't read the right books imo Any book by Scott Meyers, recent book by Jason Turner (and his yt channel). Nicolas Josuttis' books are also full of gotcha's and how to deal with them, I really like his books, I think they made me a much better C++ programmer. Then there are the cpp core guidelines and clang-tidy (a static checker that checks for some of the issues in this list and makes sugstions). And I'm sure there are more. I mean, this is a good list, very condense with the correct argumentation, but this information is not just 'tribal knowledge'
I've been a professional developer, mostly in C++, for over a decade, and I _still_ sometimes unintentionally roll my own when a standard algorithm would have been better because there's just a bunch of them that don't come up very often and they're easy to forget.
I learned SO MUCH with this video. I started with python as my first language, but now I'm switching to C and C++ because I decided that I want to work with embedded systems. Also, I watch your videos since long time, keep what you're doing, your work is awesome.
I am a noob myself, but I think another common nooby habit that's worth mentioning is using push_back instead of emplace_back when putting a newly created object in a container right away
@Jacob Sorensen I specifically said "newly created object ... right away" (temporary object that is). emplace_back creates an object with the provided arguments in-place, whereas if you create an object and push_back it immediately, you get one unnecessary copy/move. However, in most cases compiler will probably optimize it
push_back copies the object. emplace_back calls the constructor (like make_unique). If you pass a fully constructed object to emplace_back it'll have to use the copy constructor (or the move constructor if available). You want to pass instead the constructor arguments to emplace_back. Otherwise, just use push_back to make clear that you know you are copying there.
@@danielwappner1035 not really. Copy vs. move semantics matter for high performance, low latency applications or in constrained environments (e.g. embedded systems).
The point about "raw pointers aren't always bad" didn't use the best examples, because in both cases you could instead use references. That way you'd not have a raw pointer, *and* you also wouldn't have the unchecked dereferences in both functions. There are situations where you might still want to use raw pointers, but that's where you're expecting null to be a possibility as well, which is only because you can't have std::optional (which some would argue is a bad decision to have made).
>which is only because you can't have std::optional (which some would argue is a bad decision to have made). Took the words right out of my mouth! It's *really* hard to find a legitimate use for raw pointers.
@@Danielle_1234 "It's really hard to find a legitimate use for raw pointers" It really isn't. The fact that references are immutable and cannot be rebound is a strength when you use them in, say, function parameters, but it causes a world of pain if you want to _store_ a reference to an object. This is because, like const members, a reference member will implicitly delete copy and move assignment, which makes said classes a lot less ergonomic to use. The solution to the const member problem is not to make the member const but to make it private; the solution to the reference member problem is to use a pointer. Also, some codebases use the convention that function calls that mutate their arguments should take those arguments by pointer instead of by reference, which, if done consistently, makes it obvious at the call site that the argument will be mutated.
@isodoubIet In that particular case I would use a reference_wrapper if the member is not supposed to ever be null. I would only use a raw pointer if I expect to encounter or need the null state.
No issue whatsoever. Most of what he told are about new features of the STL, and using too much STL in your codebase is the most nooby thing you could ever do. It sacrifices all readability for the dev's laziness
It is their responsibility to keep up with the standard as it is updated. Regardless of age and experience, if someone is coding in C++98 when we have features through 23, they are a noob.
At last, a TH-cam video on C++ done by someone who actually knows modern C++. Thank you, thank you, thank you. But I'd like to humble suggest a number 32: declaring all your variables at the top of a function. This is a holdover from old-fashioned C, and I still see it taught in some places. Don't declare a variable until you can initialize it with a meaningful value. (BTW, even C hasn't required this coding style for about 20 years.)
NOTE: I'm not arguing against this, I'm just trying to understand the reasoning. I've been told this before but what actually is the detriment? I learned on C so I've had that tendency. I don't really do it anymore but I see the readability benefit of knowing what variables you're using in a particular function by just looking at the top (not arguing that its best practice though), I've just never gotten a good answer as to why its bad. If I simply initialize something to null until I new it or give the constructor a default value and assign the values later (assuming I'm not trying to re-invoke the constructor and essentially initialize the variable twice) what would be the problem?
@@nightfox6738 There's a few reasons why it's bad. 1. You may not have a meaningful value to initialize the variable with. Sure, you can use null, but that forces you to use a pointer, and in C++ values are usually better. You could use a std::optional, but that'd be a little confusing. Ultimately, you're having to make weird tradeoffs and what you get really isn't worth it -- it's just trying to adhere to a convention from a different language, where it only existed because of compiler limitations anyway. 2. What if you make some changes to your code and accidentally introduce a condition where the initialization code never gets run? You'll be dereferencing a null pointer/accessing an empty optional. 3. Having the variable initialized at the top of the function means it exists throughout the whole function, which means you might use it again, and this introduces coupling = complexity + possible bugs. 4. Declaring all variables upfront uses up all their names in advance, forcing you to come up with unique names for objects that have the same conceptual role. 5. Readability. By having the variable declared upfront, you signal to whoever's reading your code that it'll be used everywhere. C++ programmers expect variables to be tightly scoped and a break from convention is surprising. Ultimately, C++'s whole ethos is centered on the idea that object lifetimes are tied to lexical scope. If you work with this ethos instead of fighting against it you'll find you'll have a much better time.
cool, now that i know that i can barely comprehend 80% of these nooby mistakes, coding feels way harder than before to me. i guess thats more of a good thing.
Ive been programming on c++ alot lately and this tips are soooo helpfull, I do make alot of newbie mistakes so thank you for making this. Im waiting for a part 2!
A compiler's optimiser will evaluate a constant expression at compile time regardless whether it is marked constexpr or not. Constexpr does 2 things: 1) Check if your assumption that this could be done at compile time is true (throws a compilation error otherwise) and 2) allows you to then use this function in a place where only compile time expressions are allowed.
This is really nice to know. I haven't coded in C++ for quite some time, but back then, even though C++11 was already out, we weren't allowed to use it in the bank I was working at. Almost a decade afterwards, I see that C++ has evolved quite a bit, and seems really nice!
One thing I might note is that while using smart pointers is better, I think learners of the language should learn what new and delete do in the beginning to see how it works.
Will you do one of these for JavaScript? Here are some ideas off the top of my head: - End each line with a semicolon. VSCode has an option to insert semicolons automatically on file save. - Ditch == and !=. If you want to compare a string and a number, cast one to the type of the other and compare them with === or !==. - Declare your variables with `const` and `let`. Ditch `var`. - Use `const` rather than `let` if you're never going to reassign the variable. - Prefer template literals over string concatenation. - Don't define all your functions in one file; use modules. - Don't pollute the global scope. Prefer local variables. - Avoid if-else hell; use switches when you can. - Avoid if-else hell; use early returns. - Avoid callback hell; use async/await. - Simple callbacks should be written as arrow functions. `array.every(num => num % 2 === 0)` reads better than `array.every(function(num) { return num % 2 === 0; })`. I'd argue it's better to write all callbacks as arrow functions but that's just my opinion. - Destructure arrays and objects when needed. Write `const { username } = req.body;` rather than `const username = req.body.username;`. - When a function has many parameters, replace them with a destructured object so you can pass in arguments in any order. - Prefer for...of-loops over traditional for-loops. Write `for (const item of array)` rather than `for (let i = 0; i < array.length; i++)`, especially if you don't need i. - Document your code either with JSDoc for vanilla JS or explicit types for TypeScript. - Avoid the `argument` keyword in favor of rest parameters. - Instead of filtering an array and then mapping it, which involves iterating over it twice, use `reduce` to do it all in a single iteration. - The `includes` method is there to tell you if an array contains a given element or if a string contains a given substring. Stop writing `array.indexOf(value) > -1` like they did in the old days. - Don't add methods to the built-in constructors. This could break your code in the future. Define classes that extend them instead. - Don't use HTML attributes for event listeners, that's an old Internet Explorer practice we don't need to follow anymore. Have all your event listeners in your JS file and add them to your elements using the `addEventListener` method.
For 5:04, you want to explicitly mark the type of triangle_n as constexpr so you can guarantee the function will be evaluated in a compile-time context. Additionally, you can mark the function as consteval so the function must _always_ evaluate in a compile-time context, giving an error whenever it's only possible to do so at runtime.
I would add the following points: - using macros for things that can be done with normal variables/functions - using pointers where a reference could be used instead - passing smart pointers where no ownership is being transferred - not following "include what you use" - writing code where include order matters
Include order matters wherever there's multiple classes that rely on each other, it's very exhausting and I'm genuinely wondering what I'm supposed to do
@@vafasadrif12 The best practice is to explicitly include everything in a file that is used by code in that file. This makes headers self-contained so that the order in which you include them doesn't matter. If they share a common dependency, it will be brought in by the first file that includes it and used by every other file included later. This only is a little more challenging when you have a cyclic dependency between files, for example class A uses class B and class B uses class A. This can be solved by moving the implementations of these classes to source files and only keeping the class definitions in the respective header files. Then you can forward declare A in the header of B and include A in the source file of B and vice versa for A. Of course this means that you can only use the forward declared type in the headers of A and B, so for example class A can not have a class member variable of type B. If you need this you would have to put them into a unique_ptr. Another way to resolve this is to create an interface for A for example and then refer to the interface in B instead of A directly. Note though that if you come across this problem you should carefully examine the design of your code. Unless classes A and B represent a data structure that has cyclic dependencies by design, for example nodes and edges in a graph that refer to each other, having cyclic dependencies between classes is an indicator of bad design.
I'm somewhat proud that I knew almost everything you mentioned. The one exception is the structured bindings stuff. And now all I can think of is all the times I was annoyed that such feature doesn't exist when in reality it does ( I really like python-style stuff that gets added to C++ ). I feel like you can count on one hand the people who know the full syntax of C++. Once every few months I find that there is some new thing that has existed in this language for years that I hadn't encountered before. The one part that I would disagree with you is reinterpreting variables. The C++ standard may not guarantee certain rules about byte order but 99% of the time the hardware people use does. And the 1% is when working with embeded stuff in weird hardware. Bit hacks can give you huge performance advantage so when that's the case I would use them. But I tend to test things either way because often the compiler or hardware has a better way of doing it.
Honestly gratz, even I fall for these things on a bad day! I'm not going to say you _can't_ find a use where reinterpret_cast is the best choice, but noobs really tend to waaaay overuse it when it is not at all needed. And I totally agree that bit hacks can make a huge performance difference in critical places. However, reinterpret_cast is generally not needed (nor recommended) to do bit hacks. As of C++20 nearly all bit hacks can be done safely with std::bit_cast. Just cast any object to a std::array of bytes of the same size (as shown in this video) and you can operate on them however you like. Prior to C++20, the standard-blessed way to do bit hacks is using memcpy (this is explicitly blessed by both the C++ and C standards). I know people worry about not wanting to make a "copy", but this use case is so common that if you use memcpy no reasonable compiler will actually make a copy in a situation where you replace a reinterpret_cast. It will recognize you are trying to do the safe version of reinterpret_cast and will elide the copy. As always, you should measure the performance and check the assembly yourself in either case. But really check out bit_cast if you have the fortune of using C++20!
@@mCoding Well I don't "fall" for these but I tend to knowingly do some of these when I'm writing a small quick program. But for larger projects it seems like good coding style saves me a lot of time since I make much fewer mistakes. And yeah, I'm not using C++20 yet.
The thing about the reinterpret_cast, it is literally undefined behaviour because it returns 0 with any compiler optimization level turned on. If you use -O0, then yes use reinterpret_cast but nobody does that so just use memcpy or the c++20 function which uses memcpy. The compiler knows what you're trying to do and doesn't actually make a copy. You can look up the strict-aliasing rule which is about this exact topic.
@GalaxyShard Yes, it works if you cast a pointer to char * or std::byte * (and a few similar types). Or cast any of those types into any pointer, but all other casts using reinterpret_cast are undefined behaviour. And even those casts aren't undefined behaviour but rather the reading/writing from the resulting pointer is.
Avoid manual memory/resource management (unless you have a good reason to), use const when you can and the standard library containers and algorithms when you can and you'll mostly be fine 😀 something that is not mentioned here though and that is even worse is over relying on performance of standard library functionality - make sure you understand the cost of using the standard library methods, dont just assume that they will scale well
Bro I opened the video thinking I'm about to be so happy for knowing all of it. Ended up feeling so bad cause I just knew the first couple of things lmao. I wish I could continue C++ I remember I loved it so much in uni. I still have that book CPP by Deitel that's one of the few books that built my foundation of programming. Thank you so much for the video man. ♥️
About the 30th item. Look at what is to the left of the * const int * prt -> the thing that is const is the int (const int) is to the left of the star int const * ptr -> same as above, the value at the memory that ptr points to cannot be changed int * const ptr -> the pointer is const, meaning the int itself at the location can be changed const int * const ptr -> both the pointer, and the value that the pointer points to cannot be changed
This is great. Modern C++ tips for noobs. I haven't done a whole lot of modern C++ (my C++ typically consists of raw pointers, new, and delete) but I've done rust, and a lot of stuff in this video has parallels or even stronger implications in rust. I'll definitely start using those smart pointers from now on because they'll make my C++ more like rust
Like what? Rust prevents you from writing most of these mistakes at compile time automatically or by not having so complex language features to begin with.
THANK YOU! for making it easy to remember about const and pointer thing, being a self-taught programmer with few years in C++, I found atleast 30-40% stuff that's new to me.
Yes, I tried to mention "before/after C++XX" to indicate when each feature was introduced (though I'm sure I missed some). Structured bindings are C++17.
Yes. Not using range based for loop isn't a sign of being a beginner, as it didn't exist before. So it means the opposite, that you learned the language a long time ago. That also applies to lambda functions, bit_cast, structured bindings, constexpr, override, smart pointers, make_unique.
#21 is complete insanity. I am so glad I gave up on c++ long ago. I will only resort to using this language if I actually care about speed...which is almost never, since everything I end up writing gets barely used anyway XD
You can find many of those unfortunate C++ habits (programmer errors) also in aged professional C++ code bases: like type casting from base class to a derived class, or non virtual dtor even which may not be called when instance is used in a hierarchical class library with base classes dtor called.
@@NuggetInAJar i don't know, seems like if you want to use polymorphism then there will be at least a few cases where this is necessary. I haven't written OOP code in a while but i remember doing this a couple times
I wish this list was around when I was learning, good stuff! One tip: an easier way to remember constness is to read the type backwards. So “const int *ptr1” can be read as ptr1 is a pointer of an integer that is constant. Or “int *const ptr3” can be read as ptr3 is a const pointer to an integer.
One can argue, this is objectively incorrect as the space should be b/w the type and the variable name, and the type is pointer-to-something. The type is simply made-up of two entities.
@@YourCRTube Not objectively, you can't. Because int* x, y; declares an int pointer and an int, whether or not there is a space between int and '*', not two int pointers like: int *x, *y; Argue about whether or not you should declare two variables on the same line, sure, but it's a strong argument. However: int const * const x; const int * const y; You simply can't marry the '*' to the var name here if the _pointer_ has to be const. This is an awfully strong argument that there should be a space on each side of the '*', as much as it pains me to say so. My major complaint there is that it makes it feel orphaned and reads too much like '*' as multiplication.
@@YourCRTube "objectively incorrect"? I know this is programming but it's still a 'language', the only rule is "does it compile?" Everything else is categorically subjective.
Bonus point about tip 12: with std::tuple you can basically almost have multiple return values using structured bindings without even needing to declare your own struct std::tuple Deez() { return { 0, "Deez", 1 }; } auto [x, y, z] = Deez();
Yeah but then the function signature and what you're actually returning becomes unreadable ^^''' I'd rather declare a struct for this with explicitly named member variables than a random tuple
Very good summary of some of the fundamental c++ features. Unfortunately, it takes sooo long, until the new standards trickle through to the industry. Most companies haven't even adopted c++17.
As a long-time C++ developer and teacher, I can honestly say that my n00b score (by this video's standards, at least) was 0. But I see you also have a version for Python; that should be interesting. I'll be happy if my score there is under 5. Edited to add: Yeah, I didn't really keep score, but I'm sure there were at least 10 things on the Python list I was unaware of. They're mostly things I don't get into in the Intro to Programming course I teach, but they're still things I really should have been aware of anyway.
When I got back into C++ 6 years ago, I think I did most of these. I've since gotten better, but there were 2 or 3 I watched twice. Thank you for the video.
Might be helpful to mention that some of these things are not available by default in C++, but were implemented in a later standard (and hence you need to compile under that standard or higher in order to have access to them). Range-loops, constexpr, unique and shared pointers require C++11, structured bindings require C++17.
Our professor at master degree in computer science that explained us c++ (and multimedia data processing) has teaches us all in standard c++17, and some c++20 like ranges, format and some modules. Unfortunately not explained us concepts but still a very updated course, because even if you study alone, you don’t want to study after c++20 like the 23, because no compiler has supported yet, and not will be supported for at least other 1 or 2 years so
there's always this misunderstanding that "undefined behavior" means "anything can happen" but that's not really what it means. it just means that the standard itself provides no implementation details for the behavior. you can still know what WILL happen under certain circumstances. where it becomes a problem is past a certain point, your code's complexity gets to a degree that you _probably wont_ know what will happen if you use a lot of undefined behavior, or if the behavior may change from OS to platform to implementation (and whatever-else weird in-betweens you have based on how you are compiling) which is usually why undefined behavior exists. in some cases, undefined behavior doesnt change in those situations and are undefined behaviors for reasons that dont affect you at all. sometimes, things really are just undefined behavior because the _standard_ doesnt provide implementation details and that's it, but that the way it is implemented is still done the same way practically everywhere (such as casting ints to chars in C). another reason why undefined behavior exists is because there is a problem that has multiple solutions that are all valid.
for "std::endl" i would say it is the exact opposite. Printing like this is done usually for one of 2 reasons: 1 - user interactions. You tell the user what is going on and ask him for an input 2 - tracing - you wanna see what the program did. In the first case the performance is a complete non-issue, in the second case NOT using "std::endl" would be moronic cause in the case of an error you have no idea when it happend as you did not flush the buffer, not even considering yet that your claim about it taking extra time is actually not really that correct. Often saw people try to be "clever" and avoid the supposed performance-impact of flushing that later get frustrated cause they can not find out what went wrong cause their logging is not showing them what is really going on. People think/claim this makes such a large difference yet don't even know that 'sync_with_stdio' is a thing - by default the I/O-buffer used by std::cout is synchronised with the buffer of the old c-streams. This alone has far bigger of an impact than std::endl vs . If flushign the buffer makes ANY relevant performance difference then you are well beyond newbie-territory and at that point you really should know a lot more about the standard behaviour of streams and their buffers. Structured binding - that is still relatively knew and very often simply not available. Nearly no business-software created before 2018 will even use C++17, often still c++11. Upgrading your entire platform on an existing codebase is rather laborious. Right now i am glad that we can use C++17 in most of our current projects, but in some i am still restricted to 14 or 11, really looking forward to C++20 with the easier string-formatting.
I'm hoping to learn C++ and have a lot of experience in other languages, so videos like this which point out newbie mistakes are total gold. Thank you!
I don't like C++ particularly (I think its strengths are rarely worth the downsides), but I think most advice in this video is really good. The only thing I feel unsure about are the structured binding. Since they operate on declaration order, they depend on an implementation detail that isn't necessarily stable, and I think that's worth keeping in mind when considering their use in larger projects.
exactly what i was thinking when this was mentioned. there is generally no reason to assume that the declaration order of class variables has any effect on program logic. and unless IDEs/compilers have advanced functionality I'm not aware of, there isn't a good automated way to catch the class of bugs that reordering declarations could cause
I haven't used C++ since 2003, my mind was absolutely blown away by this video. I had no idea how far C++ had caught up with higher level languages in expressiveness and productivity since then. This is an absolutely mind-blowing, and makes me want to make a deeper dive back to C++ to see just good it has become. ps. please understand I do not mean speed, power or flexibility when I talk about the improvements above. Also, absolutely amazingly well done video in delivery
Yes c++ is evolving quickly, and it's actually still part of the original vision of Bjarne (see my interview with him). Amazing how his vision of c++ included things that are only now being implemented in 20 and 23. So much to learn!
@@mCoding I actually saw it and bought his book then, but hadn't had time to go through it. I still had no idea it had evolved this direction. Thank you so much for making this sort of great content, I constantly keep learning new ways to approach things from your videos, they are a very satifying mix of very advanced optimization tricks and just very well done comprehensive tutorials, that actually explain and teach reasoning behind design/technique decisions, so unlike in many other tutorials, that gained knowledge very easy to transfer to other projects due to helping us understand the techniques well enough to be able to adjust them to our needs
It would be great if we could fix the verbose syntax of c++ to be more similar to Python. I would love Python-type syntax with the compiled performance of c++.
This makes me realize how little C++ I know. Like for instance, the part with "std::ifstream input{name}" or any use of "{}" for initialization seems like wizardry (and frankly quite opaque) to me.
Unless in cases like std::sort that will beat almost any clever algorithm you can come up with, I really hate the idea of dogmatic following of "idiomatic" code which is just using a standard library item no matter how much it makes your code more unreadable. The std::find_if function makes it bigger and harder to read, makes it look like the code is doing something heavy/complex which it isn't.
Your code will be more expressive (easier to read) if you use algorithms because they have name, a raw loop don't have name. It doesn't do any complex, you pretty much read what it does.
@@tsunekakou1275 I disagree. A simple small raw for-loop doesn't need a name. Tell me if for (auto elem : v) { sum += elem; } is somehow more clear and expressive than std::for_each(v.cbegin(), v.cend(), [&](int em) { sum += elem; }) // I don't know if an exact API exists like this, this is just an example.
@@VivekYadav-ds8oz your example is a bit "wrong", you just have to find a better name that describes the purpose of the loop, accumulate perhaps?, the "correct" function for this problem is int sum = std::accumulate(v.cbegin(), v.cend(), 0); which is really easy to read. you can write your own. if you don't think naming things make it easier to read then aren't anything to talk about. I can't prove to you that named loop is easier to read than a raw loop, and you can't prove otherwise. Maybe someone already have done the reasearch, maybe you can find those. std::for_each is just for side-effect, for example: std::for_each(names.cbegin(), names.cend(), [](const auto& name) { std::cout
Thank you, great video! Especially enlightening for me was the description of structured bindings; I see `first` and `second` used all over the place in bitcoin core. I knew item 30 (const ptr versus ptr to const) from many years of C programming; the mental model I used is: if the `const` is to the left of the `*`, then the thing pointed to is const; if it's to the right, then the pointer itself is const. And it can make sense to have both! `const S * const p` means that what p points to can't change, nor can p itself. Or even `const S * const * const p` (p is a pointer to a pointer to S; none of these 3 things can change). Another thing people often don't realize is: don't write `int * const p` in a declaration (header file), just write `int *p`, even if the function definition (implementation) is `int * const p`. This is because p being immutable is an implementation detail of the function, not part of its interface.
I'm no C++ guru (I'm not even a software engineer.) but if I was writing a library and sending a header file to someone I think it would be ideal to let them know their variable will not change in any way; give no surprises. I would lean towards `const T *const foo` in the header file to let the reader know. This seems at least conceptually doing the right thing, but maybe I'm missing something here. (That and it's more explicit to use an Optional than a raw pointer unless interfacing with C or similar so this scenario would be pretty rare I'd think.) Structured bindings popped up I believe in C++17, and bitcoin is from pre C++11 compiler support so I imagine it is more legacy code than anything and could be updated.
Hi @@Danielle_1234 - "I think it would be ideal to let them know their variable will not change in any way" -- Yes, but the "const" in "int * const p" or "int const n" in a function argument list does not do that. Values are passed by value in C and C++, so the called function has its own copy of the argument. It's impossible for the called function to change the variable in the caller's context. So the "const" in these cases in a header file are pure noise, just clutter. (Footnote: in C++ there is a call by reference option, indicated by "&", but I'm not referring to that here.) With an argument declared like "int * p", the p is still passed by value (called function has a separate copy), but the value being passed is a *pointer* to a variable in the caller's context. So the caller *can* modify it -- which, as you say, is very useful for the caller to know! If the declaration is changed to "const int * p" (or "int const * p", which has identical meaning), then the compiler won't allow the called function to modify what p points to. This is also very useful for the caller to know, and even a restriction that the caller may want to impose on the called function. So "const" in these cases (when it's to the left of a "*") definitely does need to be in the header file. But not in those first cases I mentioned.
C++ noob, here. Lately, I've been trying to avoid writing my own functions when one already exists. But a lot of times, it takes me longer to find one (or to understand the docs for it) than it does to write one that has the exact I/O that I want. Obviously, I'm not going to write one that's as efficient or safe as I would probably find, but I'm also not typically contributing to a shared code base or writing production code. Mine's generally one-off code for my research. Advice?
the more times you search for std function the higher chances you would remember in the future without much effort! in the long run then would be less time wasted
Indeed, eventually you will learn the most common std algorithms, at which point they will become basic building blocks for your code. You will be able to think in terms of algorithms instead of variables and loops. My advice would be to power through the learning phase even if it takes a bit longer. The algorithms have a lot in common are become easier to remember the more of them you know.
I would still not advise you to write out your own versions. Why? Well,first, there are only about 80-90 functions defined in std::algorithm. It is only going to take you maybe an hour or two to go through each one and understand what it's supposed to do.(Most of them are not even complicated) Two, cranking out your own versions is not going to help you if at one point in time your going to use C++ professionally since at that time, you'll need to learn the ones defined by the standard. So it's better if you start using them now and get some practice before you start using C++ at a professional capacity
This is perfectly fine as long as you leave comments explaining the intent, to make refactoring easier. Especially since still learning. Maybe later you will find an entirely different approach altogether. Do not worship STL. It’s performance/usability aren’t perfect. Okay for most use cases though. But if you are serious about perf, you will always end up with custom containers and algos. I worked with some shitty devs who knew STL algorithms very well :)
I'm a student and we're forbidden to use some standard algorithms and several libraries. For example we can't use vector in our code. Isn't it a bit strange and stupid?
Another way to remember the difference between const pointer and pointer to const is to remember that the pointer attaches to the variable, not the type, so to have a pointer to const you need to have the const between the asterisk and the name.
Awesome video. I know some people with years and years of experience who keep doing some of these. Especially returning by parameter and move-return. I've seen a code base where literally every function had a 'return std::move'. 'constexpr' in the case you showed would most probably not do anything as long as you have a good compiler and some level of optimization enabled. 'constexpr' / 'consteval' is more useful when you have to use the result of the function in a template parameter or another compile time thing. The magic numbers one is true for virtually every language XD Still technically counts, though, so good one. The cases where you pass a pointer into the function could be a reference instead, so there's no need to worry about ownership or null. If you want to be able to pass a null to mean nothing, then use std::optional. I can't think of a reason to use raw pointer, except for interop with C.
Raw pointers are better than std optional, to represent a maybe-null reference to a single large object without copying it. However, I often find myself with unique_ptr function parameters that are expected to be non-null, and the variable will be moved out of later, so the same sort of issue is still present in modern C++.
There is a lot of complexity in C++ for sure, but most of it is for backwards compatibility. If you start with a fresh codebase using C++20, there is a quite small and elegant subset of the language that you actually need to use to do almost anything. Give it a shot!
This kind of video for Python: "Haha just use for loops silly :)" This kind of video for C++: *pain and suffering in endless standards and hidden overheads*
And then you have to use a compiler that is only capable of C++ 11 or so. And you basically can not do any of those tricks :) µC development is great, especially with bigger systems where you actually have enough memory to use dynamic allocation.
I was starting off like "let's listen to this guy, time for me to stop being nooby!" *doesn't even understand 25 of the 33 habits* Such a great video, the bits are even joined together by context!
Wow, this video is simply excellent, the tips you gave ranged from basic things to more advanced stuff and I'm thankful to you for learning new stuff I didn't really know or understand.
Nice video! I also noticed that you used const when passing an argument to the function by value like void foo(const int x), is it good practice? The outer value of x won't be modified anyway. There is also a good tip which was not mentioned in this video: passing arguments to a class constructor by value and moving them, rather than passing by const reference and copying
It is true that const in that place does not matter to the caller, but it does matter to the callee. Just like marking a variable declared within the function const, marking a value parameter const still prevents you from accidentally modifying the value in your function and signals this intention to anyone reading the code, making it easier to follow.
As someone who learned Python on my own after learning C++11 in college and not being allowed to use almost anything in namespace::std, watching this has made me realize that C++ has a lot of the convenience features that I've been learning in Python. It seems like a lot of example C++ code online doesn't seem to use them as much as Python devs do.
Are those languages considerably simpler? I don't know any of them but, for the languages I do know, beyond the basics there are always intricacies that are not apparent at first glance. If you know all of them, which would you recommend I look into first?
I think the point is that (with Rust at least. I'm not familiar with zig and nim and roc hasn't been released yet) most of the gotchas in this video are rendered moot in other languages, either by being impossible or disallowed by the compiler without explicit escape hatches.
@@TAP7a I tend to agree. Although, I must say that with LLVM, LSP, tree sitter etc., it has become much easier to create new programming languages *and* tooling, so I wouldn't discount them as easily anymore.
@@mCoding Every language has complexities, but C++ has a lot of _unnecessary_ complexities due to design flaws and legacy baggage. E.g., argument-dependent lookup has complex rules and is difficult to reason about. More recent languages have the benefit of decades of development of programming language theory as well as practical knowledge, avoiding design pitfalls. In particular, languages whose design is based on a more rigorous theoretical framework are better able to provide _orthogonal_ language features that avoid corner cases when they interact, compared to languages that evolved with ad-hoc additions. A blatant failure of orthogonality in C++: it implodes if you pass a multi-arg template into a preprocessor macro, because the C preprocessor doesn't handle commas inside angle brackets. Compare C++'s three incompatible, Turing-complete metaprogramming systems (C preprocessor, templates, constexpr functions) with the constrained, hygienic macro systems in more recent languages.
I was just looking at an old C++ textbook and thinking, yeah, I can probably understand most of this readily enough... And then I come here, and almost all of it is very, very whoosh.
1. Using std::string, using std::cout ect. has the same drawback as using namespace std. You are make a choice for someone that 'string' will refer to 'std::string' and cout will refer to std::cout. You are substituting one evil with another, instead of solving it. If it's domestic code, don't be afraid of using namespace std. If you'll use 'using std::something;' instead, you'll end up using all of the most populare std names in your headers anyway, and all your header files will begin with a screen of text telling "using std::this; using std::that; using std::those_two; using std::half_of_the_rest_library". This is not what you want. If it's a public library code than use a library namespace, otherwise you are doomed to use std qualifier before every single std name. 3. Don't use 'auto' keyword without a purpose. The 'auto' keyword was designed to solve certain kind of problems. The given example is not in the list. Using 'auto' without a purpose makes your code hard to read. Use auto in general code (templates) or when type referencing is tedious (i.e. typename std::unordered_map::const_iterator). 5. There is nothing wrong in using C arrays. Using a core part of a language can not be wrong. There is std::span to solve the problem with function parameters. 6. Regarding the famouse Quake 3 code. Note that C style casting converts one type of pointer to another type of pointer. This is not UB. UB is dereferencing it because it violates type aliasing rules. 12. If you want to return multiple parameters, use std::tuple instead. It helps to avoid unnecessary struct declaration. Overall good tips.
1. No; the problem with `using namespace` is that it pollutes the global namespace with any symbol that ever gets added to the namespace, harming maintainability. Together with ADL it becomes very complicated to reason about. 5. I disagree. The semantics of C arrays is so broken (e.g. magic pointer decay) that the C++ committee decided to add a library to replace it.
@@fat_pigeon I have a file that starts with two screens of 'using std:something'. Two whole screens becuase when the project started I decided to not embed 'std' in the global namesapce. "it pollutes the global namespace", I though. Now 'pollution' is not in the global namespace, now pollution is in every header while. My strong suggestion now is to use 'using namespace std;', and forget about hypothetic collisions that can be resolved in a matter of 10 seconds.
@@sheeftz Namespace collisions can easily cause "action at a distance" that take much longer than 10 seconds to debug, especially in large codebases. Imagine you write a file in your library that defines a function called (say) `async`. Now, your code works, but tomorrow, a coworker in a different department changes an unrelated library to include the `` header, which defines `template std::future std::async(X&& x, Y&&... y)`. Result: someone in a third department complains that you broke the build, because both libraries happen to be included by their file, and the compiler reports the ambiguous overload `async` in your code.
5. Arrays in C are horribly broken to the point where they're just not worth using at all. Implicit conversions are bad as is, converting it to a type that has an entirely different arithmetic is even worse. 12. Problem with tuples for this purpose is kinda the same issue you can get with auto, it removes the clarity of the code very quickly as you're no longer aware what something represents, creating an aggregate solves this relatively smoothly.
I'd say that most of provided "habits" can be sometimes used depending on the curcumstances. For example, C to C++ interop makes use of C++ features much harder if even possible. From my experience (i've started at C++99 era) what i sometimes have to use: 5. For me C-style arrays are just more readable than std::array. 6. There were cases when C-style casts can do things that other casts can't in any combination. 8. Provided solution does not valid in the nothrow environment. 13. Agree, unless it's a part of the code that referenced from multiple places and can't be placed into header.
I'd just like to point out for the actually noobies to c++ that a lot of these, if not all, do have times when they aren't applicable as shown in the video. Most of those reasons would be to do with performance constraints or memory layout, neither of which is something your likely to worry about as a beginner, so while your learning the language and/or programming in general, these are good to get you going, but don't consider them hard rules that must always happen. Unfortunately knowing when to bend these rules is something you can only learn with experience.
Regarding undefined behavior: IMO if all code written so far relied purely on the C/C++ standard without using any UB (read: platform-specific behavior), we would not be able to create any useful software at all
C++ has been greatly improved over the years but it still looks like barbed wire over a minefield to a java developer. I don't think I could use this language
Fair enough! But I do want to say that C++ is jagged because of its history and commitment to not breaking legacy code. If you come in with the mindset of following best practices of modern C++, I think you will find the language has simplified drastically.
About pointer and const: read the type from right to left. "const int*" is "pointer to integer constant". "int* const" is "constant pointer to integer".
They are extremely rare to the point of vanishing, though. Since you can get the underlying data from both std::array and std::vector, and they are guaranteed to be contiguous, you can use them and use data() to get a pointer for C-style calls. If you are so memory limited that the extra 24 bytes of memory is a problem, then sure, use raw arrays. But then, maybe don't use C++ at all, just plain C.
They actually dont. You get the same compatility by being able to explicitly decay them. And they work in constexpr contexts. There's not a single legitimate reason for using C styled arrays.
@@valizeth4073 actually C-style arrays are safe to pass to dynamic libraries because C's ABI doesn't change much. Vectors meanwhile can't be passed safely unless you use the same version of the compiler the program was compiled with and the same flags used to compile the program.
@@valizeth4073 From the standard's perspective (which is used to decide what features make it into C++), yes, but in practice no. The only compiler to keep ABI stability is MSVC, and that comes at the cost of features (Jason Turner explains this in his video on how the ABI should be broken again (th-cam.com/video/By7b19YIv8Q/w-d-xo.html), 16:50 is where he brings up an example of a C++ feature that wasn't supported at the time due to ABI breakage concerns).
Using new and delete is not a nooby habit. Smart pointers come with overhead at both compile and run times. As long as your new and deletes only appear in constructors, destructors, and functions that reallocate for additional memory, they won’t cause problems with exceptions.
@@mCoding read what I wrote again and then read what you just sent me. In the context I am talking about (constructors and destructors), there is no ambiguity about when new and delete need to be called. This "rule" about not using naked pointers and new and delete is more of a rule of thumb to prevent newbs from creating memory leaks. If an object maintains proper ownership of its allocations, it doesn't need an extra layer of safety with smart pointers. You do realize that smart pointers are implemented with new and delete right? Maybe unique_ptr has a bug! Oh no! How do we proceed?
I used to think I learned C++ in college. I realize now it was C with classes
That’s even better than C++ lmao
@@catsby9051 wut?
@@mario_luis_dev C++ is an awful language. It’s way too complicated, compilation is incredibly slow, there a many ways of achieving the same thing. ABI instability is a huge problem that basically renders the STL useless for library development. Undefined behavior makes it hard to reason about what your compiled code will actually do. I could go on and on.
@@catsby9051 yes. Modern C++ is pure shit
@@catsby9051 lol it’s not the language’s fault if you don’t have the skills….C++ is the king of all languages, if you know how to use it properly.
The main thing I took away from this video is that I have no idea how modern C++ works. Classic mCoding. Keep up the great work!
I will keep it up! But also don't be afraid to look up some modern C++. The language has actually simplified quite a bit if you avoid a lot of legacy functionality. I'd recommend looking up the C++ core guidelines as a great starting point!
I've used c++ for around 3-4 years for competitive programming and messing around a bit with opengl, but had no idea about anything from 22 onwards! I need to brush up on modern c++!
@@mCoding Smart pointers are amazing allow me to be all nooby forever 😂
Me too
@@ron0studios modern c++ is bloat and ugly, there's a reason highly performance intensive industries don't use most of that stuff, because only a select few sane enough features of modern c++ are actually useful. I hope you gave up on your journey to "brushing up on your modern c++", because it will only make you a worse programmer. You already be aware of all of this tho, specially if it is true that you've been into competitive programming and doing graphics programming....
I only use 1% of C++ I’m pretty sure 😂
Which is the best thing you can do, as most serious projects in C++ limit the set of features you're allowed to use.
Meh, this wasn't really about C++, it was way more focused on STL... :-
12:20 make_shared also allocates the resource and the control block together, making it much faster (and cache friendly) than directly constructing a shared pointer
Good point! You may subtract one from your noob score :)
@@mCoding hopefully it's not stored as unsigned
Also it will deallocate memory if an exception is thrown by the constructor.
@@yxlxfxf That was a good one
@@yxlxfxf very nice response
An addition to the last comment about pointers coming from C libraries: Many C libraries that expect the caller to free the object often also provide their own "free" function for that specific object type. So the solution is to make your custom deleter function call that special "free" function. Also remember that the custom deleter function, which is passed as a template parameter to unique_ptr, can be a lambda too.
Excellent advice! The idea is basically the same as malloc vs free. If you called a C library library_alloc_whatever_handle then you can follow the same pattern as in the video (or use a lambda) to make sure your unique_ptr calls the library_free_whatever_handle (the names of the alloc/dealloc functions should be documented).
Thank you so much, both of you! I moved from being a bare metal C developer to working on C++ Applications recently. I still call C libraries for hardware interfacing and I am racked with guilt for every bare pointer I juggle from the libs. This advice is golden and will save me a lot of lost sleep!
The problem with C++ is that you are spending more time writing and debugging “safe” but messy wrappers for already existing C++ libraries than it would take to write and debug some clean C :)
@@noop9k I am not sure what you're talking about. You do realize that there are excellent C++ libs out there, like Qt and Boost, right? Or are you talking about C libs that have half-baked C++ wrappers? Even in that case, it still makes more sense to use the C api but wrapped in a safe way: raw pointers -> unique_ptr with custom deleter. It is more memory safe that way. You don't need to worry about explicitly freeing the pointer at every possible exit point (when going out of scope).
@@sledgex9 Good for you :)
PLEASE DO NOT STOP MAKING THESE VIDEOS, youtube was really lacking these proper advanced(? at least for me) tutorials on programming.
I am learning to code for ~3 years now, and I have been looking too learn more advanced stuff multiple times, you are the holy grail.
I totally read that as "please stop" and then something to do with the youtube algorithm. So I was like, "oh this nutty comment is going to be good" XD
Also check out the Scott Meyer's books, Effective C++, and his subsequent ones. The first is still my favorite, but all of them are great
This is really valuable content, thank you!
I think I managed to pin down what makes this really nice. This isn't necessarily the kind of advice that you'd get by reading a book (or at least not the books I read). I learned this kind of stuff mostly by "folklore", i.e. some more experienced developer told me to do things this way because it's neater. The value of this stuff is that you're making this advice accessible to everyone, not just those with senior mentors! :D
I really appreciate that
Thanks so much for the kind words! I do my best to be a good mentor.
Yes, I couldn't agree more!
Then you haven't read the right books imo
Any book by Scott Meyers, recent book by Jason Turner (and his yt channel). Nicolas Josuttis' books are also full of gotcha's and how to deal with them, I really like his books, I think they made me a much better C++ programmer. Then there are the cpp core guidelines and clang-tidy (a static checker that checks for some of the issues in this list and makes sugstions). And I'm sure there are more.
I mean, this is a good list, very condense with the correct argumentation, but this information is not just 'tribal knowledge'
Now that you said it I realised that's how I learned most of those things.
Everyone says find a mentor
But they don’t grow on trees what kind companies y’all work at
Must be nice
I've been a professional developer, mostly in C++, for over a decade, and I _still_ sometimes unintentionally roll my own when a standard algorithm would have been better because there's just a bunch of them that don't come up very often and they're easy to forget.
I learned SO MUCH with this video. I started with python as my first language, but now I'm switching to C and C++ because I decided that I want to work with embedded systems. Also, I watch your videos since long time, keep what you're doing, your work is awesome.
Thanks! I'll probably make a C habits video sometime too!
I don't even know C++ but I'mma watch 🤣
This video showed me that i don't know C++ 🤣
That's the spirit 🤗
Same here
I did a single C++ course at uni ~2 years ago and do not intend to ever write C++ code again. Video was still cool to watch
@@arib9877 same
I am a noob myself, but I think another common nooby habit that's worth mentioning is using push_back instead of emplace_back when putting a newly created object in a container right away
That will definitely be on the list if I make another C++ nooby habits video!
@Jacob Sorensen I specifically said "newly created object ... right away" (temporary object that is). emplace_back creates an object with the provided arguments in-place, whereas if you create an object and push_back it immediately, you get one unnecessary copy/move. However, in most cases compiler will probably optimize it
push_back copies the object. emplace_back calls the constructor (like make_unique). If you pass a fully constructed object to emplace_back it'll have to use the copy constructor (or the move constructor if available). You want to pass instead the constructor arguments to emplace_back. Otherwise, just use push_back to make clear that you know you are copying there.
If your language makes you think about this then it's a bad language
@@danielwappner1035 not really. Copy vs. move semantics matter for high performance, low latency applications or in constrained environments (e.g. embedded systems).
The point about "raw pointers aren't always bad" didn't use the best examples, because in both cases you could instead use references. That way you'd not have a raw pointer, *and* you also wouldn't have the unchecked dereferences in both functions.
There are situations where you might still want to use raw pointers, but that's where you're expecting null to be a possibility as well, which is only because you can't have std::optional (which some would argue is a bad decision to have made).
>which is only because you can't have std::optional (which some would argue is a bad decision to have made).
Took the words right out of my mouth! It's *really* hard to find a legitimate use for raw pointers.
@@Danielle_1234 "It's really hard to find a legitimate use for raw pointers"
It really isn't. The fact that references are immutable and cannot be rebound is a strength when you use them in, say, function parameters, but it causes a world of pain if you want to _store_ a reference to an object. This is because, like const members, a reference member will implicitly delete copy and move assignment, which makes said classes a lot less ergonomic to use. The solution to the const member problem is not to make the member const but to make it private; the solution to the reference member problem is to use a pointer.
Also, some codebases use the convention that function calls that mutate their arguments should take those arguments by pointer instead of by reference, which, if done consistently, makes it obvious at the call site that the argument will be mutated.
@isodoubIet In that particular case I would use a reference_wrapper if the member is not supposed to ever be null. I would only use a raw pointer if I expect to encounter or need the null state.
You should put the supported C++ version alongside it. As someone using C++ 11 in production for compatibility, a lot of these may be unavailable.
I personally believe you can switch to C++ 14/17 pretty safely.
I heard 14 is mostly widely used if it's not some legacy software.
C++11 is really bad, sad to hear
Hopefully you will migrate to cpp17 soon
Seems like you stuck with bjarne Stroustrup Cpp 11 book!
Noob? A lot of these tips use features that didn’t exist when the old folks learned C++
No issue whatsoever. Most of what he told are about new features of the STL, and using too much STL in your codebase is the most nooby thing you could ever do. It sacrifices all readability for the dev's laziness
It is their responsibility to keep up with the standard as it is updated. Regardless of age and experience, if someone is coding in C++98 when we have features through 23, they are a noob.
@@Anonymous-fr2opsounds like boomer copium to me
Looks like c++ made a lot of improvements in the newer releases
At last, a TH-cam video on C++ done by someone who actually knows modern C++. Thank you, thank you, thank you.
But I'd like to humble suggest a number 32: declaring all your variables at the top of a function. This is a holdover from old-fashioned C, and I still see it taught in some places. Don't declare a variable until you can initialize it with a meaningful value. (BTW, even C hasn't required this coding style for about 20 years.)
Great advice!
Some of these are more "oldie" than "newbie" habits.
NOTE: I'm not arguing against this, I'm just trying to understand the reasoning.
I've been told this before but what actually is the detriment? I learned on C so I've had that tendency. I don't really do it anymore but I see the readability benefit of knowing what variables you're using in a particular function by just looking at the top (not arguing that its best practice though), I've just never gotten a good answer as to why its bad. If I simply initialize something to null until I new it or give the constructor a default value and assign the values later (assuming I'm not trying to re-invoke the constructor and essentially initialize the variable twice) what would be the problem?
Maybe it frees up a tiny bit of CPU, but you lose style points lol
@@nightfox6738 There's a few reasons why it's bad.
1. You may not have a meaningful value to initialize the variable with. Sure, you can use null, but that forces you to use a pointer, and in C++ values are usually better. You could use a std::optional, but that'd be a little confusing. Ultimately, you're having to make weird tradeoffs and what you get really isn't worth it -- it's just trying to adhere to a convention from a different language, where it only existed because of compiler limitations anyway.
2. What if you make some changes to your code and accidentally introduce a condition where the initialization code never gets run? You'll be dereferencing a null pointer/accessing an empty optional.
3. Having the variable initialized at the top of the function means it exists throughout the whole function, which means you might use it again, and this introduces coupling = complexity + possible bugs.
4. Declaring all variables upfront uses up all their names in advance, forcing you to come up with unique names for objects that have the same conceptual role.
5. Readability. By having the variable declared upfront, you signal to whoever's reading your code that it'll be used everywhere. C++ programmers expect variables to be tightly scoped and a break from convention is surprising.
Ultimately, C++'s whole ethos is centered on the idea that object lifetimes are tied to lexical scope. If you work with this ethos instead of fighting against it you'll find you'll have a much better time.
cool, now that i know that i can barely comprehend 80% of these nooby mistakes, coding feels way harder than before to me. i guess thats more of a good thing.
Ive been programming on c++ alot lately and this tips are soooo helpfull, I do make alot of newbie mistakes so thank you for making this. Im waiting for a part 2!
You are so welcome! I wish you the best in your C++ journey. Hopefull I will find the time to make a part 2 eventually!
Exactly this! learning the best practices is easier if you know what's bad by contrast
A compiler's optimiser will evaluate a constant expression at compile time regardless whether it is marked constexpr or not. Constexpr does 2 things: 1) Check if your assumption that this could be done at compile time is true (throws a compilation error otherwise) and 2) allows you to then use this function in a place where only compile time expressions are allowed.
This is really nice to know. I haven't coded in C++ for quite some time, but back then, even though C++11 was already out, we weren't allowed to use it in the bank I was working at. Almost a decade afterwards, I see that C++ has evolved quite a bit, and seems really nice!
One thing I might note is that while using smart pointers is better, I think learners of the language should learn what new and delete do in the beginning to see how it works.
I agree, learners of the language should understand why smart pointers are useful, and that means knowing about new/delete, and malloc/free.
As well as cases (edge or not) where one would be justified in, or prefer, to use raw ptrs.
Will you do one of these for JavaScript? Here are some ideas off the top of my head:
- End each line with a semicolon. VSCode has an option to insert semicolons automatically on file save.
- Ditch == and !=. If you want to compare a string and a number, cast one to the type of the other and compare them with === or !==.
- Declare your variables with `const` and `let`. Ditch `var`.
- Use `const` rather than `let` if you're never going to reassign the variable.
- Prefer template literals over string concatenation.
- Don't define all your functions in one file; use modules.
- Don't pollute the global scope. Prefer local variables.
- Avoid if-else hell; use switches when you can.
- Avoid if-else hell; use early returns.
- Avoid callback hell; use async/await.
- Simple callbacks should be written as arrow functions. `array.every(num => num % 2 === 0)` reads better than `array.every(function(num) { return num % 2 === 0; })`. I'd argue it's better to write all callbacks as arrow functions but that's just my opinion.
- Destructure arrays and objects when needed. Write `const { username } = req.body;` rather than `const username = req.body.username;`.
- When a function has many parameters, replace them with a destructured object so you can pass in arguments in any order.
- Prefer for...of-loops over traditional for-loops. Write `for (const item of array)` rather than `for (let i = 0; i < array.length; i++)`, especially if you don't need i.
- Document your code either with JSDoc for vanilla JS or explicit types for TypeScript.
- Avoid the `argument` keyword in favor of rest parameters.
- Instead of filtering an array and then mapping it, which involves iterating over it twice, use `reduce` to do it all in a single iteration.
- The `includes` method is there to tell you if an array contains a given element or if a string contains a given substring. Stop writing `array.indexOf(value) > -1` like they did in the old days.
- Don't add methods to the built-in constructors. This could break your code in the future. Define classes that extend them instead.
- Don't use HTML attributes for event listeners, that's an old Internet Explorer practice we don't need to follow anymore. Have all your event listeners in your JS file and add them to your elements using the `addEventListener` method.
Sounds like _you_ should make that video. I'm not qualified to make a JS video.
@@mCoding Yeah if I had a programming channel I probably would x)
Write a medium article man, it can help a lot of people
I encountered almost none of these problems in my 40+ years career. Maybe my coworkers were professionals 😅
Finally someone who uses '
' instead of endl! Thank you!
And gives a compelling reason to use it too! :-)
For 5:04, you want to explicitly mark the type of triangle_n as constexpr so you can guarantee the function will be evaluated in a compile-time context. Additionally, you can mark the function as consteval so the function must _always_ evaluate in a compile-time context, giving an error whenever it's only possible to do so at runtime.
alt title: james calls me a noob in 31 ways
You didn't fall for all 31 did you!?
I would add the following points:
- using macros for things that can be done with normal variables/functions
- using pointers where a reference could be used instead
- passing smart pointers where no ownership is being transferred
- not following "include what you use"
- writing code where include order matters
Include order matters wherever there's multiple classes that rely on each other, it's very exhausting and I'm genuinely wondering what I'm supposed to do
@@vafasadrif12 The best practice is to explicitly include everything in a file that is used by code in that file. This makes headers self-contained so that the order in which you include them doesn't matter. If they share a common dependency, it will be brought in by the first file that includes it and used by every other file included later.
This only is a little more challenging when you have a cyclic dependency between files, for example class A uses class B and class B uses class A. This can be solved by moving the implementations of these classes to source files and only keeping the class definitions in the respective header files. Then you can forward declare A in the header of B and include A in the source file of B and vice versa for A. Of course this means that you can only use the forward declared type in the headers of A and B, so for example class A can not have a class member variable of type B. If you need this you would have to put them into a unique_ptr.
Another way to resolve this is to create an interface for A for example and then refer to the interface in B instead of A directly.
Note though that if you come across this problem you should carefully examine the design of your code. Unless classes A and B represent a data structure that has cyclic dependencies by design, for example nodes and edges in a graph that refer to each other, having cyclic dependencies between classes is an indicator of bad design.
Great video! A lot of tutorials/documentation found online teach the "old" C++ and make C ++ appear less appealing to novices
I'm somewhat proud that I knew almost everything you mentioned. The one exception is the structured bindings stuff. And now all I can think of is all the times I was annoyed that such feature doesn't exist when in reality it does ( I really like python-style stuff that gets added to C++ ). I feel like you can count on one hand the people who know the full syntax of C++. Once every few months I find that there is some new thing that has existed in this language for years that I hadn't encountered before.
The one part that I would disagree with you is reinterpreting variables. The C++ standard may not guarantee certain rules about byte order but 99% of the time the hardware people use does. And the 1% is when working with embeded stuff in weird hardware. Bit hacks can give you huge performance advantage so when that's the case I would use them. But I tend to test things either way because often the compiler or hardware has a better way of doing it.
Honestly gratz, even I fall for these things on a bad day! I'm not going to say you _can't_ find a use where reinterpret_cast is the best choice, but noobs really tend to waaaay overuse it when it is not at all needed. And I totally agree that bit hacks can make a huge performance difference in critical places. However, reinterpret_cast is generally not needed (nor recommended) to do bit hacks. As of C++20 nearly all bit hacks can be done safely with std::bit_cast. Just cast any object to a std::array of bytes of the same size (as shown in this video) and you can operate on them however you like. Prior to C++20, the standard-blessed way to do bit hacks is using memcpy (this is explicitly blessed by both the C++ and C standards). I know people worry about not wanting to make a "copy", but this use case is so common that if you use memcpy no reasonable compiler will actually make a copy in a situation where you replace a reinterpret_cast. It will recognize you are trying to do the safe version of reinterpret_cast and will elide the copy. As always, you should measure the performance and check the assembly yourself in either case. But really check out bit_cast if you have the fortune of using C++20!
@@mCoding Well I don't "fall" for these but I tend to knowingly do some of these when I'm writing a small quick program. But for larger projects it seems like good coding style saves me a lot of time since I make much fewer mistakes. And yeah, I'm not using C++20 yet.
The thing about the reinterpret_cast, it is literally undefined behaviour because it returns 0 with any compiler optimization level turned on. If you use -O0, then yes use reinterpret_cast but nobody does that so just use memcpy or the c++20 function which uses memcpy. The compiler knows what you're trying to do and doesn't actually make a copy. You can look up the strict-aliasing rule which is about this exact topic.
@GalaxyShard Yes, it works if you cast a pointer to char * or std::byte * (and a few similar types). Or cast any of those types into any pointer, but all other casts using reinterpret_cast are undefined behaviour. And even those casts aren't undefined behaviour but rather the reading/writing from the resulting pointer is.
Avoid manual memory/resource management (unless you have a good reason to), use const when you can and the standard library containers and algorithms when you can and you'll mostly be fine 😀 something that is not mentioned here though and that is even worse is over relying on performance of standard library functionality - make sure you understand the cost of using the standard library methods, dont just assume that they will scale well
Yes i think it is very important to not just understand the results of algorithms, but their complexity guarantees as well!
nah. STL containers are heap-allocating. Most of them must only be used when you know what you're doing.
Bro I opened the video thinking I'm about to be so happy for knowing all of it. Ended up feeling so bad cause I just knew the first couple of things lmao. I wish I could continue C++ I remember I loved it so much in uni. I still have that book CPP by Deitel that's one of the few books that built my foundation of programming.
Thank you so much for the video man. ♥️
Very welcome, glad you enjoyed. Never be afraid to find things you don't know! This is just an opportunity to learn even more!
I just started C++ and you show me all the things not to do. It‘s like telepathy😅
About the 30th item. Look at what is to the left of the *
const int * prt -> the thing that is const is the int (const int) is to the left of the star
int const * ptr -> same as above, the value at the memory that ptr points to cannot be changed
int * const ptr -> the pointer is const, meaning the int itself at the location can be changed
const int * const ptr -> both the pointer, and the value that the pointer points to cannot be changed
This is great. Modern C++ tips for noobs. I haven't done a whole lot of modern C++ (my C++ typically consists of raw pointers, new, and delete) but I've done rust, and a lot of stuff in this video has parallels or even stronger implications in rust. I'll definitely start using those smart pointers from now on because they'll make my C++ more like rust
Like what? Rust prevents you from writing most of these mistakes at compile time automatically or by not having so complex language features to begin with.
THANK YOU! for making it easy to remember about const and pointer thing, being a self-taught programmer with few years in C++, I found atleast 30-40% stuff that's new to me.
I can proudly say, i know all of these. And i rarely proud of my C++ code.
Great videos as usual, really easy to understand.
Great to hear!
I recently started to get more into C++ and there he is with a C++ video. God fucking damn it James, you got me again. Excellent work as always!
Thanks I really appreciate your kind words!
Some of this stuff is relatively new, isn't it? Those structured bindings look awesome
I think structured bindings where introduced in c++17, so yeah that is pretty new
Yes, I tried to mention "before/after C++XX" to indicate when each feature was introduced (though I'm sure I missed some). Structured bindings are C++17.
I would've liked if you could name your bindings, like (member_var1: name1, member_var2: name2), or something similar.
Yes. Not using range based for loop isn't a sign of being a beginner, as it didn't exist before. So it means the opposite, that you learned the language a long time ago. That also applies to lambda functions, bit_cast, structured bindings, constexpr, override, smart pointers, make_unique.
@@tsg1zzn Well C++11 isn't new anymore. You can be a senior "legacy C++" developer and still be a noob in "modern C++".
#21 is complete insanity. I am so glad I gave up on c++ long ago. I will only resort to using this language if I actually care about speed...which is almost never, since everything I end up writing gets barely used anyway XD
You can find many of those unfortunate C++ habits (programmer errors) also in aged professional C++ code bases:
like type casting from base class to a derived class,
or non virtual dtor even which may not be called when instance is used in a hierarchical class library with base classes dtor called.
Whats wrong with casting up if you know it is indeed that type?
@@NuggetInAJar i don't know, seems like if you want to use polymorphism then there will be at least a few cases where this is necessary. I haven't written OOP code in a while but i remember doing this a couple times
I wish this list was around when I was learning, good stuff!
One tip: an easier way to remember constness is to read the type backwards. So “const int *ptr1” can be read as ptr1 is a pointer of an integer that is constant. Or “int *const ptr3” can be read as ptr3 is a const pointer to an integer.
I'm glad that there are C++ programmers putting the asterisk in pointer declaration on the variable name's side as a C programmer!
One can argue, this is objectively incorrect as the space should be b/w the type and the variable name, and the type is pointer-to-something. The type is simply made-up of two entities.
Rationale: K&R
@@YourCRTube Not objectively, you can't. Because
int* x, y;
declares an int pointer and an int, whether or not there is a space between int and '*', not two int pointers like:
int *x, *y;
Argue about whether or not you should declare two variables on the same line, sure, but it's a strong argument. However:
int const * const x;
const int * const y;
You simply can't marry the '*' to the var name here if the _pointer_ has to be const. This is an awfully strong argument that there should be a space on each side of the '*', as much as it pains me to say so. My major complaint there is that it makes it feel orphaned and reads too much like '*' as multiplication.
@@YourCRTube "objectively incorrect"? I know this is programming but it's still a 'language', the only rule is "does it compile?" Everything else is categorically subjective.
Bonus point about tip 12: with std::tuple you can basically almost have multiple return values using structured bindings without even needing to declare your own struct
std::tuple Deez() {
return { 0, "Deez", 1 };
}
auto [x, y, z] = Deez();
Yeah but then the function signature and what you're actually returning becomes unreadable ^^'''
I'd rather declare a struct for this with explicitly named member variables than a random tuple
Great video, very informative for beginner-intermediate devs
Glad you think so! May have been towards the intermediate side but I think that's where my audience is anyway!
Very good summary of some of the fundamental c++ features.
Unfortunately, it takes sooo long, until the new standards trickle through to the industry. Most companies haven't even adopted c++17.
I think waiting with C++20 adoption for a bit is fair, like avoiding bleeding edge software. But C++17 really should be adopted by now.
@@Spielix As in flicking the compiler switch, true. As in actually understanding and using those features correctly, probably not.
@@ZenSepiol I mean "should" as "it would make sense to do so" , not as "it already happened".
As a long-time C++ developer and teacher, I can honestly say that my n00b score (by this video's standards, at least) was 0. But I see you also have a version for Python; that should be interesting. I'll be happy if my score there is under 5.
Edited to add: Yeah, I didn't really keep score, but I'm sure there were at least 10 things on the Python list I was unaware of. They're mostly things I don't get into in the Intro to Programming course I teach, but they're still things I really should have been aware of anyway.
When I got back into C++ 6 years ago, I think I did most of these. I've since gotten better, but there were 2 or 3 I watched twice. Thank you for the video.
Might be helpful to mention that some of these things are not available by default in C++, but were implemented in a later standard (and hence you need to compile under that standard or higher in order to have access to them). Range-loops, constexpr, unique and shared pointers require C++11, structured bindings require C++17.
I mean to be fair, if you're learning C++ now, you should be using at least C++14 to start with if not C++17.
Our professor at master degree in computer science that explained us c++ (and multimedia data processing) has teaches us all in standard c++17, and some c++20 like ranges, format and some modules. Unfortunately not explained us concepts but still a very updated course, because even if you study alone, you don’t want to study after c++20 like the 23, because no compiler has supported yet, and not will be supported for at least other 1 or 2 years so
there's always this misunderstanding that "undefined behavior" means "anything can happen" but that's not really what it means. it just means that the standard itself provides no implementation details for the behavior. you can still know what WILL happen under certain circumstances. where it becomes a problem is past a certain point, your code's complexity gets to a degree that you _probably wont_ know what will happen if you use a lot of undefined behavior, or if the behavior may change from OS to platform to implementation (and whatever-else weird in-betweens you have based on how you are compiling) which is usually why undefined behavior exists. in some cases, undefined behavior doesnt change in those situations and are undefined behaviors for reasons that dont affect you at all. sometimes, things really are just undefined behavior because the _standard_ doesnt provide implementation details and that's it, but that the way it is implemented is still done the same way practically everywhere (such as casting ints to chars in C). another reason why undefined behavior exists is because there is a problem that has multiple solutions that are all valid.
I would love more C++ videos, if you’d consider it. Thank you so much for your content!
Of course! Feel free to suggest things you'd like to learn about!
I remember watching this video months ago and not undersating it, now I understand and it helped me with a bug in the mistake 15
That's growth! Keep going!
why these videos are so rare..... such a gem,,,,🤩
After watching this, I really wish there were a modern C++ counterpart to Steve Oualline's « How Not To Program In C++ ».
After watching this video, I realized that I don't know C++.
It was next-level cool of you to put the little indexes in your video so people can jog around your video to see parts.
You are welcome! It was super tedious to do so I'm glad you appreciate it!
As a C developer, I would appreciate if you did one for C
Definitely have one incoming maybe 1 to 2 months? Feel free to suggest any habits too!
@@mCoding would be cool if you did it
Thank you! I rarely do C++, some of these provided a really good insight on problems that I never would have anticipated coming from other languages.
for "std::endl" i would say it is the exact opposite.
Printing like this is done usually for one of 2 reasons:
1 - user interactions. You tell the user what is going on and ask him for an input
2 - tracing - you wanna see what the program did.
In the first case the performance is a complete non-issue, in the second case NOT using "std::endl" would be moronic cause in the case of an error you have no idea when it happend as you did not flush the buffer, not even considering yet that your claim about it taking extra time is actually not really that correct.
Often saw people try to be "clever" and avoid the supposed performance-impact of flushing that later get frustrated cause they can not find out what went wrong cause their logging is not showing them what is really going on.
People think/claim this makes such a large difference yet don't even know that 'sync_with_stdio' is a thing - by default the I/O-buffer used by std::cout is synchronised with the buffer of the old c-streams. This alone has far bigger of an impact than std::endl vs
. If flushign the buffer makes ANY relevant performance difference then you are well beyond newbie-territory and at that point you really should know a lot more about the standard behaviour of streams and their buffers.
Structured binding - that is still relatively knew and very often simply not available. Nearly no business-software created before 2018 will even use C++17, often still c++11. Upgrading your entire platform on an existing codebase is rather laborious.
Right now i am glad that we can use C++17 in most of our current projects, but in some i am still restricted to 14 or 11, really looking forward to C++20 with the easier string-formatting.
I'm hoping to learn C++ and have a lot of experience in other languages, so videos like this which point out newbie mistakes are total gold. Thank you!
I don't like C++ particularly (I think its strengths are rarely worth the downsides), but I think most advice in this video is really good.
The only thing I feel unsure about are the structured binding. Since they operate on declaration order, they depend on an implementation detail that isn't necessarily stable, and I think that's worth keeping in mind when considering their use in larger projects.
exactly what i was thinking when this was mentioned. there is generally no reason to assume that the declaration order of class variables has any effect on program logic. and unless IDEs/compilers have advanced functionality I'm not aware of, there isn't a good automated way to catch the class of bugs that reordering declarations could cause
I haven't used C++ since 2003, my mind was absolutely blown away by this video. I had no idea how far C++ had caught up with higher level languages in expressiveness and productivity since then.
This is an absolutely mind-blowing, and makes me want to make a deeper dive back to C++ to see just good it has become.
ps. please understand I do not mean speed, power or flexibility when I talk about the improvements above.
Also, absolutely amazingly well done video in delivery
Yes c++ is evolving quickly, and it's actually still part of the original vision of Bjarne (see my interview with him). Amazing how his vision of c++ included things that are only now being implemented in 20 and 23. So much to learn!
@@mCoding I actually saw it and bought his book then, but hadn't had time to go through it. I still had no idea it had evolved this direction.
Thank you so much for making this sort of great content, I constantly keep learning new ways to approach things from your videos, they are a very satifying mix of very advanced optimization tricks and just very well done comprehensive tutorials, that actually explain and teach reasoning behind design/technique decisions, so unlike in many other tutorials, that gained knowledge very easy to transfer to other projects due to helping us understand the techniques well enough to be able to adjust them to our needs
It's become a clusterfuck of a language but it still has its place 😅
That's some weird looking python code.
See this thread for code that is valid both as Python and C++. stackoverflow.com/questions/52980076/existence-of-universal-c-python-polyglot
It would be great if we could fix the verbose syntax of c++ to be more similar to Python. I would love Python-type syntax with the compiled performance of c++.
This is what happens when you `from ___future___ import braces`.
@@nonconsensualopinion Real
yo thanks for teaching me about structured bindings, as a self taught programmer working alone nobody tells you about this stuff
This makes me realize how little C++ I know. Like for instance, the part with "std::ifstream input{name}" or any use of "{}" for initialization seems like wizardry (and frankly quite opaque) to me.
i honestly just treat all the new c++ stuff as a suggestion. just use what works well and be consistent :)
Unless in cases like std::sort that will beat almost any clever algorithm you can come up with, I really hate the idea of dogmatic following of "idiomatic" code which is just using a standard library item no matter how much it makes your code more unreadable. The std::find_if function makes it bigger and harder to read, makes it look like the code is doing something heavy/complex which it isn't.
If your code doesn't do what it looks like it might, please add a comment.
Your code will be more expressive (easier to read) if you use algorithms because they have name, a raw loop don't have name.
It doesn't do any complex, you pretty much read what it does.
@@tsunekakou1275 I disagree. A simple small raw for-loop doesn't need a name.
Tell me if
for (auto elem : v) {
sum += elem;
}
is somehow more clear and expressive than
std::for_each(v.cbegin(), v.cend(), [&](int em) {
sum += elem;
})
// I don't know if an exact API exists like this, this is just an example.
@@VivekYadav-ds8oz your example is a bit "wrong", you just have to find a better name that describes the purpose of the loop, accumulate perhaps?,
the "correct" function for this problem is
int sum = std::accumulate(v.cbegin(), v.cend(), 0);
which is really easy to read. you can write your own. if you don't think naming things make it easier to read then aren't anything to talk about. I can't prove to you that named loop is easier to read than a raw loop, and you can't prove otherwise. Maybe someone already have done the reasearch, maybe you can find those.
std::for_each is just for side-effect, for example:
std::for_each(names.cbegin(), names.cend(), [](const auto& name) {
std::cout
@@VivekYadav-ds8oz std::reduce() is cleaner, though.
Thank you, great video! Especially enlightening for me was the description of structured bindings; I see `first` and `second` used all over the place in bitcoin core. I knew item 30 (const ptr versus ptr to const) from many years of C programming; the mental model I used is: if the `const` is to the left of the `*`, then the thing pointed to is const; if it's to the right, then the pointer itself is const. And it can make sense to have both! `const S * const p` means that what p points to can't change, nor can p itself. Or even `const S * const * const p` (p is a pointer to a pointer to S; none of these 3 things can change).
Another thing people often don't realize is: don't write `int * const p` in a declaration (header file), just write `int *p`, even if the function definition (implementation) is `int * const p`. This is because p being immutable is an implementation detail of the function, not part of its interface.
I'm no C++ guru (I'm not even a software engineer.) but if I was writing a library and sending a header file to someone I think it would be ideal to let them know their variable will not change in any way; give no surprises. I would lean towards `const T *const foo` in the header file to let the reader know. This seems at least conceptually doing the right thing, but maybe I'm missing something here. (That and it's more explicit to use an Optional than a raw pointer unless interfacing with C or similar so this scenario would be pretty rare I'd think.)
Structured bindings popped up I believe in C++17, and bitcoin is from pre C++11 compiler support so I imagine it is more legacy code than anything and could be updated.
Hi @@Danielle_1234 - "I think it would be ideal to let them know their variable will not change in any way" -- Yes, but the "const" in "int * const p" or "int const n" in a function argument list does not do that. Values are passed by value in C and C++, so the called function has its own copy of the argument. It's impossible for the called function to change the variable in the caller's context. So the "const" in these cases in a header file are pure noise, just clutter.
(Footnote: in C++ there is a call by reference option, indicated by "&", but I'm not referring to that here.)
With an argument declared like "int * p", the p is still passed by value (called function has a separate copy), but the value being passed is a *pointer* to a variable in the caller's context. So the caller *can* modify it -- which, as you say, is very useful for the caller to know! If the declaration is changed to "const int * p" (or "int const * p", which has identical meaning), then the compiler won't allow the called function to modify what p points to. This is also very useful for the caller to know, and even a restriction that the caller may want to impose on the called function. So "const" in these cases (when it's to the left of a "*") definitely does need to be in the header file. But not in those first cases I mentioned.
C++ noob, here. Lately, I've been trying to avoid writing my own functions when one already exists. But a lot of times, it takes me longer to find one (or to understand the docs for it) than it does to write one that has the exact I/O that I want. Obviously, I'm not going to write one that's as efficient or safe as I would probably find, but I'm also not typically contributing to a shared code base or writing production code. Mine's generally one-off code for my research. Advice?
the more times you search for std function the higher chances you would remember in the future without much effort! in the long run then would be less time wasted
Indeed, eventually you will learn the most common std algorithms, at which point they will become basic building blocks for your code. You will be able to think in terms of algorithms instead of variables and loops. My advice would be to power through the learning phase even if it takes a bit longer. The algorithms have a lot in common are become easier to remember the more of them you know.
I would still not advise you to write out your own versions. Why?
Well,first, there are only about 80-90 functions defined in std::algorithm. It is only going to take you maybe an hour or two to go through each one and understand what it's supposed to do.(Most of them are not even complicated)
Two, cranking out your own versions is not going to help you if at one point in time your going to use C++ professionally since at that time, you'll need to learn the ones defined by the standard. So it's better if you start using them now and get some practice before you start using C++ at a professional capacity
This is perfectly fine as long as you leave comments explaining the intent, to make refactoring easier. Especially since still learning.
Maybe later you will find an entirely different approach altogether.
Do not worship STL. It’s performance/usability aren’t perfect.
Okay for most use cases though. But if you are serious about perf, you will always end up with custom containers and algos.
I worked with some shitty devs who knew STL algorithms very well :)
I'm a student and we're forbidden to use some standard algorithms and several libraries. For example we can't use vector in our code. Isn't it a bit strange and stupid?
first time i saw the q3 alg it was mind-blowing. 100 times later it still is
Had two of these.
Wasn't aware of structured bindings with parameters.
21 (eval order) is something I have forgotten.
Another way to remember the difference between const pointer and pointer to const is to remember that the pointer attaches to the variable, not the type, so to have a pointer to const you need to have the const between the asterisk and the name.
One thing that makes beginners more excited to use range-based for loop: typically faster because better code-gen.
From same to worse. It's same in case of optimizing compiler, worse in case of O0 where iterator check that it's not invalidated on every access.
I really appreciate the timestamps, Great Vid!
Awesome video. I know some people with years and years of experience who keep doing some of these. Especially returning by parameter and move-return. I've seen a code base where literally every function had a 'return std::move'.
'constexpr' in the case you showed would most probably not do anything as long as you have a good compiler and some level of optimization enabled. 'constexpr' / 'consteval' is more useful when you have to use the result of the function in a template parameter or another compile time thing.
The magic numbers one is true for virtually every language XD Still technically counts, though, so good one.
The cases where you pass a pointer into the function could be a reference instead, so there's no need to worry about ownership or null. If you want to be able to pass a null to mean nothing, then use std::optional. I can't think of a reason to use raw pointer, except for interop with C.
Raw pointers are better than std optional, to represent a maybe-null reference to a single large object without copying it. However, I often find myself with unique_ptr function parameters that are expected to be non-null, and the variable will be moved out of later, so the same sort of issue is still present in modern C++.
Why would anyone return an std::move()? Aren't all function returned-values rvalues by default?
"I can't think of a reason to use raw pointer, "
Pointer members are better than reference members
at 1:04, I prefer using "for(auto elem : data)" because we know that vector holds int.
Thank you, this is really great content, but it just make me less and less want to dive in c++. It's too complex for my primitive brain.
There is a lot of complexity in C++ for sure, but most of it is for backwards compatibility. If you start with a fresh codebase using C++20, there is a quite small and elegant subset of the language that you actually need to use to do almost anything. Give it a shot!
Get a linter dude it will automatically notify you when violating the rules, nobody can remember even 30 percent of this.
Thanks for ur content. I’m programming in cpp for 1.5yrs and learning features starting from cpp 11, these really helps.
This kind of video for Python: "Haha just use for loops silly :)"
This kind of video for C++: *pain and suffering in endless standards and hidden overheads*
As someone who's barely used C++ I have no idea what the hell I'm looking at
And then you have to use a compiler that is only capable of C++ 11 or so. And you basically can not do any of those tricks :) µC development is great, especially with bigger systems where you actually have enough memory to use dynamic allocation.
You can do almost any except structured bindings in c++11
I was starting off like "let's listen to this guy, time for me to stop being nooby!"
*doesn't even understand 25 of the 33 habits*
Such a great video, the bits are even joined together by context!
2 months later I understand 25 out of the 33 habits! I call that progress. Be back later.
I do a good number of these, but it's not by choice, but due to having to support older compilers, some of which don't even support C++ 11.
I feel for you. Don't worry, working on old code or otherwise not having access to newer features doesn't make you a noob.
Out of curiosity, what is your use case? I'm not a professional programmer, when is it necessary to support old compilers? Thanks.
Wow, this video is simply excellent, the tips you gave ranged from basic things to more advanced stuff and I'm thankful to you for learning new stuff I didn't really know or understand.
i’m starting to realise i’m not very good at c++
This is an excellent reference video.
Those "habits" were exactly the things I keep forgetting and I need to go and look up my notes.
Nice video!
I also noticed that you used const when passing an argument to the function by value like void foo(const int x), is it good practice? The outer value of x won't be modified anyway.
There is also a good tip which was not mentioned in this video: passing arguments to a class constructor by value and moving them, rather than passing by const reference and copying
It is true that const in that place does not matter to the caller, but it does matter to the callee. Just like marking a variable declared within the function const, marking a value parameter const still prevents you from accidentally modifying the value in your function and signals this intention to anyone reading the code, making it easier to follow.
As someone who learned Python on my own after learning C++11 in college and not being allowed to use almost anything in namespace::std, watching this has made me realize that C++ has a lot of the convenience features that I've been learning in Python. It seems like a lot of example C++ code online doesn't seem to use them as much as Python devs do.
Thank you… I was contemplating learning modern c++, but now I’m 100% convinced not to bother, and focus on rust, zig, nim, and roc instead.
Are those languages considerably simpler? I don't know any of them but, for the languages I do know, beyond the basics there are always intricacies that are not apparent at first glance. If you know all of them, which would you recommend I look into first?
I think the point is that (with Rust at least. I'm not familiar with zig and nim and roc hasn't been released yet) most of the gotchas in this video are rendered moot in other languages, either by being impossible or disallowed by the compiler without explicit escape hatches.
Rust is a reasonable choice, but to prioritise any of the other three, let alone all three, over c++ seems... unwise
@@TAP7a I tend to agree. Although, I must say that with LLVM, LSP, tree sitter etc., it has become much easier to create new programming languages *and* tooling, so I wouldn't discount them as easily anymore.
@@mCoding Every language has complexities, but C++ has a lot of _unnecessary_ complexities due to design flaws and legacy baggage. E.g., argument-dependent lookup has complex rules and is difficult to reason about. More recent languages have the benefit of decades of development of programming language theory as well as practical knowledge, avoiding design pitfalls. In particular, languages whose design is based on a more rigorous theoretical framework are better able to provide _orthogonal_ language features that avoid corner cases when they interact, compared to languages that evolved with ad-hoc additions. A blatant failure of orthogonality in C++: it implodes if you pass a multi-arg template into a preprocessor macro, because the C preprocessor doesn't handle commas inside angle brackets. Compare C++'s three incompatible, Turing-complete metaprogramming systems (C preprocessor, templates, constexpr functions) with the constrained, hygienic macro systems in more recent languages.
Watching this makes me happy for the simplicity of C.
As a reasonably confident C++ developer, I upvote every single advice in this video.
Thanks! I'm glad you enjoyed!
I was just looking at an old C++ textbook and thinking, yeah, I can probably understand most of this readily enough...
And then I come here, and almost all of it is very, very whoosh.
From someone who has never coded in c++, it looks like an ancient script or something. Really could not tell what was going on.
lmao magic numbers
the absolute most horrendous evil of a language somebody had come up with. my job made me do it, otherwise you would never see me here.
1. Using std::string, using std::cout ect. has the same drawback as using namespace std. You are make a choice for someone that 'string' will refer to 'std::string' and cout will refer to std::cout. You are substituting one evil with another, instead of solving it.
If it's domestic code, don't be afraid of using namespace std. If you'll use 'using std::something;' instead, you'll end up using all of the most populare std names in your headers anyway, and all your header files will begin with a screen of text telling "using std::this; using std::that; using std::those_two; using std::half_of_the_rest_library". This is not what you want.
If it's a public library code than use a library namespace, otherwise you are doomed to use std qualifier before every single std name.
3. Don't use 'auto' keyword without a purpose. The 'auto' keyword was designed to solve certain kind of problems. The given example is not in the list. Using 'auto' without a purpose makes your code hard to read. Use auto in general code (templates) or when type referencing is tedious (i.e. typename std::unordered_map::const_iterator).
5. There is nothing wrong in using C arrays. Using a core part of a language can not be wrong. There is std::span to solve the problem with function parameters.
6. Regarding the famouse Quake 3 code. Note that C style casting converts one type of pointer to another type of pointer. This is not UB. UB is dereferencing it because it violates type aliasing rules.
12. If you want to return multiple parameters, use std::tuple instead. It helps to avoid unnecessary struct declaration.
Overall good tips.
1. No; the problem with `using namespace` is that it pollutes the global namespace with any symbol that ever gets added to the namespace, harming maintainability. Together with ADL it becomes very complicated to reason about.
5. I disagree. The semantics of C arrays is so broken (e.g. magic pointer decay) that the C++ committee decided to add a library to replace it.
@@fat_pigeon I have a file that starts with two screens of 'using std:something'. Two whole screens becuase when the project started I decided to not embed 'std' in the global namesapce. "it pollutes the global namespace", I though. Now 'pollution' is not in the global namespace, now pollution is in every header while.
My strong suggestion now is to use 'using namespace std;', and forget about hypothetic collisions that can be resolved in a matter of 10 seconds.
@@sheeftz Namespace collisions can easily cause "action at a distance" that take much longer than 10 seconds to debug, especially in large codebases.
Imagine you write a file in your library that defines a function called (say) `async`. Now, your code works, but tomorrow, a coworker in a different department changes an unrelated library to include the `` header, which defines `template std::future std::async(X&& x, Y&&... y)`.
Result: someone in a third department complains that you broke the build, because both libraries happen to be included by their file, and the compiler reports the ambiguous overload `async` in your code.
5. Arrays in C are horribly broken to the point where they're just not worth using at all. Implicit conversions are bad as is, converting it to a type that has an entirely different arithmetic is even worse.
12. Problem with tuples for this purpose is kinda the same issue you can get with auto, it removes the clarity of the code very quickly as you're no longer aware what something represents, creating an aggregate solves this relatively smoothly.
I'd say that most of provided "habits" can be sometimes used depending on the curcumstances. For example, C to C++ interop makes use of C++ features much harder if even possible.
From my experience (i've started at C++99 era) what i sometimes have to use:
5. For me C-style arrays are just more readable than std::array.
6. There were cases when C-style casts can do things that other casts can't in any combination.
8. Provided solution does not valid in the nothrow environment.
13. Agree, unless it's a part of the code that referenced from multiple places and can't be placed into header.
I'd just like to point out for the actually noobies to c++ that a lot of these, if not all, do have times when they aren't applicable as shown in the video. Most of those reasons would be to do with performance constraints or memory layout, neither of which is something your likely to worry about as a beginner, so while your learning the language and/or programming in general, these are good to get you going, but don't consider them hard rules that must always happen. Unfortunately knowing when to bend these rules is something you can only learn with experience.
Regarding undefined behavior: IMO if all code written so far relied purely on the C/C++ standard without using any UB (read: platform-specific behavior), we would not be able to create any useful software at all
C++ has been greatly improved over the years but it still looks like barbed wire over a minefield to a java developer. I don't think I could use this language
Fair enough! But I do want to say that C++ is jagged because of its history and commitment to not breaking legacy code. If you come in with the mindset of following best practices of modern C++, I think you will find the language has simplified drastically.
For a java developer using Qt is your best option.
About pointer and const: read the type from right to left.
"const int*" is "pointer to integer constant".
"int* const" is "constant pointer to integer".
Most of these I agree, but C style arrays have a time and place. C++ was designed to contain all of C's functionality for a reason.
They are extremely rare to the point of vanishing, though. Since you can get the underlying data from both std::array and std::vector, and they are guaranteed to be contiguous, you can use them and use data() to get a pointer for C-style calls. If you are so memory limited that the extra 24 bytes of memory is a problem, then sure, use raw arrays. But then, maybe don't use C++ at all, just plain C.
They actually dont. You get the same compatility by being able to explicitly decay them. And they work in constexpr contexts. There's not a single legitimate reason for using C styled arrays.
@@valizeth4073 actually C-style arrays are safe to pass to dynamic libraries because C's ABI doesn't change much.
Vectors meanwhile can't be passed safely unless you use the same version of the compiler the program was compiled with and the same flags used to compile the program.
@@standingpadC++'s ABI hasn't changed since C++11
@@valizeth4073 From the standard's perspective (which is used to decide what features make it into C++), yes, but in practice no. The only compiler to keep ABI stability is MSVC, and that comes at the cost of features (Jason Turner explains this in his video on how the ABI should be broken again (th-cam.com/video/By7b19YIv8Q/w-d-xo.html), 16:50 is where he brings up an example of a C++ feature that wasn't supported at the time due to ABI breakage concerns).
Using new and delete is not a nooby habit. Smart pointers come with overhead at both compile and run times. As long as your new and deletes only appear in constructors, destructors, and functions that reallocate for additional memory, they won’t cause problems with exceptions.
isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#r11-avoid-calling-new-and-delete-explicitly
@@mCoding read what I wrote again and then read what you just sent me. In the context I am talking about (constructors and destructors), there is no ambiguity about when new and delete need to be called. This "rule" about not using naked pointers and new and delete is more of a rule of thumb to prevent newbs from creating memory leaks. If an object maintains proper ownership of its allocations, it doesn't need an extra layer of safety with smart pointers. You do realize that smart pointers are implemented with new and delete right? Maybe unique_ptr has a bug! Oh no! How do we proceed?
There should be no runtime overhead. Quick test:
```
#include
#include
struct X final {
X() {std::cout
@@fat_pigeon if you are using shared_ptr, there is indeed overhead wrt to the reference counting internals