31 nooby C++ habits you need to ditch

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hello and welcome to my list of newbie c plus habits c plus is an incredibly complex language with a lot of history so whether you're an actual noob that really needs to look out for these things or whether you just want to improve your code a little bit let's get started newbie thing number one using namespace standard people generally use it to save typing so you can say things like string instead of std colon colon string if it's limited to just a single function that might not be that bad but let's be honest it's usually used at the global level even worse if you do this in a header file then you also force this choice upon everyone that uses your code so please don't do this in a header file and also consider just using the names that you actually use number two using standard end line especially in a loop you probably meant to just print out a new line but endline does more than that it also flushes the buffer which takes extra time instead just use a new line character number three using a for loop by index when a range-based for loop expresses the intent better in this case i don't really care at all about the index instead use a range-based for loop there's no index so one less chance for an accidental typo or off by one error number four using a loop when a standard algorithm already exists to do what you're trying to do there's some simple thing that you want to do like find the index of the first positive number in a vector it's so simple though you decide to just write it yourself instead consider if there's an algorithm that already does what you're trying to do in this case we can use standard find if to find where the first positive element is number five using a c style array when you could have used a standard array c style arrays often decay into pointers and require you to pass the length of the array along with the array itself this is just another opportunity to make a typo instead use a standard array number six any use of reinterpret cast pretty much the only thing you're allowed to do with an object that you got from reinterpret cast is to reinterpret cast it back to the original type almost everything else is undefined behavior and yes the same goes for c style casting so sorry famous quake 3 algorithm for computing the inverse square root reinterpreting the bytes of a float as a long is actually undefined behavior you're always allowed to reinterpret cast the address of an object as a character type this allows you to inspect or print out the bytes that make up the object so you can see its memory layout however as of c plus 20 this use of reinterpret cast is also not necessary bitcast interprets the bytes of one object as a different type in this case i can cast any object to an array of bytes of the same size number 7 casting away const this function takes in a map that maps strings to the number of times that they've occurred it takes two words and then returns back to you whichever one has a higher count the first way you might try to implement this is by looking up the counts of the two words and then if the first count is bigger than the second then returning the first word if i try to compile this i get a weird error message telling me that the method isn't marked const and that's how we ended up with this code i know that i'm not modifying the map right the correct thing to do in this case is not to cast away const but to instead use the at method at is a const version of operator square bracket that throws if the word isn't in the map but you might ask why don't they just add a const version so that this would compile this brings us to newbie thing number eight not knowing that operator square brackets inserts the element into the map if it doesn't already exist that's right simply trying to look up this word in the map actually inserts it with a count of zero into the map number nine ignoring const correctness this function loops over a vector simply printing out each element one element per line it doesn't modify the vector so we could and should mark the vector const this is doubly important for a function parameter so that the caller knows that they can use this function without their vector being modified number 10 not knowing about string literal lifetimes string literals like this one are guaranteed to live for the entire life of a program so it's perfectly fine to return this even though it looks like it's a reference to a local variable number 11 not using structured bindings here we have a map of color names to their hex values then we just loop over all the pairs and print out the name and hex value it would be a lot more readable if we could refer to these things as name and hex rather than pair.first and pair.second well that's exactly what a structured binding lets us do we grab the pair by reference then the structured binding introduces name and hex as names for the first and second elements of the pair structured bindings can also be used with your own types if the members are public the names get assigned to the variables according to the order of their declarations number 12 using multiple out parameters when you want to return multiple things from a function instead create a basic struct and give those things names then you can return the multiple values that you wanted to by returning the struct instead the caller can even make it look like it was multiple values returned by using structured bindings 13. doing work at runtime that could have been done at compile time here's a function that gives the formula for the sum of the first n integers sometimes the parameters might be known at compile time and you could do the calculation ahead of time go ahead and let the compiler know that it's totally fine to compute this ahead of time number 14 forgetting to mark destructor's virtual if a derived class gets deleted through a pointer to this base class then the derived class destructor will not be called only the base classes destructor will be called here i have a class that derives from the base that doesn't have a virtual destructor this function expects a pointer to the base class it uses some base functionality and then deletes the pointer when it's done this will happen automatically since i'm using a unique pointer but the same would be true if you just took in a normal pointer and then manually called delete if you pass a pointer to an instance of this derived type then the wrong destructor gets called at the end to make sure the correct destructor is called even through a pointer to a base class you need to mark the function virtual it's also good practice to explicitly mark the derived classes destructor as override number 15 thinking that class members are initialized in the order they appear in the initializer list reading left to right this looks fine the actual order that members are initialized in is the order that they're declared in first we initialize end as start plus size but this m start is garbage it hasn't been initialized yet we can of course fix this by declaring start first instead or since start is also a parameter to the function we could just define the end variable in terms of the start parameter 16 not realizing there's a difference between default and value initialization x and x2 are default initialized they contain garbage until you put something into them y y2 and y3 are value initialized they are guaranteed to contain the value 0. z on the other hand is neither default nor value initialized this is a function declaration even if it's a tiny bit less efficient initializing your values is almost always a good idea the same kind of thing goes if you have an aggregate or array type in the first two cases n m will be garbage and s will be the empty string in the last three cases n and m are zero and s is the empty string notice that even with default initialization the empty string did still get initialized that's because for both default and value initialization if you define a default constructor it will be called 17. overuse of magic numbers introducing a basic constant in your code can make it many times more readable the compiler is going to optimize it away anyway just give it a good name 18. attempting to add or remove elements from a container while looping over it well doing that is sometimes just necessary but what i mean is noobs often do it incorrectly we're trying to put a copy of the vector at the end of the vector adding or removing an element to the vector may invalidate the iterators to the vector for example pushback might need to resize the vector and move all the elements to a new location after moving the contents of the vector to a new location you can't expect the end pointer to be the same you would run into the exact same issue in fact it's probably clearer why you would run into this issue if you used iterators directly this is a case where using a loop index actually does solve the problem it doesn't matter if the contents of your vector get moved somewhere else the i element is still the i element number 19 returning a moved local variable i totally get where you're coming from a vector can be a large object and you don't want to make a copy of it so you go ahead and try to move it out if you had just tried to return v directly there would have been no copy and no move in this situation that's because of return value optimization but what if the compiler can't do return value optimization in all cases the move is unnecessary the compiler always knows that it can move a local variable but in some cases this actively prevents return value optimization so that's why this is one of the few rules where i can say you should just never do this which brings us to number 20 thinking that move actually moves something here is an implementation of standard move the full templated definition might be a bit much to take in all at once so let's take a look just at the int case move takes in an int l value reference static casts it to an r value reference and returns it the exact same thing happens in the r value overload it just static casts to an r value and returns it a more accurate name for move is probably something like cast to r value number 21 thinking that evaluation order is guaranteed to be left to right here's a famous example we have a string that says but i've heard it works even if you don't believe in it we replace the first four characters with the empty string then we find even and replace it with only then we find don't and delete it with this reasoning we should end up with i've heard it works only if you believe in it but prior to c plus 17 the compiler is actually allowed to compute any sub-expression in any order so theoretically it could find the location of even first and then replace the first four characters making that location off by four so then when the second replace happens it would replace these four characters with only and you can see how this goes on you don't get the result you expected well the good news is that as of c plus 17 this example is guaranteed if you have a dot b then a is guaranteed to be evaluated before b is however even in c plus 20 the order that function arguments are evaluated is still not guaranteed left to right this wouldn't matter much if a b and c were pure functions but if a b and c have side effects then the order that they're called in might actually make a difference 22. using totally unnecessary heap allocations when a stack allocation would have been fine here we create two customer records on the heap then we do some work and then we end up deleting those variables at the end of the function the question we should ask ourselves is did this really need to be a heap allocation there's a good chance it would have been fine if we just stack allocated them so let's just say for the sake of argument that these objects are too big and you really do want them on the heap that brings us to number 23 not using unique pointer and shared pointer to do your heap allocations what happens if an exception gets thrown in the middle here then these deletes never occur and the memory is leaked when you want to make sure that a resource is cleaned up you need to put that cleanup code in a destructor so why don't we make a class that holds a pointer and then in its destructor it deletes that pointer well that's exactly what unique pointer does you can give it a heap allocated pointer and when it goes out of scope it deletes it a shared pointer on the other hand uses a reference counting scheme similar to what you might have in a language like python when the reference count hits 0 as the last shared pointer goes out of scope that shared pointer is in charge of the deletion this scheme is much more expensive because reference incrementing and decrementing have to be done atomically that brings us to number 24 constructing a unique or shared pointer directly instead of using make unique or make shared make unique and make shared will pass your arguments directly to the constructor of your object 25 any use of new or delete there's no reason to rewrite functionality that already exists here i'm trying to manage the memory of some resource and then delete it when it's done that's already what a unique pointer does don't try to couple the purpose of your class to the idea of ownership of an object that's a completely separate issue unique pointer and shared pointer together cover pretty much every valid use of new or delete number 26 any kind of attempt to do manual resource management this is pretty much the same story as with new and delete if you find yourself manually freeing closing or releasing any kind of resource then look to see if there's a class that does that automatically by the way this idea of having resources automatically close in a destructor is called raii it stands for resource acquisition is initialization but it really has more to do with ensuring that resources are released upon destruction number 27 thinking that raw pointers are somehow bad here's a basic max function this function is just reading from the pointers it doesn't care at all who's in charge of deleting them if your function doesn't have anything to do with the ownership of the objects in question then there's no need to use smart pointers the convention is that raw pointers don't own what they're pointing to and this should not be confused with the const-ness of the pointer this add function adds the source into the destination but this function is not in charge of the lifetime of either of the pointers of course if you're interoperating with c code c code is not going to share this convention a c function might very well return some heap allocated memory to you that it expects you to delete be careful though if you've got a pointer with malik you need to delete it with free you can't just let uniquepointer try to delete it with the built-in delete you can still use a unique pointer though you just need to define your own deleter which uses free number 28 returning a shared pointer when you aren't sure the object is going to be shared if a caller got a unique pointer it's cheap and easy to convert it into a shared pointer if that's what they really need they could even directly assign the unique pointer return value to a shared pointer but if you return to them a shared pointer in the first place the damage would already be done if all they really wanted was a unique pointer number 29 thinking that shared pointer is thread safe the reference counting part of the shared pointer is thread safe here i make a shared resource and pass it off to two worker threads because the reference counting is thread safe there's no danger that the object will fail to be deleted or be deleted twice however it's only the reference counting part that's atomic this part here where we're accessing the x variable and the resource is not atomic and there's no locks this is a plain old data race of two threads trying to modify the same memory if you want to fix the data race you need to fix it the way you'd fix any other data race and 30 confusing a const pointer with a pointer to const the concept of a const pointer versus pointer to const is pretty simple but a lot of newbies struggle to remember how to tell the difference between them syntactically the rule is that const applies to whatever is immediately to its left unless it's the leftmost thing in which it applies to the thing to its right so here the cost applies to the int not to the pointer here the cons applies to the int not to the pointer and here con supplies to the pointer not to the int number 31 ignoring compiler warnings ignoring them or turning them off very frequently leads to undefined behavior i hope you enjoyed my list of newbie c plus habits as always thank you to my patrons and donors for supporting me see you next time
Info
Channel: mCoding
Views: 740,438
Rating: undefined out of 5
Keywords: C++, cpp
Id: i_wDa2AS_8w
Channel Id: undefined
Length: 16min 18sec (978 seconds)
Published: Mon Dec 13 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.