B1 中級 28 分類 收藏
開始影片後,點擊或框選字幕可以立即查詢單字
字庫載入中…
回報字幕錯誤
there exists a place that you so secret that 1,000,000 thinking about it's guaranteed to upset somebody.
Embracing these ideas is so dangerous that simply suggesting you ever heard of them will cause immediate outrage on the Internet.
Joining me on a journey to this frightening location.
Join me as we wait.
Oh, hello.
I was just reading a story about processes from the eighties.
I don't think anybody is going to be interested in those.
If you like stories, I have a good story for you.
I've recently become the owner of a box, the boxes labeled Forbidden C++ on Within Lies the most heinous and evil forms of C plus plus code ever known.
Would you like to look inside?
Did 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 variable.
There we go.
Variable.
A of type Interview on 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 concede the variable a that have 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 programs such as this.
There's nothing wrong, a tall with that approach, and indeed it's quite convenient to operate this way.
And to all intents and purposes, it behaves like a regular horrible.
So here I am, out putting 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 on They all have a function called some method on that some method is going to print the global variable.
I'll include these at the top of my main program, but straightaway, we've a problem because these classes can't see.
My global variable on, the compiler warns, is a such So what happens if in the global name space here for all of the classes, I also declare my global variable.
Well, we still have a problem.
It's now saying there are multiple instances off this global variable and that's quite right.
So how do we bind them all together?
Instead, why don't we create a head of file called Global Header and declare our variable in that on include our global head of file in each class?
Well, that's not really changed anything.
There's still a redefinition on that makes sense, because hash include is effectively just couldn't paste the same bit of code into each class.
Head of 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 on in my main program, I'm going to create an instance of each one of my classes and call the method.
Tell that to run it.
And here we see the result perhaps somewhat expected.
They all display 666 They've access that global variable under 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 output, it's 666 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 of the 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, saying It's got unresolved external.
So now I need to go and add on implementation for this variable.
Tell you what.
We may as well just do that here.
Okay, now it's just 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 wanted somewhere tied to where have declared it, because I could effectively put this anywhere, and I might put it in the wrong place.
Well, modern sea has a solution for that.
We can now declare this as being in line onset.
Its value compiles just fine.
Aunt behaves as expected, This in line 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 is part of a compile unit.
Then it's the same across all of the Compile a ble units, so that's made implementing global variables a little easier.
But let's not just bypassed the big problem we have.
They're one of our classes, did something unexpected to 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 on this.
Convicts squashing certain bugs very, very difficult indeed, particularly in large code basis, with lots of variables on 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.
On the whole, the c++ language allows us to do that very well because it has scope variable, 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.
A tall but for multi file projects.
Firstly, there always a pain to get set up properly.
Secondly, they may not always behave the way you expect them to do.
And thirdly, they are accessible from absolutely anything and anywhere in your program.
Therefore, you have no guarantee what this state of the global variable will be after you've executed some code.
Well, let's not stop the shall we see what's next.
Oh, I hope you've got a strong stomach, Mac Rose.
Here is a deliberately simplified program.
It has a loop which it rates 20 times on outputs, the result of the max function between a random number drawn between zero and 10 on Fight.
So what we should expect to see is that the smallest number ever displayed is five.
Let's take a look.
Well, here are the results.
I've got five got a four and five and another four men and eight.
That's good.
55550 And then a a one and then another five.
That's good too.
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 appear, and it's a fairly standard implementation off a maximum macro on just a little side point.
This is also considered really evil, for some reason, the turn every 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 Mac Rose is they are not executed at runtime there, purely a pre process er 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 a result in correct 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?
Other 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 on will do the same for B.
Now we've inflated the macro.
Let's just tidy up this turner, a operator, so we can understand what decision it's making.
So this is the equivalent of saying if this is true, then returned.
Five ehlz return 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 house of the Standard Library.
I've access that they're the algorithm header.
This will now behave as we expect.
So let's take a look.
And here I've got 575595885 There no numbers less than five.
But don't feel you have to avoid macro is entirely.
Sometimes they offer quite interesting utility, so he was a macro that takes in a parameter s and then declares a variable called s on assigned it to the string value off s.
It turns out macros have quite a bit of syntax of their own.
And if you google it, you can find all of the different key words and symbols that too involved.
And it allows us to do rather obscure things like this.
And this might be another reason why people think they're evil is because he's starting to break all of the known conventions of the language on you're basically just making up your own here.
I've created a structure called test, but don't forget, this isn't executed at runtime.
It's executed at compile time.
So if I create an object my stroked t on.
I'm just going to go into the deep bugger here to look at what tea contains my highlight tea and expand on the window.
It's got four string type of variables, each initialized with the name of the variable.
Effectively Mac Rose.
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 consensus ble expect on that will only lead to frustration on dhe 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, lying programming languages that weren't a sophisticated is what we have today, and effectively, it's allows you to label parts of your program.
So here I've got part one and part two Onda.
At any point, I can jump to that part.
So in this case, if Y equals one, I'm going to jump to part one.
And if y equals two, I'm going to jump to part two.
So if I jumped apart to part one is never executed.
The primary problem would go to in C++ is that it will lead to spaghetti code.
Not it might.
It will on 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 doesnt 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 on 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 will have a part three to make sure that they skip Part two.
As you might expect, this example is deliberately simplified, but it does demonstrate a bit of an anti Patton where we're using go twos to implement subroutines.
We don't need to implement subroutines like this in a language which could natively handle subroutines in this code.
If we were to re factor it, toe have its intended purpose.
The obvious answer is to simply perform the operation we want when we need it and get rid of the goto 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 on reducing the possibility of bugs.
As I've just demonstrated, when you start using go twos, you tend to end up requiring Maur 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 four loops on.
There is an explicit condition inside the bottom of the nest that I want to capture on, then behave differently.
Perhaps I no longer need to carry on iterating through all of these.
For lips, 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 brakes and continues to break out of the four 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 on 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 unknown state.
So don't use go to for subroutines.
But do you think about its use carefully when you need to capture exceptional circumstances?
The bravery you were showing is commendable.
Let's keep pressing forward.
Oh, well, I wasn't expecting this one.
Avoid stuff.
One of those 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 into juice and floats, and we can define our own types in the forms of strokes and classes void.
Star is often found 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 voiced our permits tremendous flexibility.
Let's consider this small example here.
I've got a struck to assume this is a very complicated strokes, but it's got some different types within it.
On.
I'm going to create a little utility function dump to file, which will simply dump the binary data in that stroked 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 daughter to 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.
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 invite to that stream, and then I'm going to close that street.
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 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 bites 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 right function, I need to cast it to type chum.
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 the cast on Dhe because B isn't a pointer.
Actually need the value at the location after that cast, and there's my object.
Be 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 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 on.
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 on 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 avoid 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.
I owned things such as writing to a file, cause all I really care about the bites.
I don't care about the type, and so I can use this one function to handle any object in my sister.
A very powerful thing, but it's only a CZ good as the documentation on 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 on That's really were the problem with Void Star begins.
It is really the case that we ever want to removed the type of an object in a program.
So let's look at an alternative method, which allow similar flexibility but does things in a slightly more modern way 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++.
Wrap it for void, star, and where is Void?
Start is completely type lis.
At least here we've got a type of some description.
It is a standard any so immediately that signifies to the programmer.
Hey, this could be anything at all.
We use it in much the same way as avoid star, but it does come with a KN added feature.
Let's say I wanted to bring back the original objects.
I passed it in, as in any but I want to convert it back to type test.
Well, I could do that with standard any caste, 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 on We'll run it and see what happens.
Well, it's those an error.
Those exception on handled standard bad any caste 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 is a degree of protection from stopping us doing unnecessarily dodgy and foolish things with this generic general purpose variable Quite a powerful safety feature on because it was an exception, we could potentially wrap up this instruction inside an exception handler and catch it at runtime 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 name space S t B.
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 is happens due to the introduction on acceptance.
Off name spaces, which are effectively syntactic prefix, is to function names and variables so we can use the same function names in variables, but in different packages or libraries or context.
For example, here I'll create to name Spaces and S one and N s too.
On both of these names, spaces 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 name spaces the prefix, and I can make a distinction between the two.
The compiler is very happy with this.
And if I run, this program will see its output it the two unique strings.
And it's quite a common approach for library developers to wrap up their functionality in a name space.
In fact, probably the universal best example of this is the pixel game engine.
It exists in the O.
L.
C.
Name, space and effectively.
What this has allowed me to do is I can create functions with any kind of name I know that was long as we reside only in the OLC name space.
I'm not going to be copying the names of other functions leading to confusion.
So what exactly is the problem with using name space?
S t D.
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 name space in front of all of the functions.
Potentially, the coat becomes clearer, but the reason it's considered evil is for the potential off name space contamination.
Now, I must have been exceptionally lucky throughout my programming career because, like, I don't recall ever having encountered named space contamination.
But it can potentially happen.
The standard library is huge, and as we include, more and more parts of it were bolting a lot more functionality into that name space.
They're 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 are three and 10.
Very trivial example as usual, but what you may have just noticed there was the Intel, a sense environment in visual studio telling us something.
There are five possible implementations of this max function.
The 1st 4 are contained somewhere within the standard library on dhe.
Number five here is the one that we've created.
Compiling the program doesn't seem to yield a problem on.
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 a example of something with a known name.
And so in situations like this, it's quite Handley tohave, a specific name space 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 name space.
Mine hasn't even appeared now, going back to small programs and tribulation samples 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 ahead of file that we're including.
If I just use that head of 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 head of file, there may be some ambiguity as to which functions they should select.
I think on the whole you don't see name space 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 name spaces here, I don't need the prefix anymore.
But if I include both name spaces with a function that's called the same thing, we start to see the compilers flagging open era.
And it's actually quite a verbose era Onda meaningful one.
It's telling us 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 name space, 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 on more difficult for the end user.
I think with 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 Malik, and now I'm telling you that new and a liter evil well, not quite like all of these things.
They need to be used with some care fundamentally new and delete a 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 more subtle cases that catch people out.
For example, I'm going to create a simple struck called some object on.
It has a constructor and a D structure, so we can visualize when is being created on when it's being destroyed.
All the struck does is store 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 struck is actually a really complicated structure with all sorts of complicated rules regarding copying and multiple instances, etcetera, etcetera.
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 eight 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 to 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 could see lots of objects being created, but here on the right, we can also see that we're consuming memory, 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 consume 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 on.
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 iterated through all of the vector of objects on call the corresponding delete.
So let's run this, and now we can see objects are being created on 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 off ownership and the idea that objects have lifetimes.
C++ provides utilities to help us handle this, and we could make a simple modification to this code.
Bye.
Instead of using a roar, Pointer will use a smart pointer.
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 will call the make unique function I know don't need to worry about cleaning up after myself.
In fact, I can rely on the clear method of the vector toe handle this for me because the unique pointer or typically smart point is, in general a sensitive to when the memory is no longer required on will automatically called elite.
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 is end users.
So let's run this program and we can see again objects that created and destroyed on 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 there's 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 well 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 is a programmer, your programs and your projects are going to become more sophisticated on larger on.
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 experience programmers on doing your best to make sure that you're only carefully using the things I've shown in the video today were 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 plus.
Plus, I hope this hasn't given you any nightmares.
And I hope to see you in the future.
Good night.
提示:點選文章或是影片下面的字幕單字,可以直接快速翻譯喔!

載入中…

Forbidden C++

28 分類 收藏
林宜悉 發佈於 2020 年 3 月 28 日
看更多推薦影片
  1. 1. 單字查詢

    在字幕上選取單字即可即時查詢單字喔!

  2. 2. 單句重複播放

    可重複聽取一句單句,加強聽力!

  3. 3. 使用快速鍵

    使用影片快速鍵,讓學習更有效率!

  4. 4. 關閉語言字幕

    進階版練習可關閉字幕純聽英文哦!

  5. 5. 內嵌播放器

    可以將英文字幕學習播放器內嵌到部落格等地方喔

  6. 6. 展開播放器

    可隱藏右方全文及字典欄位,觀看影片更舒適!

  1. 英文聽力測驗

    挑戰字幕英文聽力測驗!

  1. 點擊展開筆記本讓你看的更舒服

  1. UrbanDictionary 俚語字典整合查詢。一般字典查詢不到你滿意的解譯,不妨使用「俚語字典」,或許會讓你有滿意的答案喔