To sumarize:
- Ogre's use of distinct allocation policies through template class AllocatedObject made me delve deeper into the subject of policy-based class design, and once you enter that world you have to read Andrei's "Modern C++ Design: Generic Programming and Design Patterns Applied". This book has been the source of many of my "Wow!" moments so far.
- While I was reading Andrei's book, I stumbled upon his technique for achieving static dispatch on an integer constant, which I later discovered is used by Ogre::ExceptionFactory::create by means of Ogre::ExceptionCodeType. Kudos...
- The principle behind template class Ogre::Singleton is so simple, yet so powerful, that it literally knocked my socks off when I understood how it works. Even though it cannot assert the singleton's unicity from its interface, it is the best way I've seen so far to retain full control over its lifetime -- a must in systems like games. (There's one issue that I don't yet fully understand, though. Is the cast found in the constructor always safe? Isn't this an object of type Singleton<SomeType> at this point, even though its layout in memory may coincide with the one of an object of type SomeType? What if SomeType has a virtual function, and the vptr is (as many compilers do it) at the beginning of the SomeType objects, or SomeType inherits from other class besides Singleton<SomeType> and the latter is declared after the former?)
Still, there were some moments in which I wanted to poke someone's head because of design decisions that made my life harder. Yes, Ogre is a well-thought system, but I (modestly, let me not pretend otherwise) think there are things that can be done in order to help us (the users):
- I have to look inside the code to know what functions throw exceptions most of the time. That is annoying to me. Exception specifications should be part of the function's documentation. Note that I'm not referring to exception specifications as part of the function's declaration; those are useless most of the time. I can realize that would be a ton of work, but it would make Ogre more enjoyable (and secure) to use. It would also ease the challenge that is getting a piece of code to be exception-safe. Yes, Ogre's documentation does indicate the exceptions that a bunch of functions may throw, but some functions are documented as not throwing exceptions and are implemented in terms of some that may.
This all comes from the debugging I had to do in order to find the source of a crash that, according to my program's design, shouldn't occur, yet happened when the application was being shut down. It was only fifteen minutes later that I discovered that Ogre::ResourceGroupManager::clearResourceGroup throws an exception if it cannot find a group with the supplied name. I'm not questioning this decision at all, in fact I strongly believe that is The Right Thing To Do. The issue is that this happened after another exception had been thrown and the system was being correctly shut down (and yes, it was complex enough as to need clearing the group before final shutdown), and, as we know, two active exceptions is something C++ doesn't handle very well
- To my (unexpected) surprise, Ogre's "destroyX"-like functions have undefined behavior if the instances of X handled to them are equal to nullptr. My initial guess was that these functions would emulate C++'s operator delete behavior, which does nothing when presented with a nullptr pointer (pun intended). I know, it is only a matter of me checking the pointers before passing them to Ogre's "destroyers", but I think that's something the library could do. And when you wrap these objects with std::unique_ptr's, well... it doesn't feel right to insert the test inside every functor. (And again, the system is complex enough as to need destroying these objects long before Ogre::Root.)
- Build times are just huge, which basically shocked me when I first did a full build of a not-so-simple Ogre-based program, because the library does a terrific job at using abstract classes and the "program-to-interfaces-not-to-implementations" principle. From what I've seen, the main cause of this is almost every file including the header OgrePrerequisites.
- Once the different compilers catch up, consider eliminating Boost as a dependency, in favor of C++11's <chrono> and threading API.
Long preamble, final thoughts: Ogre is enjoyable to use, you can learn a big deal just by trying to figure out how all of its components fit together, and thank God for its active community.
You are doing a great job, guys.