Log in

No account? Create an account
C++ rants - 'Twas brillig, and the slithy toves did gyre and gimble in the wabe [entries|archive|friends|userinfo]

[ website | Beware the Jabberwock... ]
[ deviantArt | the-boggyb ]
[ FanFiction | Torkell ]
[ Tumblr | torkellr ]

[Random links| BBC news | Vulture Central | Slashdot | Dangerous Prototypes | LWN | Raspberry Pi]
[Fellow blogs| a Half Empty Glass | the Broken Cube | The Music Jungle | Please remove your feet | A letter from home]
[Other haunts| Un4seen Developments | Jazz 2 Online | EmuTalk.net | Feng's shui]

C++ rants [Friday 28th April 2017 at 7:50 pm]


I've recently been doing some C++ hackery, which has involved much ranting...

Did you know that

const int* foo;


typedef int* T;
const T bar;

are not the same thing? The first is a mutable pointer to a constant foo, the second is a constant pointer to a mutable foo. And for added laughs you can't do const T const foo and get a pointer to a constant foo - no, for that you either have to hope someone's done a variant of the typedef with const (like LPTSTR and LPCTSTR) or unpick the typedef and specify it manually.

The whole thing of const and pointers is bonkers anyway and gives such charming expressions as const int * const foo which my code is now full of, because making all your function parameters const is quite good for catching errors like using single-equals instead of double-equals in an if statement (similar to how current advice for Java is to make all your function parameters final).

Another surprise is that

FooType bar = FooType("something");

creates not one, but two objects. It creates an anonymous FooType instance, but doesn't assign it to bar like you'd expect. No, instead it copies (using the copy constructor) it into the bar variable. Except there's something called copy elision which means the compiler can just do the assignment directly and not bother actually making the copy. Except except if your class doesn't have a copy constructor, then the compiler's not allowed to use copy elision and instead throws a completely accurate and yet highly confusing "use of deleted function FooType::FooType(const FooType&)" message. The correct way to construct objects is apparently FooType bar("something").

Oh, because pointers aren't confusing enough, C++ added references for objects. So instead of passing a pointer to an object and having to use -> to access members you can pass a reference and treat it the same way as a normal variable. Except they're immutable so if you have one as a member variable you have to assign it in the constructor... and they can't be NULL, so you can't use NULL as a signal for "item not found" or similar. Oh, and you can't use new or delete with them. So that rules out a chunk of places where references would be really convenient.

Another rant is the lack of reflection makes some design patterns much harder than they ought to be. I wanted a class factory where you could ask it to create one of a slew of different subclasses (all inheriting from the same common base class). The obvious C++ implementation involves a massive switch table but that's a poor way to handle it as you have to make sure you keep it up to date - a much better solution is to have a map of names-to-classes and lookup the name in that when the factory method is called. Except that doesn't work in C++, because there's no way to invoke a class constructor by name. Eventually I found a horrid hack on StackOverflow involving using a function template to create (at compile time) per-class factory methods, and then a class template to create copies of the factory methods and register those with my name-to-constructor map. And to ensure the class templates exist they're referenced as static members of the actual classes, giving some very wordy blocks like

class BarType : public FooType

static FooRegistrar<BarType> registrar;
FooRegistrar<BarType> BarType::registrar = FooRegistrar();

because C++ needs to be told everything multiple times in almost but not quite exactly the same way. Oh and just to make things more interesting, at compile time all your static initialisers get shuffled together and end up running in an undefined order, so whatever map FooRegistrar() ends up using can't itself be statically initialised as it might not be constructed first (I fixed that with a singleton object and a GetInstance() method). Java just makes this downright trivial - have a static Map in your factory class, have your subclasses store class objects or even constructor objects in it, and then just create objects with newInstance() or invoke(). And everything happens in the correct order at program startup as well. The only slight annoyance is Java doesn't have a "thisClass" keyword but it's still only one place where you have to repeat the class name instead of 3.

Anyway, enough ranting for now, before I leave the core language and starting ranting about MFC and the Windows API...
Link | Previous Entry | Share | Next Entry[ Penny for your thoughts? ]