Why C++ for Unreal 4(forums.unrealengine.com) |
Why C++ for Unreal 4(forums.unrealengine.com) |
But maybe they are just trying to fend off Unity (open source, a more coherent experience). Usually when companies do this, it's too late. I've no idea if that's the case here.
Note how many games have come out using UE3, think about the royalties coming in.
Also, right now they have just about the best game-engine, only hobbyists and some indies would choose Unity, mostly because its easier.
UE4 will definitely get several high profile games soon from big AAA companies.
I would say that Unity is in deep trouble right now :P
I'd say they should start worrying about Unity when something like Unreal Tournament 2004 (a decade old game btw) comes out built with Unity.
... I get what you're saying, but I think the Unreal Engine is going to continue to be a cash cow for a good while yet.
Another data point: id was killing it (and, to us, Carmack remains undisputed). But they completely lost out the next generation, to Unreal, because of vehicles. I don't think that'll happen here, just disclaimin' past performance is no guarantee of future success.
Though, to be fair, the Quake engine underlied Call of Duty, the most successful franchise (I believe), and it really showed in the framerate. Note: no vehicles. And yes, past tense.
It has nothing to do with vehicles.
Re: vehicles. I'm going by what Carmack said in an interview.
He was talking aboout the time around Unreal Tournament 2004, and at that generation, it was a big thing (kinda like waving grass was at one time).
Sorry, I don't recall which interview (and it would be hard to google unless there's a transcript). It might have been one of his keynotes, perhaps the one with Rage on an iPhone. I'm pretty sure it was a long one (at least 1.5 hours). It was one of the big popular videos on HN/r/programming (not an esoteric one).
The main problem with scripting layers is that you are basically handing programming tasks over to team members who's job is not to solve programming tasks, and thus getting a lot of beginner's code quality and performance problems which are almost impossible to debug and profile (unless you have a few top-notch coders in the game- and level-design teams).
And then there will be definitely those "creative workarounds" to get something done which neither the engine nor the scripting layer was designed for, which make entertaining horror stories the programmers tell the new hires when they inevitable ask why your engine doesn't have scripting ;)
A better approach is to give the level designers simple, pre-programmed, combinable high-level building blocks (AI behaviours, actions, triggers, etc), and let them customize a level (as in game area) with this. But never build the entire game logic with such an approach! With this, stuff can still be fucked up, but at least the performance-sensitive stuff can be implemented by the programming team, and it's much easier to debug and maintain.
[edit: typos]
1. Co-routines
Co-routines (co-operative multi-tasking?) mean you can do stuff like
while (isWalking()) {
advance();
yield();
}
This is effectively 'yield' from Python, C#, etc.. You can implement this in C++ by swapping stacks and calling setjmp but there's usually issues.2. Iteration time
You can usually swap script code live. This project aims to fix that for C++ though I'm a little skeptical it can stay robust
https://github.com/RuntimeCompiledCPlusPlus/RuntimeCompiledC...
Boost has a c++ implementation but it looks quite different:
http://www.boost.org/doc/libs/1_55_0/libs/coroutine/doc/html...
[1] http://supercollider.github.io
edit: pythons new asyncio stuff looks very interesting:
Every place that I'm tempted to write an event-driven finite state machine, or something similar, I spawn a thread instead. I get to write synchronous code, which feels much more natural to me.
For instance my actor, running in a thread, calls a function like advance(). That drops data into an object, and wakes up the main thread, and blocks.
The next time the main thread wants to give processing time to the actor, it describes the world into the same shared object, waked up the actor's thread, and blocks.
Doing a switch like this dozens or even hundreds of times per second seems to work pretty well, especially if the main thread only gives execution to the actor thread when it needs to - inputs have changed, etc.
For my use cases, it radically simplifies my code, and I have a small number of different inputs to handle, so it has been scaling well.
An ideal scripting language:
- Allows designers to build complex gameplay elements, define complex ai behaviors, create gameflow with minimal work from the software engineers.
- Handles memory allocation/destruction behind the scenes
- Does not crash the game when an error occurs
- Handles multithreading and/or events
- Does not allow designers to shoot themselves in the foot
- Has a simple and clean syntax
- Allows software engineers to expose their APIs to it easily
- Can be reloaded on the fly
If you control the feature set of the scripting language then it becomes a crucial tool for rapid development of your game - empowering the creative team to make their game and allowing the software engineers to concentrate on all the other stuff that needs their attention.
In my opinion visual scripting and component systems can be a useful addition to an engine, but I have always seen a usefulness in having a scripting language layer.
(I have shipped multiple high profile console games with more lines of script code than of game code and design teams matching the size of the programming teams)
I disagree that it has to follow that scripting -> bad code. I think of the C <--> scripting integration that I do as a Judo secret weapon. I get to easily fling "high-performance" C around in a super-dynamic way (REPL, easy-to-use high-level abstractions)... I think the problem you're describing is real (possibility to abuse/mis-use scripting power), but dismissing the notion of scripting plus C/C++ because of possibility of abuse seems like throwing out the baby with the bathwater. Better addressed with training and culture.
It sounds like having a real software engineer do code reviews and/or rewrite the poor scripts before they get committed would be a solution to this problem. Presumably giving level designers the ability to script things is good for the game play.
> 'What starts out as a sandbox full of toys eventually grows into a desert of complexity and duplication.'
is beautiful and is a pattern I've seen multiple times before. Its not feature creep per-se, but more something a bit more insidious in software development.
Rust for instance looks very promising but you still have to go through the tedious task of redeclaring all the prototypes of the C functions before you call them, it cannot directly parse C headers (as far as I know). That makes writing hybrid code (for instance incrementally porting code from C into Rust) much more difficult and error prone than they need to be.
https://github.com/crabtw/rust-bindgen/
There are also long-term plans for adopting this into the compiler itself:
That is the only way to make people adopt them. Otherwise they become just another language to do business applications.
It is very possible to write games in high-level languages, but you will lose at least half the compute power of the machine by doing so. Note that unless you're writing a Gears of Wars, you don't really need such performance and productivity wins once again.
As much effort as they put into the IDE it would always play second fiddle to Visual Studio. When I left there was no way to remote debug unreal script on the target device (this may not be the case now).
I know that all of the guys I worked with in the studio would welcome pure C++ approach. The only real losers here are mod makers who will have a higher entrance bar.
They didn't really drop the highlevel language, they just dropped the idea of a highlevel general purpose language, favouring a strict DSL.
There's still a bit of interop, but you have to be very explicit about it. In C++ you use macro's to tell the compiler what's accessible to blueprint, and to access blueprint from C++ you have to make weird queries that are very obviously highly dynamic and ill-performant.
So cool stuff all around :)
The reason builds were sometimes a bit tricky was due to chicken and egg syndrome because the script compilation wouldn't just compile scripts, but also modify headers to support the new objects created in script.
Aside from the performance issues, the main drawback (to me) with UC was definitely how tightly coupled to native code it was.
PS: I like the BluePrint though.
Doom 3 and previous Unreal engines had scripting languages; even the first moddable FPS engine, Quake, had a 'scripting language' of sorts -- Quake C, a sort of subset of C. id software turned back to pure code with the Quake 4 engine, however, recognizing the mistake that introducing the overhead of script-vs-code, and the limitations of scripts, outweighs any gains from being "easier to edit".
http://dconf.org/talks/evans_1.html
I wonder if some of the same points would apply here. The short version of the talk is that D compiles much faster than C++, has limited C++ link compatibility (e.g. classes, but not templates), and overall has nicer syntax / language features than using C++ directly. Metaprogramming / compile-time introspection allow automatically serializing/deserializing data to allow updating data structures without restarting the engine.
This is why I follow with high interest the work clang guys are doing for the committee.
[1] http://www.w3.org/TR/WebIDL/
[2] http://jstenback.wordpress.com/2012/04/11/new-dom-bindings/
(But maybe I'm missing something. What exactly is the role of the Unreal Editor in UE4? Is it mostly for things like graphics and sound?)
EDIT: OK, so apparently UE4 has something called Blueprints. I'm still not exactly sure what they are, but people in the thread are saying that they're superior to C# in Unity, and that they can even allow you to make a game without knowing how to program. So why is Tim Sweeney saying that C++ is replacing UnrealScript for gameplay code?
You can also mix up the blueprints and C++.
At this point Unity only really has simplicity because of the lack of features, and well if people are too scared about C++.
Blueprints are a weird entity, not really code, but definitely similar.
I think the major difference is they are heavily event based and probably have severe performance restrictions placed on them, which is the major difference. If you want a real time-slice you need to do it in C++.
It also had some clever ideas about replicating state across the network. You want to run the simulation locally to reduce latency, but you also need it to run elsewhere to have a consistent source of truth. So some state would be calculated by the local simulation, but be updated when packets of truth arrive. Member variables could be annotated according to how they were replicated, IIRC.
It's also possible to recompile UnrealScript files without recompiling the whole C++ program which took quite a while. (Unreal4 allows hot reloading C++ so this is no longer an issue.)
Ultimately it was confusing at first. Besides the official docs I had to look at their script source and old UT2 tutorials to figure it out.
I haven't seen the new engine, but I imagine simply doing a net.replicate(&player_info) would have been more straight forward than dealing with all of the unrealscript language constructs for it.
What's most distressing is people (as normal) are completely ignoring the garbage collection overhead, which is mostly where the advantage of having complete control is, in terms of micro-managing your memory allocation, e.g. using slab allocators, memory pools, pre-allocation, etc.
C# code in theory (ignoring things like intrinsics support and inline asm) can be as fast as C++ for tight loops, but in my experience (writing 2D/3D software for the VFX industry) +85% of the time if you profile something, it'll be the memory allocation which is killing things performance wise.
GC-based languages run games on many, many platforms. The problem, imho, is that you have to leave 90% of the language features on the shelf when you're doing your main loops in order to avoid triggering the GC.
The gaming industry is practically begging for a language like Rust.
You can essentially choose and manage how you want to deal with memory by virtue of how you choose between native and managed types, as well as control the behavior of the garbage collector itself.
Replace "C++" with "JavaScript/client-side processing" and "script" with "server-side scripting" and I feel like this adequately describes web-development.
Attempts to hide this have thus far all been leaky abstractions, and we're still in the research phase (e.g. Meteor). I'm not convinced that it will be possible to create a coherent web environment which abstracts the server-client boundary effectively.
Note that this doesn't preclude e.g. using Javascript as a server-side language. That's not related.
At least that is how it feels for guys like myself that lack designer skills.
FTFY
On a serious note:
I've recently started using Clojure on the backend and ClojureScript on the frontend and while its not quite 100% of the way there, its close and quite pleasant to work with.
Seems an appropriate term here.
I think the more effective solution for most developers will be to keep all the engine-adjacent code in C++, but integrate a scripting engine of your choice just with your game logic.
Also, I found this comment from later in the thread really interesting about how easy it is for hackers to abuse the reversibility of managed (.NET) code. Granted, this could just be due to poor design on the developers part, but giving hackers that kind of insight into the games design cannot be helping anything.
So with that, This could be a unity issue, mono issue, or just the game developers issue. For background reasons, I am a local memory hacker, tho the reason I am here on UE is im teaching myself how to develop games not for hacking purposes. The game im going to talk about is the only game I know that is MP only and uses the unity engine. This game has one big problem when it comes to hackers, what we do is simply edit the .net dll's to manipulate the game, no hooking, no debugging, no working out functions in assembly, it also meant that we could reverse the source to pretty much 100% usable source - this resulted in the end user being able to change things like the user id to stop them being able to be banned. Un-Ban able hackers? its destroying this game. [1]
[1] - https://forums.unrealengine.com/showthread.php?2574-Why-C-fo...
[Edit] Is Unity's approach drastically different in a way that solves them?
I think this article is going to push me to strip out the embedded Lua from the engine and use plain old C++ as well. Great read!
In general, it seems to be a red flag if a single thread of logic runs back and forth between host and scripting contexts.
Or something else.
Honestly, having too much scrip is very much a thing to try and avoid. Perf (gc, etc), long term maintainability (usually no static types in script), tooling (frequently no debuggers, profilers, or the ones that exist are low quality), etc are all reasons for this.
If you're going to have 90% lua, you probably should be writing the game in lua anyway...
What I would have liked to see was better support for interactive development. When I was playing with it I still had the edit/save/compile/run loop to see my changes, that's not what I want. I use Common Lisp extensively and appreciate the power of a REPL, it's available in many languages now. This brings me to the next point of providing powerful reflection support so that I can easily explore the state of the application, as well as better debugging tools.
UnrealScript was just not a very good language implementation in my opinion.
So in my opinion removing it and exposing the C++ is a good thing. But not for the same reasons that most are touting, I don't want to code in C++. But now it should be a lot easier to build a reasonable Lisp on top of Unreal Engine 4, which is what I really want.
Now, to make the gameplay programmer and level designer's job easier, you still have to build well made and documented building blocks.
It's either that or hire people who do know how to talk a statically typed language. Might be good news for the job market, I always found it weird to have people making games who were not really competent in programming. always baffled me.
I guess you can still force people who are unable to write good c++ to write c++ anyways and hire somebody else to valgrind everything. In the end using a statically typed language is more a requirement for performance, clarity and consistency than a lack of flexibility.
Setting the bar high or demanding discipline if you prefer. Computers are stupid, so you need to be precise when you work with them.
Heck it even allows live preview of how it is executing.
> It is ... more dangerous than UnrealScript, C#, and JavaScript. But that is another way of saying that it's more powerful.
is why we can't have nice things.
Quake 1 ran a virtual machine, which QuakeC compiled down to. Quake 2 ran native code via DLLs. Quake 3 ran either VM code or native code--depending on how clever you wanted to be, you might need to break into the native code.
There wasn't some "mistake" about using scripts-vs-code, because they would actually compile down to executable bytecode. This made it much easier to load mods over the network if you needed to, and to port their tech to different architectures.
idTech 1-3 internally were a lot more like VR operating systems than scriptable game engines.
and the limitations of scripts, outweighs any gains from being "easier to edit"
Wrong, wrong, and wrong. The mod scene flourished back in the day specifically because it was so easy to bodge together mods in these friendlier environments--especially in the Unreal series.
The place where scripting falls apart is in modern AAA games where way too much stuff is expected of/exposed to designers, and then you end up with gigantic sprawling piles of poor performance. A friend worked with a licensee of the Unreal engine for a few games, and their script dispatch switch went on for...well, let's just say that many good programmers lost many good hours in those mines.
Scripting is a perfectly good tool, and one that makes sense until you start doing crazy AAA stuff with it.
But maybe the UnrealScript wasn't simple enough in practice to do that?
Or perhaps, giving people a template function, and a few functions, is just as easy/hard as a separate scripting language.
Generally, scripting languages are a really great idea: consider all the bash scripts in unix. An imperfect mismatch with the underlying language, yes; but worth it.
I also know of a few game development studios with R&D departments who have had an eye on Rust in the past, though I bet it will be a long time before a major studio is willing to make the risk that writing an engine in Rust would entail.
I know it's not your point, just providing some answers in case someone is wondering.
# compute average line length
var count = 0
var sum = 0
for line in stdin.lines:
count += 1
sum += line.len
echo "Average line length: ",
if count > 0: sum / count else: 0
Type inference ensures that this uses efficient types internally, and is compiled to something very close to the efficiency of C. Here [2] is the generated C code, minus line tracing and stack trace frame generation. (Nimrod does things like bounds checking and overflow checking; without them, the program obviously becomes faster; the AddInt() function, for example, is replaced with a simple "+=".)As for Go, it's not very well suited to this domain. Or at least no better suited than Java or C#.
Indeed. Most of the trail blazing work is being done by professional game devs in their spare time, hobbyists, indies and students who are willing to endure the pains of early adoption for the incredible gains that Rust gives them. It will only be post 1.0 however that the bigger players will be able to even consider using Rust. It's too risky to bet an entire company on – and that's coming from somebody who is willing to bet their indie project on it. ;)
- No null pointers (with an Option type that compiles down to a nullable pointer)
- Data race free concurrency
- Zero cost abstractions
- RAII and destructors
- No exceptions
- A modern, highly expressive type system
- Generics that throw type errors at the call site, not deep in a template expansion
- True immutability (not `const`)
- An excellent C FFI
- Compiles to native code
- You don't pay for what you don't use
- Safe by default, but you can circumvent the safety checks if you know what you are doing, and those places are clearly marked and easy to audit. Inline assembly is supported.
- Most safety is enforced statically, so you don't pay for it at run time.
If so, I believe Nimrod's support for immutability gets you pretty far, but I have not looked very deeply at it. For example, implements an explicit "IO taint" mechanism reminiscent of Haskell.
Doom3 seemed a bit of an odd duck.
On the projects I worked on script performance wasn't too much of an issue, the scripts were used to control state and flow, not to do any number-crunching.
If you control what is exposed to the scripting then engineering will know when design asks for access to something that should be implemented outside of scripts. Which in my experience seemed to have minimized performance issues.
One concerns when switching over to a development system such as Unity or Unreal 4 is that your gameplay 'scripts' have access to the entire engine. It seems very easy at that point for your game to turn to unintentional spaghetti.
What I was trying to say is that, while its not as fast as JIT or AOT, it is still extremely fast (many times faster than the reference Lua implementation apparently).
Sounds definitive, interesting considering that in some cases languages like OCaml outperform C++.
C++ does not mystically provide good performance. Knowledge of algorithms and appropriate data structures are far more beneficial.
Does the coder know how to come up with something like http://en.wikipedia.org/wiki/Fast_inverse_square_root or the cost of a hash map verses indexed array lookup. These will win you far more than any language choice.
Many languages can outperform C++ in some cases and this language it would not be my choice for a next-gen game engine either (D would be).
The thing about going native, however, is that you also control the memory layout of these data structures easily to lower cache miss rates. You can write highly performant code in all the cases where its needed. For a game engine that's very likely to be most of the sub-systems updating the game objects and interfacing with the hardware. Writing native code makes it pretty straightforward once you learn to structure your data for cache locality and prefetch the memory when you know its going to be needed. Video game engines are chock-full of use-cases for this.
I'm mostly a functional programmer now and I do love referential transparency. It's perfect to reason about the logic of our programs and has completely changed how I view software now. But the tradeoff for this is that we lose the ability to easily reason about the execution speed of our programs without deep knowledge on how it gets compiled, and is usually dependent on the compiler vendor.
For real-time applications crunching tens of thousands of objects 60 times a second running sometimes on sub-par hardware written by armies of programmers straight out of Java-School, this makes C++ a no-brainer.
Mostly as a way for young generations to finally grasp GC/memory safe != VM, as they seem to have been brainwashed since Java became widespread.
Though in this context it's relevant to point out pretty much any implementation will stack allocate them, it's not accurate to say C# has stack allocated objects.
[1] http://blogs.msdn.com/b/ericlippert/archive/2009/04/27/the-s...
Sticking with C/C++ also means you'll be able to more easily port your game to any platform of the future. It's just a safe bet that any future platform (either hardware or software) will support C/C++. Especially for games. Anything else is a dice roll. Sometimes a roll worth taking, but still a roll.
For me, Mac/Win/Linux/Android/iOS support is more than enough, and I think it will be enough to bootstrap the language into some level of industry acceptance. It depends on the project really, and how much the developer values console support over the benefits that a modern systems language like Rust provides.
I really need to get around to writing a blog post to explain this in detail since this misapprehension is endemic. Python and JavaScript do not have coroutines, they have generators. Lua has actual coroutines.
The latter is dramatically more expressive than what you can do with what Python, JavaScript, and C# offer. This mistake drives me crazy because it means people don't know what they're missing.
Here's a quick example. Let's say we've got a little Python class for binary trees:
class Tree:
def __init__(self, left, data, right):
self.left = left
self.data = data
self.right = right
We'll add a method to do an in-order traversal. It takes a callback and invokes the callback for every data value in the tree, like so: def walk(self, callback):
"""Traverse the tree in order, invoking `callback` on each node."""
if self.left:
self.left.walk(callback)
callback(self.data)
if self.right:
self.right.walk(callback)
We can create a little tree and then print the data items in order like so: tree = Tree(Tree(Tree(None, 1, None), 2, Tree(None, 3, None)), 4, None)
tree.walk(print)
(This works in Python 3, in Python 2, you'll have to make a little fn for print.) Swell, right?Later, we decide we want to iterate over the items in a tree. Easy-peasy, Python has generators! We can just make a function that takes a Tree and returns a generator. We already have a method to walk the nodes, so we just need to call that and then yield the items, like so:
def iterateTree(tree):
def callback(data):
yield data
tree.walk(callback)
Then you can just use it like so: for x in iterateTree(tree):
print(x)
Perfect, right?Actually, no. This doesn't work at all. You can't yield from the callback passed to walk. That's because walk() itself doesn't know that the callback is a generator.
This is the problem with generators: they divide all functions into two categories: regular functions and generators. You run a regular function by calling it. You run a generator by iterating over it. The caller must use it in the correct way.
At its simplest level it means you have to be careful when refactoring. If you have a generator function that gets too big and you want to split it up, you have to remember that the functions you split out are also special generator functions if they contain a yield. You have to remember to flatten it when you "invoke it".
It's more than just annoying though: it means it's impossible to write code that works generically with both kinds of functions. In other words, all of your higher-order functions like map, filter, etc. now only work with some of your functions. (Or, I suppose, you could explicitly implement them to support both but that's more work and I don't think most languages do.)
In languages like Lua, the above code just works. You can yield from anywhere in the callstack and the entire stack is suspended. It's fantastic.
(If I can be forgiven a bit of self-promotion, I'll note that my programming language Wren[1] can not only express full coroutines like Lua, but also supports symmetric coroutines which can express some things Lua cannot. They are roughly like the equivalent of tail call elimination for coroutines.)
I realized this limitation one day while trying to do it in python. You cannot just yield another stream.
SuperCollider has proper co-routines: http://danielnouri.org/docs/SuperColliderHelp/Core/Kernel/Ro...
and the pattern library is built entirely around embedding in streams and yielding others streams. it uses this for very interesting numeric music patterns.
Python 3.4 also now has co-routines: https://docs.python.org/3.4/library/asyncio-task.html
esp this is interesting:
result = yield from future – suspends the coroutine until the future is done, then returns the future’s result, or raises an exception, which will be propagated. (If the future is cancelled, it will raise a CancelledError exception.) Note that tasks are futures, and everything said about futures also applies to tasks.
result = yield from coroutine – wait for another coroutine to produce a result (or raise an exception, which will be propagated). The coroutine expression must be a call to another coroutine.
Javascript does/will have simple generators
CurrentCoroutine = None
def run(main, arg):
global CurrentCoroutine
CurrentCoroutine = main
while CurrentCoroutine is not None
CurrentCoroutine, arg = CurrentCoroutine.send(arg)
def corodecorator(coro):
@functools.wraps(coro):
def init():
c = coro()
c.next()
return c
return init
And this is pretty much it. A simple example for two coroutines that pass control to each other would be: @corodecorator
def coro1():
# yield nothing on first call to receive args
arg = yield None
friend = arg[0]
while True:
print('coro1')
arg = yield friend, (CurrentCoroutine,)
friend = arg[0]
@corodecorator
def coro2():
arg = yield None
friend = arg[0]
while True:
print('coro2')
arg = yield friend, (CurrentCoroutine,)
friend = arg[0]
run(coro1(), (coro2(),))
You can do the same with javascript and events, but it requires a much higher degree of masochism.Think of it like this. Let's say you have a chunk of code that you want to refactor out into a separate method. The usual way to do that is to pull it out into a separate method and then call it from the place where the code used to be inline.
In languages with coroutines, you can just do that, regardless of what's in that chunk of code. In Python, you have to think, "Oh, does this chunk of code contain a yield?" If so, you need to do a "yield from" the function you pulled out instead of a regular call.
It forces you to constantly be cognizant of and design around the split between normal code and generators.
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n267...
Currently Visual C++ is the only C++ compiler offering support for it.
As for Unreal, I only know it has one by reading game development foruns. I don't know what type of control it offers.
The GC in unreal engine is used to know when what entity should be removed from the game. (If you have multiple entities pointing each other.)
The memory allocation still works how it works in C++.
Thanks for the clarification, as I only knew there was a GC from gamedev articles, forums, without much details.
No exceptions is my only objection, but I know they're dubious for a systems language.
Regarding exceptions: whilst they can be be very useful, unfortunately a significant number of large, performance sensitive C++ projects outlaw them due to overhead and safety concerns (the semantics can become quite hairy when mixed with destructors). The Rust developers felt that it was easier to forgo them entirely.
My understanding is that the old exception code called "SJLJ" (short for setjmp, longjmp, which is what it was) was slow. I think each try/catch required hooks, and yes, it was.
The newer compilers generate something called "DWARF"; resources on it are unfortunately scarce, but my understanding is that you don't pay anything in speed for an exception until you throw one. (You do however pay a bit of disk/memory for data about where try/catch handlers are, I think.)
> safety concerns (the semantics can become quite hairy when mixed with destructors)
I'm assuming that you shouldn't throw in a destructor.¹
This argument, to me, always needs more information attached to it, because by itself it's meaningless. Assuming the alternative is returning either the result, or an error code, you run into exactly the same semantic issues, you're just handling them manually now. Is that better, and how?
In the manual case. If I assume I have some code that returns to me an error code that I can't handle, I need to propagate that error up to a stack frame that can. Thus, I begin to manually unwind the stack, during which, I destruct things. If we're assuming destructors can throw², then I can potentially run into the problem of having two errors: now what do I do?
C++ isn't the only language here: C#, Python, and Java share the problem of "What do you do in the face of multiple exceptions requiring propagation up the stack?", though I think C++ is the only one that solves it by terminating the program. I believe C# and Python just drop the original exception, and I have no idea what Java does. Honestly, if things are that effed up, terminate doesn't sound that bad to me. In practice in C++, most destructors can't/don't throw. (Files are about the hairiest thing, since flushing a file to disk on close can fail: C++'s file classes will ignore failures there, which doesn't exactly sit well with me. You can always flush it manually before closing, but of course, if you do this during exception propagation and throw on failure, you risk termination due to two exceptions.)
Even C has this, in that if you're propagating an integer error code up the stack, and something goes wrong in a cleanup, you've got this problem. In C, you're forced to choose, of course, including the choice of "ignore the problem entirely".
That said, I'll add the answer for Rust here. (I've never used Rust, so correct me if I'm wrong. I'm going abstract away the Rust-specific types, however.) Rust, for a function returning T but that might fail, returns either Optional<T> or a Result<T>-ish object, which is basically (T or ErrorObject). Rust has strong typing, so if there's an error, you can't ignore it directly, because you can't get at the result. And if you try, it terminates the "task". Strong typing is the winner here. (This reminds me why I need to look into Rust.)
¹It's not illegal to do so, but since destructors get called while an exception unwinds the stack, you can potentially run into an exception causing an exception. Two exceptions in C++ result in a termination of the program.
²If we're not, then exceptions are perfectly safe.
Remember not everybody has the luxury of working on a system where you can spawn 10,000 threads without breaking a sweat.
However this territory of literally hundreds of thousands of programmed agents participating in a game does not seem to be very populated. Perhaps part of the reason is that very few languages had efficient (this eliminates stack copying), scalable and portable support of coroutines. This is starting to change, but not as fast as I would like.
I think C is to be blamed for the long under appreciated status of coroutines. It is one abstraction that C left out, although the VM C had as its execution model (the PDP) had excellent support for coroutines at the instruction level. C exported pretty much every abstraction of the underlying instruction set, but not coroutines.
EDIT: @VikingCoder Replying here as HN wouldnt allow me to respond till some time has past. Yes I have looked at asio although just scratched the surface. It looks very interesting, as far as I know they are not threads though (which is a good thing), they use macro and template metaprogramming trickery to turn producer-consumers into one big switch case. If you interested in coroutines and seamless interaction with C++ I can recommend http://felix-lang.org
My hunch is that the designers of C would have said "goto" and "switch" cover the use case where you have a bunch of peer chunks of code that you want to freely bounce between.
Remember, at the time function calls were considered expensive, so not support full coroutines across function call boundaries may not have been on their minds as much.
Quoting the most interesting bits from that thread, (although I urge you to read the original):
Of the many styles of subroutine calls on the PDP-10, JSP ac,addr is the fastest,
as it's the only one that doesn't require a memory store.
Its ISP is something like:
ac = PC
PC = effective address [addr in the usual case]
The subroutine return, of course, is:
JRST (ac)
Here, the efective address is the contents of the register.
The coroutine instruction combined the two:
JSP ac,(ac)
This essentially exchanged the PC with ac.I'd need one lock per actor thread and its communication object.
I say again, this works in my problem domain, and probably wouldn't work in other domains.
Overall, it has been fun reading on all the variants of this idea.
side note: in my first job, there were a few PDP-11s in the lab that I was responsible for. We never turned them on though.
Also, the PDP 10, which you mention above, was one of the most revered machines by hackers.
[1] http://en.wikipedia.org/wiki/Battlefield_4#Technical_issues_...
The security track record of applications written in C++ disagrees with you.
Modern C++ is really safe if you use the subset that involves automatic storage duration, well bounded arrays, etc and use all the warning flags of your compiler, run static analysis, have a robust test framework, etc.
That is really important, but still, wouldn't it be better if you could encode at least some of those good practices into the language itself, rather than relying on humans to be constantly on their game? I'm certainly not perfect, so I would rather my sloppiness be caught earlier rather than having it come back to bite me in the future. See: http://thecodelesscode.com/case/116
The fact that applications like browsers and operating systems (which are known to be high value targets) have a lot of effort & resources put into security but still have attack vectors makes the "C++ is secure" position fairly indefensible.
I've posted a list of the things I consider the most relevant to game development: https://news.ycombinator.com/item?id=7587413 Any one or two of them alone wouldn't really be a compelling enough reason to switch, but put together they form a very compelling value proposition.
If on the other hand the devs understand which classes of bugs aren't ruled out in Rust, then sure, you will end up with fewer bugs.
Consider iterator invalidation, null pointer dereference (which is undefined behavior, not a segfault -- and you can't get away from pointers because of "this" and move semantics), dangling references, destruction of the unique owner of the "this" pointer, use after move, etc. etc.
* Iterator invalidation: if you destroy the contents of a container that you're iterating over, undefined behavior. This has resulted in actual security bugs in Firefox.
std::vector v;
v.push_back(MyObject);
for (auto x : v) {
v.clear();
x->whatever(); // UB
}
* "this" pointer invalidation: if you call a method on an object that is a unique_ptr or shared_ptr holds the only reference to, there are ways for the object to cause the smart pointer holding onto it to let go of it, causing the "this" pointer to go dangling. The simplest way is to have the object be stored in a global variable and to have the method overwrite the contents of that global. std::enable_shared_from_this can fix it, but only if you use it everywhere and use shared_ptr for all your objects that you plan to call methods on. (Nobody does this in practice because the overhead, both syntactic and at runtime, is far too high, and it doesn't help for the STL classes, which don't do this.) class Foo;
unique_ptr<Foo> inst;
class Foo {
public:
virtual void f();
void kaboom() {
inst = NULL;
f(); // UB if this == inst
}
};
* Dangling references: similar to the above, but with arbitrary references. (To see this, refactor the code above into a static method with an explicit reference parameter: observe that the problem remains.) No references in C++ are actually safe.* Use after move: obvious. Undefined behavior.
* Null pointer dereference: contrary to popular belief, null pointer dereference is undefined behavior, not a segfault. This means that the compiler is free to, for example, make you fall off the end of the function if you dereference a null pointer. In practice compilers don't do this, because people dereference null pointers all the time, but they do assume that pointers that have been successfully dereferenced once cannot be null and remove those null checks. The latter optimization has caused at least one vulnerability in the Linux kernel.
Why does use after free matter? See the page here: https://www.owasp.org/index.php/Using_freed_memory
In particular, note this: "If the newly allocated data chances to hold a class, in C++ for example, various function pointers may be scattered within the heap data. If one of these function pointers is overwritten with an address to valid shellcode, execution of arbitrary code can be achieved." This happens a lot—not all use-after-free is exploitable, of course, but it happened often enough that all browsers had to start hacking in special allocators to try to reduce the possibility of exploitation of use-after-frees (search for "frame poisoning").
Obligatory disclaimer: these are small code samples. Of course nobody would write exactly these code examples in practice. But we do see these issues in practice a lot when the programs get big and the call chains get deep and suddenly you discover that it's possible to call function foo() in one module from function bar() in another module and foo() stomps all over the container that bar() was iterating over. At this point claiming that C++ is memory safe is the extraordinary claim; C++ is neither memory safe in theory (as these examples show) nor in practice (as the litany of memory safety problems in C++ apps shows).
I think it is a valid criticism of the language that not all non-primitive types aren't implicitly const, though. But you could never implement that without colossal backwards compatibility breakage. Which I guess is fine, since you could just keep a code base an std= behind until you fixed it.
> Use after move: obvious. Undefined behavior.
This I don't have an answer to though. I've always disliked how this isn't a compiler error.
Besides, isn't "C++ is memory safe if you don't use mutation" (even if it were true—which it isn't) an extremely uninteresting statement? That's a very crippled subset of the language.
Mutability in Rust is perfectly safe because of the static checks built into the type system – the compiler will catch you if you screw things up.
> you could never implement that without colossal backwards compatibility breakage
I cannot express how important immutability as default is. This prevents the issues that C++ has with folks forgetting to mark things as const. There is also lint that warns when locals are unnecessarily marked as mutable, which can catch some logic errors (I say that from experience).
Also note that I said 'immutability' not 'const'. Immutability is a far stronger invariant than const, and therefore is much safer. It could also lead to better compile-time optimisations in the future. I'm sure you know this, but just in case:
- const: you can't mutate it, but others possibly can - immutable: nobody can mutate it
Examples of things efficiently implemented entirely in the standard library in pure Rust (well, with some calls into the operating system/libc): Vec, the std::vector equivalent. Rc, reference counted pointers (statically restricted to a single thread). Arc, thread-safe reference counted pointers. Mutex. Concurrent queues. Hashmap.