Jump to content
  • Advertisement


  • Content Count

  • Joined

  • Last visited

Community Reputation

5396 Excellent

About SmkViper

  • Rank
    Advanced Member

Recent Profile Visitors

The recent visitors block is disabled and is not being shown to other users.

  1. SmkViper

    C++ exceptions

    I know it wasn't pro-return codes/anti-exception. Simply pointing out that if you're going to make a dynamically linked library, C++ exceptions cannot cross the API boundary, so they aren't really an option for APIs.
  2. Not necessarily. It's a lot easier if the static types of all the interfaces necessary are available, but a library that implements the IDispatch interface can be dynamically interrogated and manipulated. Both libraries need to know about COM automation, but they don't necessarily need to know about each other. For example, this is how scripting support for new components was implemented in Office (up to around 2003, no idea if later versions of Office changed their inner workings). If you added a new charting widget to your worksheet, Excel wouldn't know about new interfaces it added over the base OLE stuff, but you could still manipulate them through VBA because the charting widget would implement the IDispatch interfaces VBA would use to make the actual calls.   In which case the "common parent" is IDispatch. A pre-defined interface that both compile against. My point still stands. Someone needs to define a common API somewhere.   You may want to be a lot more clear about what you mean by "C++ cannot be used" as it's pretty obvious that people do write dynamic libraries in C++. Again, many COM DLLs are written in C++. Right now your statement only makes sense if you already know what it means.   Fair enough. C++ does not define a standard binary interface. In other words, C++ compilers are able to produce any binary structures they want to, as long as the produced binary code follows the C++ standard, and any calls to external functions and entry points into your code follow the OS's calling convention. (C++ compilers don't have to follow any calling convention internally - the obvious example being inlined functions which aren't called at all) For example, the C++ standard does not specify how virtual calls are performed. Most compilers will do this using a table of pointers to functions known as a "v-table", but nothing in the standard dictates this. Even if two compilers both use a v-table, no standard says where to put said v-table in the object's memory, or the order functions appear in the v-table. Because of this, code compiled with C++ compiler A cannot talk to code compiled with C++ compiler B. And compiler vendor A could change their mind between versions, changing things again, so you can't even use code compiled with different compilers from the same vendor. In comparison, C defines a standard binary layout for its structs, arrays, and the like, as well as things like not mangling function names. Because of this, C code compiled with any compiler, can talk to C code compiled by any other compiler. Therefore, at the API level, where your library exposes functions and data types to be used by external programs, you cannot use any C++ features. Functions must be defined under C-type calling convention, cannot have any overrides, and cannot be members of structs. Classes cannot be exposed. Exceptions must be caught and turned into error codes or something similar. And so on. Nothing prevents you from using C++ (or C#, or Python, or whatever else you want) to actually implement the internals of your library. You are simply restricted to using C as the public API. Otherwise other code cannot utilize your library. Of course, if you are making a static library, you can use whatever you want, because the end user will be compiling the library themselves, so everything will match up properly.
  3. SmkViper

    C++ exceptions

    Side note on your side note - most APIs like DirectX must work across a variety of languages and utilize DLLs, in which case a C++ API (and therefore C++ exceptions) cannot be used. Their only option is to use return values and/or out parameters, because those are the only types of error handling that are generic enough to be called from everything that might use the DLL. Note that this doesn't prevent the libraries from using exceptions internally, or from users of said API to wrap the error codes in exceptions, just that exceptions cannot cross the API boundary.
  4. To be fair, each library still has to know about the other in COM because they have to have access to the interface definition. If two libraries want to talk to each other they have to at least agree on something. For static libraries, you can take advantage of C++ mechanisms. For template classes with mix-ins the "duck typing" templates do let two classes or functions talk to each other using only names. You can also use interfaces and virtual dispatch, but either each library needs to agree on a common interface (that both will compile against) or you'll need to write an adapter class that can convert one library's interface to the other library. For dynamic libraries your options are far more limited, as C++ cannot be used. (Insert arguments about how C++ can be used under special conditions A, B, or C) In most cases you'll be dealing with C APIs and things like function pointers.
  5. SmkViper

    C++ exceptions

    You're implying that such a comment even exists. From my experience with "production code", documentation comments tend to be uncommon, especially in legacy code. Documentation that is correct is a rarity in and of itself. I've come across many "documentation comments" that belong in the "Best Comment Ever" thread. I've also worked under coding guidelines where not including documentation comments at all was the preference specifically because of how easy it is for the comments and the code to become desynchronized. Most of the time, I end up going and reading the function itself if I need to know how it can fail. Most importantly, the compiler can enforce a return type. (Though the language lets you ignore them) It cannot enforce comments.
  6. SmkViper

    C++ exceptions

    Considering some of the "production quality" code I've worked with, I wouldn't put it past the implementer to hide a forced null pointer dereference crash on error. But now I'm being pedantic, and perhaps a little bit bitter with having put a lot of effort into error handling and reporting only to have the users completely ignore it 99% of the time and then come to me wondering why their stuff isn't working. On the upside, I always start such conversations with "Did you check the log?" which makes finding and fixing the issues a matter of a few minutes.
  7. Also keep in mind that most ISPs (at least in the US, but I imagine elsewhere) frown at running servers on standard service contracts, and so you may be subject to having your account canceled if they detect it. But check with your ISP and contract to be sure.
  8. And then there is Delorian locality, where once you read 88 bytes the cache is instantly filled with data from 1985.
  9. SmkViper


      This one isn't all that great, in my opinion. It is very short and rather shallow. Which is the entire point of the book. It's supposed to be a basic overview of C++ that you can read in a few hours, not an in-depth explanation of every feature. As stated, it's for people who already know how to program, or know how to program in old C++ (pre-C++11) and want to learn what "Modern" C++ is all about.
  10. The easiest approach is going to be doing what a lot of older games did and make sure each map is self-contained with "doors" between maps. Then when the player clicks the "door" they are teleported to the corresponding map at the other side of the "door". This makes everything much easier as you don't have to worry about maps fitting seamlessly together, or having up to four maps loaded at once, or (the real kicker) trying to handle moving objects crossing map/coordinate boundaries. Otherwise it looks like you already have them meshed together just fine - you'll just want to offset each map by half a tile up and to the left (though for maps on the boundaries you will only want to shift them up or left, not both) to close the seam in rendering (though again, this won't address issues you might have with moving things crossing maps and potentially extremely large coordinates)
  11. Only had time to skim over the readme for now, but I can answer the above two.I would like an editor or compiler for the script. JSON is nice and easy to parse, but for me it's a nightmare to write. At least XML has schemas which an XML editor can use to provide auto-completion and basic syntax checking, but JSON doesn't have any validation system. Plus, JSON has no comment support (though depending on the reader you use you can enable it). I'm not saying throw away JSON, but I would want a tool or compiler from something easier to use that produced JSON before releasing it to users.Sure, I'd experiment with it. Always looking for new ways to do things, and, having written a scripting language myself, I'm always interested in seeing how others approach the same/similar problemsEdit (after looking over the code and scripts):Your bindings code seems to require a lot of "grunt work". This could easily be alleviated with template/macro work however.Seems like you only allow very few parameter types (one double, two doubles, one unsigned) which seems like it would potentially be an even bigger restriction. Having some kind of variant array for parameter passing combined with vararg templates for unpacking and dispatching could remove this restriction.Found the code a little hard to figure out - not because the logic was hard to follow, but I think because I had trouble figuring out the naming since I have pre-conceived notions of what properties and entities are, which doesn't seem to apply with this.I would never want to script in this language as-is. The JSON is far too wordy and feels like someone basically mangled assembler in a hard-to-read and hard-to-write format. I basically have to write four or five "lines" of code for what would be a single line in assembler. Fine for the person making the language, but a user is going to want some kind of compiler with a more readable and easy to remember syntax.I'm not sure what this gives you over a more imperative scripting language. It seems to do what other languages do - handle events to produce values and call functions in the native code. And if I wanted to do that I'd pick something I could easily read and write with a larger team behind itTLDR: I wouldn't want to write scripting in JSON, the binding system is too limited for my usual use cases, and it doesn't seem to provide anything unique that other more-established languages can't already do. So I would put some effort into making the binding code easier to work with, and add a compiler or editor for the language so I never have to see any JSON. Finding a unique niche for your language only matters if you're trying to "sell" it Best of luck with it though, making new languages is always fun
  12. SmkViper

    static variable in function

      Even without the mutex, wouldn't that be likely to be a pretty bad idea? In the best case, GetInstance would return a half-way constructed RenderManager, in the worst case, it would become an infinite recursion.   Is it even defined in C++ what would happen? (I have no idea, if someone knows, please do tell ) I guess the question is, will the return-value from "new" be assigned before or after the constructor is run?   My unqualified guess is "after" which would mean an infinite recursion. Yes, you don't want to use this type of singleton and call QInstance inside your constructor. It would work if you had an externally created/destroyed singleton where QInstance only accessed a pointer however. Though I've personally run into this deadlock problem when someone made the mistake of allocating in the memory manager's constructor. It worked "fine" on platforms that didn't implement thread-safe statics and immediately deadlocked on the one that did implement them. (Cause you redefine new to point at the memory manager, which needs to get the instance...)
  13. SmkViper

    static variable in function

    To hopefully put this in easier to read terms... The compiler does the equivalent of this (not valid C++ code):   static bool __renderManagerInitialized = false; static RenderManager& RenderManager::GetInstance() { //static RenderManager renderManager; if (!__renderManagerInitialized) { new (&renderManager) RenderMananger(); // placement-new if you haven't seen it, creates an object at a pre-allocated memory location __renderManagerInitialized; } return renderManager; } Depending on your compiler, it may or may not implement this in a thread-safe manner. C++11 requires thread-safe static variable initialization, but check your compiler to see if it supports it (i.e. MSVC doesn't support this until 2013 or later) Edit: Frob's points are also very valid - you should avoid singletons wherever possible, and, if you do use them, try to make sure you specifically set up the order in which they are created, usually via static Init/Shutdown functions on the class that manipulate a pointer that the QInstance() can return.
  14. SmkViper

    C++ exceptions

    Apologies for double-post - Hodgman responded and I knew it would take a while to work through a response, so didn't want to edit my previous. Of course. This is C++ exceptions Yup - oh well If that's a crashworthy error as you're suggesting (which in a game it should be -- missing assets should not ever happen), then you should be documenting the assumption that it can't happen with an assertion, not an exception. Every engine I've used has had an assertion system that does what you want here -- in development builds, it will halt the debugger on the line causing the problem, give you a string of what went wrong, but also (above your solution), it allows you to continue execution line by line from the 'crash' site if required. Actually, in the games I work on, failure to load most kinds of file logs an error and replaces it with a temporary asset - like bright pink textures or an error model. That lets the game continue working, but makes it obvious the asset is missing. Plus if you support modding (and why wouldn't you? ) your users will be providing assets and making mistakes, but won't be getting asserts. Also, I would say that file handling is exactly the case where asserts won't help you, because file integrity is something outside your control. Assert that you passed the wrong value into asin? Sure. Assert that a file failed to load? Not really... lots of things can happen to a file system to cause it to fail at runtime, even if you've completely tested everything and the file actually exists on disc (when the game was launched...) This feeds into your next point - file errors are "unlikely but possible". Not "never ever happens unless programmer made a mistake". I agree that this is one of C++'s exceptions main weakness - the reliance on documentation (which is "code that doesn't run" to borrow a phrase). However I do not think that mandatory exception clauses help (see: Java for how this is just a mess). I'm not sure what the solution is though. Are exception specifications runtime or compile-time checked? Are we willing to pay the cost for runtime checking? Is compile-time checking even possible in the presence of external libraries, function pointers, and virtual functions? How do we implement it without breaking all existing C++ code? And if they're not mandatory, what motivates people to write them? I don't think I've ever seen exception specifications in C# (didn't know that was even a thing). Maybe the best we can hope for now is tooling support. An IDE could theoretically trawl the code and display which exceptions the function is likely to throw, based on compile-time checks. It wouldn't be 100% accurate though - and is that then dangerous? Um... sure you are. Nothing prevents swap from returning an error code, or asserting. You could even overload std::swap for your own class to return an error code. I could also put whatever I want in the function and silently eat the errors, causing worse problems later on. Your choice of error handling mechanisms doesn't dictate where errors can occur, only how you report them. Is the argument that writing "throw MyError" is too easy? That the coder feels that they do not have to think about alternatives that a return value might cause them to? Maybe you have a point there. But then maybe the lack of exception support forces the coder to write sub-optimal code in order to support the non-exception method. I feel that this is more "when you have <blank> everything is a <blank>" - you get some new shiny feature and you start sticking it everywhere, regardless of the consequences. In which case your code reviewers better smack you upside the head There is a lot of work being done to actually make exception handling work well with threads and "job" systems with the various implementations of std::future and async coding. Typically this means that if the function throws an exception, it gets trapped and stored in the future, and the unpacking code will throw when you open the future for inspection (or you can query it if an exception is waiting). This gets you the callstack and most of the features. (I haven't looked into what happens if there is no one to unpack the future cause it's a fire-and-forget, but maybe you have to have a future for the task) Hacky? Probably. And you lose out on the callstack that queued the task in the first place (that might have passed in the bad data) but I'm not sure what else you can do there unless some mechanism stores the callstack when a job is created. It's especially annoying because the standard committee has only chosen to even acknowledge threads are thing since C++11, let alone start on making anything work with them. You can quite easily make a forgotten return code act just like a forgotten-catch by returning the error code in a RAII object that asserts in it's destructor if no one checks it's value -- i.e. make it crash at runtime.   I've seen engines before that are based around non-RAII C++ resource management (e.g. C-style initialization and cleanup), which have used similar techniques to catch class members that are "ownership"-semantic pointers that are not-null in the destructor, and to crash at runtime, in order to catch leak bugs. In cleanup, when freeing a pointer, you also had to set it to NULL to signal that you'd cleaned it up. Clever solution, and one that didn't occur to me. However you lose the callstack and stack state, because it's only going to fire when the object is destroyed, which could be quite a long time depending on how your blocks are structured. Oh sure, it's great if you can eliminate errors at compile time, and something that everyone should certainly strive for, but is not always possible. As to your iteration example, I'm sure that's one of the reasons the new range-for loop was invented, to make iteration much safer. However, if you'll allow me to pick apart your example a little, it's basically impossible (or at least incredibly difficult) to test every possible path and error, especially in the presence of (as you so rightly pointed out earlier) multi-threaded code. Perhaps someone else modified the array length while you were iterating over it. Even if you've designed the array to be thread-safe, the manual iteration or range-for version may be tripped up (though if you're using iterators you can design the iterator to fail when it detects the container has changed). And even if you thread lock the forEach function you now have to worry about holding the lock on the container while external code is running, which may lead to accidental deadlocks. Again, I prefer (where possible) loud errors. The problem with not checking is the silent memory corruption that takes days to fix. Yup - and it's great that C++ gives us the tools to do things like this. In fact, in your particular example, you could have used the new user-defined literals to catch it at compile time (assuming the string is a compile-time constant, so it doesn't catch runtime violations). And I agree that if you can avoid errors by changing your API, you should, absolutely. Like regular code - the fastest error checking code is the code that doesn't exist And I think what we can all take away from this is that error handling is f-ing hard!
  15. SmkViper

    C++ exceptions

    It gives you relevant information that isn't otherwise available with just a memory dump. Human readable error message, file/line number where the throw occured, often times additional information relevant to the issue at hand (ex. if I'm reading a file from an archive and the file is corrupted I need both the file name and the archive name). A crash rarely occurs at the actual point of the error, its often 2 or 3 function calls later where key pieces of information may not be available. Having a memory dump is nice, but a well formed exception is better, you know exactly the line and condition that caused the exception. edit: I generally agree with your synopsis of 'flag' bits as error codes. There are times where they are preferential; but I think they kinda of skirt the problem. I personally love exceptions and have used them successfully for a while now, and I'd never go back to error codes or similar, but there is one thing that exceptions do not do as well, and that is the 'sort of an error, but maybe not' error code. Kinda like the equivalent of S_FALSE, or NaN as you presented. In this case I think its best to pick the best tool for the job. Using exceptions for most things doesn't preclude the use of flag bits for the situations where they make sense. In this there is a large gray area. NaN I think makes sense, where-as with file streams I'm not a fan of the whole goodbit/good(), failbit/fail(), etc... In the former case (floats) having a sentinel value made sense, where-as having a file operation silently fail IMO is very bad. An uncaught exception still gives you that information because the OS traps it and dumps everything, including the uncaught exception, so you can see what happened. IOW - the OS/runtime already wraps your app in a "try {} catch(...) {}" so I guess I don't see the point in duplicating work.     If you weren't using exceptions, chances are good that you wouldn't be touching the hard disk in a constructor in the first place as a matter of policy. In fact, I'm not completely certain I like the idea of touching the hard disk - or doing anything that can fail - in a constructor even with exception support. No one mentioned a constructor. It could just as easily have been an open() call that threw. As to the whole "what should you and should you not do" in a constructor... that's a whole other kettle of fish. On the one hand you have people saying you shouldn't be doing a lot of work in the constructor - in which case you'd probably delegate to an open() function (which, of course, can report errors with return values as almost all OS APIs do). On the other hand, what use is a file object that doesn't actually point at a file? Or to put it more succinctly, why are we (usually) fine with doing allocation in a constructor, but not a "handle allocation" as it were? Is it because we generally expect file opening to fail more often than running out of memory? Not in a shipping game, I would think, as you should know exactly what files to load, so the only reason for failure is hardware failure or the user pulling the disk/drive.
  • Advertisement

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!