Forbidden C++

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments

Aye! one of my favorite YouTubers

👍︎︎ 16 👤︎︎ u/Quiet_I_Am 📅︎︎ Mar 22 2020 🗫︎ replies

tldr?

👍︎︎ 17 👤︎︎ u/bumblebritches57 📅︎︎ Mar 22 2020 🗫︎ replies

The std::any section was extremely insightful and gave me more knowledge on the standard library and ways to maximize effectiveness in C++ programming. Great video!

👍︎︎ 4 👤︎︎ u/[deleted] 📅︎︎ Mar 22 2020 🗫︎ replies

Felt like sharing this

👍︎︎ 10 👤︎︎ u/sephirothbahamut 📅︎︎ Mar 22 2020 🗫︎ replies

I've been told following http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines is a good idea to avoid this kind of issue

👍︎︎ 3 👤︎︎ u/cisco1988 📅︎︎ Mar 22 2020 🗫︎ replies

goto to short circuit a loop? Sounds like a loop that needs to be in it's own function and use a return to short circuit.

👍︎︎ 4 👤︎︎ u/[deleted] 📅︎︎ Mar 22 2020 🗫︎ replies
Captions
[Music] there exists a place that posture is so secret that merely thinking about them is guaranteed to upset somebody embracing these ideas is so dangerous that simply suggesting you're heard of them will cause immediate outrage on the Internet join me on a journey to this frightening location join me as we visit the c++ dark side oh hello I was just reading a story about processors from the 80s I don't think anybody's going to be interested in those do you like stories I have a good story for you I've recently become the owner of a box the box is labeled forbidden C++ and within lies the most heinous and evil forms of C++ code ever known would you like to look inside my hand into the box and see what it yields oh yes most evil what a good place to start Global variables I'm sure everybody watching this is quite familiar with a variable there we go variable a of type integer and this variable exists within the scope of this main function the scope is defined by these curly brackets and we should be familiar with the idea that nothing outside of this scope can see the variable a that I've just created but what happens when we create a variable that isn't inside any scope well it becomes a global variable it is within all of the scopes within this file now for a simple one file program such as this there's nothing wrong at all with that approach and indeed it's quite convenient to operate this way and to all intents and purposes it behaves like a regular variable so here I'm outputting the value of that variable from the main function global variables start to become more tricky when you've got multiple files in your project here I've added three very similar classes to my project and they all have a function called sum method and that sum method is going to print the global variable I'll include piece at the top of my main program but straight away we have a problem because these classes can't see my global variable and the compiler wanders as such so what happens if in the global namespace here for all of the class I also declare my global variable well we still have a problem it's now saying there are multiple instances of this global variable and that's quite right so how do we bind them all together instead why don't we create a header file called global header and declare our variable in that and include our global header file in each class well that's not really changed anything there's still a redefinition and that makes sense because hash include' has effectively just couldn't paste the same bit of code into each class header file we've not really changed anything there so I need to indicate to the compiler that I want this to be the same variable no matter where it's called and in my main program I'm going to create an instance of each one of my classes and call the method to let's run it and here we see the result perhaps somewhat expected they all display 666 they've accessed that global variable and are displaying its value except that's incorrect because I didn't mention this but class B changes the global variable value so that's the first thing to worry about but secondly even though we changed the global variables value when we output from Class C its outputted six-six-six again so clearly even though the compiler hasn't complained the program isn't functioning in any way like I intended so why don't we try throwing some other buzzwords at it this is what people usually do when they're trying to make global variables work across files now it's compiled it's saying it's got an unresolved external so now I need to go and add an implementation for this variable tell you what we may as well just do that here now it's got an implementation let's run it oh well at least now it's behaving in a somewhat expected way but this seems a little bit of a mess I don't like having to have this implementation somewhere in my code I want it somewhere tied to where I've declared it because I could effectively put this anywhere and I might put it in the wrong place well modern C has a solution for that we can now declare this as being inline and set its value compiles just fine and behaves as expected this inline keyword has sort of mutated over the last 20 years or so but one way to think about it now is that when it sees a symbol like this as part of a compile unit then it's the same across all of the compiler ball units so that's made implementing global variables a little easier but let's not just bypass the big problem we have there one of our classes did something unexpected - the global variable and that's affected the subsequent behavior of the program and that's the really big problem with global variables is nothing is protecting them and nothing is guarding them and this can make squashing certain bugs very very difficult indeed particularly in large code bases with lots of variables and many files as programmers we should always aim to write functions where the end user can anticipate what the expected result is going to be and on the whole the C++ language allows us to do that very well because it has scoped variables so any variables inside a function somewhere can only really be changed by that function the fact that a function can change a global variable and therefore alter the state of the entire system without any warning or hint that it has done that to the user means that we get unpredictable behavior from call in that function so just be aware of these things when using global variables for simple applications where you're in full control nothing wrong with them at all but for multi file projects firstly they're always a pain to get set up properly secondly they main always behave the way you expect them to do and thirdly they are accessible from absolutely anything and any word in your program therefore you have no guarantee what the state of the global variable will be after you've executed some code well let's not stop there shall we see what's next I hope you've got a strong stomach macros here is a deliberately simplified program it has a loop which iterates 20 times and outputs the result of the max function between a random number drawn between 0 and 10 and 5 so what we should expect to see is that the smallest number ever displayed is 5 let's take a look well here are the results I've got 500 I've got a 4 and then 5 and another 4 and then an 8 that's good 5 5 5 500 and then a 1 and then another 5 that's good and there - hmm okay clearly not working and this is because in a rather evil way max is not a function it's a macro that I've defined up here and it's a fairly standard implementation of a maximum macro and just a little side point this is also considered really evil for some reason the ternary operator but hey and so to all intents and purposes it looks and feels like a function and even in most cases it behaves correctly but the thing with macros is they are not executed at runtime they are purely a preprocessor glorified cut copy and paste tool for the compiler which undeniably can also be very very powerful you just need to wield this power rather carefully so why was our result incorrect macros are just a copy and paste tool so even though they can take in arguments in this case a and B let's copy and paste this manually so what we see here are the two arguments that I supplied to the macro wherever we see the argument a we're going to copy and paste in the corresponding argument and we'll do the same for B now we've inflated the macro let's just tidy up this operator so we can understand what decision it's making so this is the equivalent of saying if this is true then returned five eltz returned our first argument and here we can see the problem the original test is fine but one of the possible answers is to call the random function again when in reality what that should be returning is the result of the argument we passed in in the first place don't forget this has happened at compile time so this code appear hasn't been executed at any point it's just a copy and paste tool so what can we do instead well my golden rule is that if you have something that behaves like a function it should be a function in this case I'm going to call the standard max function out of the standard library I've accessed that via the algorithm header this will now behave as we expect so let's take a look and here I've got five seven five five nine five eight eight five there are no numbers less than five but don't feel you have to avoid macros entirely sometimes they offer quite interesting utility so here is a macro that takes in a parameter s and then declares a variable called s and assigned it to the string value of s it turns out macros have quite a bit of syntax of their own and if you google it you can find all the different key words and symbols that are involved and it allows us to do rather obscure things like this and this might be another reason why people think that evil is because you're starting to break all of the known conventions of the language and you're basically just making up your own here I've created a struct called test but don't forget this isn't executed at runtime it's executed at compile time so if I create an object with my struct T and I'm just going to go into the debugger here to look at what tea contains tip I highlight T and expand on the window it's got four string type variables each initialized with the name of the variable effectively macros allow us to automate the construction of our own code they also allow you to play cool pranks on your coding buddies but be careful not to abuse them simply because it's easy to move the program away from what the programmer can sensibly expect and that will only lead to frustration and difficulty debugging your programs I think we're getting to the point where we both want this to be over as soon as possible let's get on to the next one oh that's just truly disgusting go to I feel that poor old go to gets an unnecessarily hard time its origins lie in programming languages that weren't as sophisticated as what we have today and effectively it allows you to label parts of your program so here I've got part 1 and part 2 and at any point I can jump to that part so in this case if y equals 1 I'm going to jump to part 1 and if y equals 2 I'm going to jump to part 2 so if I jump to part 2 part 1 is never executed the primary problem would go to in C++ is that it will lead to spaghetti code not it might it will and as your programs get more sophisticated if you start to lean on go to too much you're jumping backwards and forwards all over the place and it can also lead to unexpected bugs and behavior to your program this label is invisible so it doesn't partition any of the code within the region that is labeled as being distinct from any other code so in this case if I jump to part one I'll jump here and execute x plus plus but then I'll immediately execute X minus minus so to counteract that I need to use yet another go-to we'll have a part 3 to make sure that I skip part 2 as you might expect this example is deliberately simplified but it does demonstrate a bit of an anti-pattern where we're using go twos to implement subroutines we don't need to implement subroutines like this in a language which can natively handle subroutines in this code if we were to refactor it to have its intended purpose the obvious answer is to simply perform the operation we want when we need it and get rid of the go-to is entirely the if construct provides enough functionality to make sure that we're not executing code we shouldn't be executing and it can be argued that if you're using go-to in your code you can always replace it with something a little bit more intelligent enhancing encapsulation making the code clearer and reducing the possible of books as I've just demonstrated when you start using go twos you tend to end up requiring more go twos and I believe it's probably safe to say that if you do find yourself using go twos then you've some sort of design or logic problem with your algorithm that's not to say go twos don't have some utility here I've added three nested for loops and there is an explicit condition inside the bottom of the nest that I want to capture and then behave differently perhaps I no longer need to carry on iterating through all of these four loops this becomes a very big computational bottleneck in an algorithm perhaps if I'm searching for something as soon as I found it within this three dimensional space then I want to stop the search now I could use breaks and continues to break out of the for loop in a more conventional way but in this instance there's absolutely nothing wrong with using a go-to to just break out of all of the loops at once and then carry on doing what I'm doing with the results and in my experience it's situations like this where you do see the use of go-to being used properly particularly for error handling and in situations where you need to capture when all else fails you can always go to a location in your algorithm which can reset the algorithm to a known state so don't use goto for subroutines but do think about its use carefully when you need to capture exceptional circumstances the bravery you are showing is commendable let's keep pressing forward oh well I wasn't expecting this one void star one of the standout features of the C++ language is it is a tight language nothing exists in this system without having some sort of type associated with it we have integers and floats and we can define our own types in the forms of structs and classes void star is often frowned upon because fundamentally it removes the type from a pointer and once you've removed that type you can turn that data into anything you want so void star permits tremendous flexibility let's consider this small example here I've got a struct assume this is a very complicated struct but it's got some different types within it and I'm going to create a little utility function dump the file which will simply dump the binary data in that struct to a file so in my main program I'm going to create an instance of my structure and I'm going to call my dump two file function notice here I'm passing in the address of the object so I'm passing in a pointer I'm also passing in the size of the object and we'll see why in a minute very crudely I'm going to create an output file stream in binary mode I'm going to write to that stream and then I'm going to close that stream notice that the argument is void star this means I really don't care what type a is all I care about is where it exists in memory and so as soon as I've entered this function a is just a number it is a memory address I know nothing about what it is pointing to so if I want to write it to the file I also have to provide the size of the object in bytes because I don't know when to stop writing to the file I don't know what a is all I know is it's a starting location in order to make a compatible with the write function I need to cast it to type char and so this is quite a powerful thing because I can use this one function to dump any kind of object in my program to a file if I did want to retrieve my original object back I'd have to cast the void star pointer back into something sensible so here I'm performing caste and because B isn't a pointer I actually need the value at the location after that caste and there's my object B so I've reconstructed my test structure from the void star pointer that was passed into the function but I could quite legally also cast it to absolutely anything else and it will be interpreted as such there'll be no sort of translation or clever manipulation of the memory whatever memory exists at that void star location will now be read as a double and this is quite a useful technique when you're dealing with lower-level programs where you don't necessarily need to care about the type of the information all you care about is where it starts in memory however C++ is a typed language and it's good practice to try and keep things in their original type after all this line here makes absolutely no sense at all and I'll also add fundamentally a void star on its own other than being a memory address is quite useless you'll always need to cast it to something else and as a programmer you need to take care that you're casting it to something sensible or indeed the right thing so as I mentioned before quite useful for low-level IO and things such as writing to a file because all I really care about are the bytes I don't care about the type and so I can use this one function to handle any object in my system very powerful thing but it's only as good as the documentation and the programmer that's using it the compiler will see nothing wrong with this line yet it's complete gobbledygook and could potentially lead to very difficult to track down bugs and that's really where the problem with void star begins it is really the case that we ever want to remove the type of an object in a program so let's look at an alternative method which allows similar flexibility but does things in a slightly more modern way I'm going to include the any library from the standard library and rewrite my function to support the any type coming in so any is effectively a modern C++ wrapper for void star and whereas void star is completely typeless at least here we've got a type of some description it is a standard so immediately that signifies to the programmer hey this could be anything at all we use it in much the same way as a void star but it does come with an added feature let's say I wanted to bring back the original object so I passed it in as in any but I want to convert it back to type test well I can do that with standard anycast and that's very nice but let's say I wanted to cast my any variable into something that doesn't make sense so here I'm trying to cast it to a double like we did before so I'll just change my main program here to call the new function and you'll notice I'm no longer passing the address because I'm passing in a reference and we'll run it and see what happens well it throws an error throws exception on handle standard bad any cast at memory location whatever and it's highlighted the line here that hey look it made no sense to convert this any into a double because the original type was of type test so that's given us a degree of protection from stopping us doing unnecessarily dodgy and foolish things with this generic general-purpose variable quite a powerful safety feature and because it was an exception we could potentially wrap up this instruction inside an exception Handler and catch it at run time without terminating our program well I must say I'm impressed well done you've made it this far not many people have a stomach like you let's see what's next oh dear using namespace STD those of you that have been programming long enough will recall that we used to call a function like this though nowadays you may have noticed that calling a function is a little bit more involved and this has happened due to the introduction and acceptance of namespaces which are effectively syntactic prefixes to function names and variables so we can use the same function names in variables but in different packages or libraries or contexts for example here I'll create two namespaces NS 1 and NS 2 and both of these namespaces contain a function called test function the implementation of the functions is different and if I want to call a particular function I use the namespace as the prefix and I can make the distinction between the two the compiler is very happy with this and if I run this program we'll see it's outputted the two unique strings and it's quite a common approach for library developers to wrap up their functionality in a namespace in fact probably the universal best example of this is the pixel game engine it exists in the OLC namespace and effectively what this has allowed me to do is I can create functions with any kind of name I know that as long as we reside only in the OLC namespace I'm not going to be copying the names of other functions leading to confusion so what exactly is the problem with using namespace STD well in a simple single file program like this there isn't really a problem in fact it makes things quite convenient I don't need to type the namespace influence of all of the functions potentially the code becomes clearer but the reason it's considered evil is for the potential of namespace contamination now I must have been exceptionally lucky throughout my programming career because I don't recall ever having encountered namespace contamination but it can potentially happen the standard library is huge and as we include more and more parts of it who are bolting a lot more functionality into that namespace there are perhaps thousands of functions and variables contained within so let's bring in our good old max function from earlier it's a function this time and then I'll try to use it so a equals max of three and ten very trivial example as usual but what you may have just noticed there was the intellisense environment in visual studio telling us something there are five possible implementations of this max function the first four are contained somewhere within the standard library and number five here is the one that we've created compiling the program doesn't seem to yield a problem and on the whole it shouldn't but can we really be sure which version of this max function were calling sure they both perform a similar operation but they needn't this is just an example of something with a known name and so in situations like this it's quite handy to have a specific namespace specify to indicate precisely which set of Max functions were choosing from so now I only get four options because they're the four functions defined in the STD namespace mine hasn't even appeared now going back to small programs and trivial examples here it's not necessarily a problem we're in control of everything that's going on but let's suppose we included this line in a header file that we're including if I just use that header file blindly without looking at its contents I don't know that it's already included the standard library for this translation unit whilst it's being compiled therefore to the users of my header file there may be some ambiguity as to which functions they should select I think on the whole you don't see namespace contamination really being a problem because the compiler is very good at identifying that it's happening for example if I use one of my namespaces here I don't need the prefix anymore but if I include both namespaces with a function that's called the same thing we start to see the compilers flagging up an error and it's actually quite a verbose error and a meaningful one it's telling us that there are two implementations of this function and it can't decide which one to use therefore I suggest a good rule of thumb is to only use using namespace whatever when and where you need it it's a great way of having to avoid typing out lots and lots of prefixes therefore making the code a lot clearer but by placing it in the wrong location you can make the code ambiguous and more difficult for the end user I think we've time for one more for now well that is surprising it's quite a modern one new and delete now I know some of you will be thinking that you've only just moved on from malloc and now I'm telling you that new and delete are evil well not quite like all of these things they need to be used with some care fundamentally new and delete are used to allocate memory in your program and you don't own your memory your operating system does so when you ask the operating system for some memory you should remember to give it back like a nice person and on the whole you might think well that's not too difficult but it's the most subtle cases that kept people out for example I'm going to create a simple struct called some object and it has a constructor and a destructor so we can visualize when it's being created and when it's being destroyed all the stroke does is store a value so we can identify that particular object now because I'm trying to keep things brief and simple I want you to imagine that this some object struct is actually a really complicated struct with all sorts of complicated rules regarding copying and multiple instances etc etc I'm just keeping it very simple and in such cases it might not always be correct or convenient to store the object in a container instead we'd rather store a pointer to the object in the container and then we might be tempted to do something like this so we just push back a new object we've created it and we push the pointer back in so far so good then at some point in your program you may decide well I'm done with those objects now so I'll clear that vector and just to make things a little more complicated you might decide do that 100,000 times in your program this program clearly doesn't make much sense but imagine that once you've populated your vector you're going to do something with it here so let's run this program well we can see lots of objects being created but here on the right we can also see that we're consuming memory at quite a rapid rate it's just constantly and constantly increasing this is a problem imagine my application was some sort of server application and it was expected to run for months if not years at a time with no human maintenance eventually I'd consumed all of the memory in my system this is called a memory leak and it's because we've not remembered to delete the objects that we've created we've naively assumed out of ignorance perhaps that the clear function of the vector would do something about tidying up the contents of the vector and if you're coming from a background where you've worked with more modern languages that automatically handle the memory for you you might be thinking well that seems quite a reasonable assumption to make because quite a few of the languages go to some efforts to hide memory from you and when it identifies that some memory exists that you're no longer using it will quietly clear it up for you this is called garbage collection but C++ doesn't do that so a simple fix in this scenario would be to iterate through all of the vector of objects and call the corresponding delete so let's run this and now we can see objects are being created and destroyed and my memory consumption is flat it's no longer continuously growing we've plugged that memory leak admittedly this is a trivial example of this problem and it would be remiss of me not to introduce the more modern C++ way of doing things as programming in general is trending towards models of ownership and the idea that objects have lifetimes C++ provides utilities to help us handle this and we can make a simple modification to this code by instead of using a raw pointer we'll use a smart pointer or pass it the type of some object we don't need to specify it's a point to explicitly anymore and instead of specifying new or call the make unique function I now don't need to worry about cleaning up after myself in fact I can rely on the clear method of the vector to handle this for me because the unique pointer or typically smart pointers in general are sensitive to when the memory is no longer required and will automatically call delete obviously somewhere buried inside the standard library in the implementation for these smart pointers exists new and delete it's just it's hidden and guarded from us as end-users so let's run this program and we can see again objects are created and destroyed and I'm not leaking memory as with all of the little programs I've shown today in the context of a single file demonstration application it may seem like this not really anything wrong with using these techniques and if you're just starting out on your programming journey you may be wondering why the more experienced developers out there will quite frequently scream at you about not using any of the techniques shown in this video well even though they could be phrasing it in perhaps a more eloquent way they do have good intentions the principle being that as you develop as a programmer your programs and your projects are going to become more sophisticated and larger and all of the things I've shown today are great ways to make those projects far more complicated than they need to be and so in the long term following some of the guidance from these experienced programmers and doing your best to make sure that you're only carefully using the things I've shown in the video today where appropriate you'll be a better programmer well I say congratulations you've made it through the horrors and yet incredible utility of the dark side of C++ I hope this hasn't given you any nightmares and I hope to see you in the future good night
Info
Channel: undefined
Views: 709,269
Rating: 4.9136829 out of 5
Keywords: one lone coder, onelonecoder, learning, programming, tutorial, c++, beginner, olcconsolegameengine, command prompt, ascii, game, game engine, pixelgameengine, olc::pixelgameengine
Id: j0_u26Vpb4w
Channel Id: undefined
Length: 33min 6sec (1986 seconds)
Published: Sat Mar 21 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.