Flutterby™! : C++ sucks

Next unread comment / Catchup all unread comments User Account Info | Logout | XML/Pilot/etc versions | Long version (with comments) | Weblog archives | Site Map | | Browse Topics

C++ sucks

2007-02-14 22:23:44.125111+00 by Dan Lyke 14 comments

C++ sucks. What's the reason of the moment? Well, imagine you have a global array of strings (std::string, not char *), and imagine that you have a static object, and imagine that as your program exits the destructor gets called on that static object. What happens if the destructor has been called on the strings in that global array of strings, and that static object uses the strings in that array?

Especially given that destructor execution order seems to move around a lot depending on whether you're, say, compiling for debug or release modes. Half a day of digging through compile options to in XCode, Apple's abomination of a layer on top of the GNU tools, trying to figure out how to get it to output an executable that'll both give you hints and the right destructor order, and an hour or so of "sigh, where's the destructor order dependency this time?".

[ related topics: Apple Computer Software Engineering Macintosh C++ ]

comments in ascending chronological order (reverse):

#Comment Re: made: 2007-02-15 05:37:22.373835+00 by: Dave Goodman

You could create the string array and the static object dynamically, then you can determine the order they're destroyed in. We have a similar problem with global statics that don't get initialized properly when the game launches. So we make them global pointers and then create them dynamically.

#Comment Re: made: 2007-02-15 16:50:25.389833+00 by: Dan Lyke

The right thing to do in this case was to make this an array of char *s, however...

Yeah, the right thing to do in general is to control the object lifecycle explicitly.

#Comment Re: made: 2007-02-16 00:26:09.855005+00 by: ziffle

its time for "/murphy" mem manager

#Comment Re: made: 2007-02-16 00:35:02.403513+00 by: Dan Lyke

Yeah.

I'm also, after a discussion today, realizing that while language features are great, sometimes people let themselves be seduced into thinking that because a language makes certain idioms work well, those are the idioms they should limit themselves to.

If we were writing this in C, there are a number of places where we'd implement certain code structures because the language doesn't favor subclassing over delegates, but because we're writing it in C++ we moan about the lack of delegates without implementing an equivalent structure.

#Comment Re: made: 2007-02-16 01:28:34.231305+00 by: Dave Goodman

When I write a for loop, I think of the three arguments as init, test, change. i.e. for (init; test; change)

I just found this code in our game from a Russian programmer: for ( i = count; 0 <= --i; )

That's totally different from how I would write it... but it works, which most of the time in the game business is all that matters.

I'd write that for (idx=count-1; idx >= 0; idx--)

I love finding new idioms.

#Comment Re: made: 2007-02-16 15:02:32.224811+00 by: Dan Lyke

I like new idioms, but I like idioms that are likely to reduce the potential for bugs. Tests for >= 0 on anything int-like scare me because I've run into too many times where someone's gone all "unsigned" with that. So for much the same reason that I put the rvalue on the left side of the equation in equality tests wherever possible (ie: 47 == i rather than i == 47), I like to test for < cardinal rather than starting at the cardinal value and counting down.

Similarly, I've also seen at least two bugs in the code of someone who likes to do <= count-1 in his for loops because he got the tailing index off by one (usually by leaving off the equals sign).

However, I'd be interested to know how that idiom compiles. The count-1 in the init clause is undoubtedly costing you an extra decrement (or even a full subtract, which it would have back in the '80s), but the compiler should be able to turn the >=0 into a sign test, which may actually be faster than a comparison against the cardinal.

Aaaaaand, the C++ geek in me has finally been hammered down enough to say that "post increment and decrement operators could theoretically result in an extra copy constructor and destructor call". Which is one of the other reasons I hate C++.

#Comment Re: made: 2007-02-16 17:53:21.658616+00 by: ebradway

I've never been a big fan of relying on the compiler/interpreter to do too much for me. It's why I never really liked Perl. Too much magic.

That said, I am enjoying my first real foray into C++. It's definitely a better C. But I still wouldn't count on the automatic calls to deconstructors working in any particular manner.

(And I'd definitely rewrite the for (i = count; 0<=--i;) because for some folks it's not intuitive what is happening (and heaven help you if, as Dan suggests, i is unsigned...).

A good rule of thumb:

Not everyone reading your code might be as l33t as you, so write for clarity!

#Comment Re: made: 2007-02-16 18:12:31.171865+00 by: Dan Lyke [edit history]

So I actually wrote out some code to test that thing that I've been warned over and over again about post-increment and post-decrement operators, and it turns out it's hoohey:

#include <iostream>
using namespace std;

class SillyIncrement
{
public:
    int value;
    SillyIncrement() : value( 0 ) {}
    SillyIncrement( const SillyIncrement &toCopy )
        : value( toCopy.value )
    {
        cout << "SillyIncrement copy constructor called with value "
             << value << endl;
    }
    ~SillyIncrement()
    {
        cout << "SillyIncrement destructor called" << endl;
    }
    SillyIncrement & operator++()
    {
        cout << "Pre-increment operator called " << value << endl;
        value++;
        return *this;
    }
    SillyIncrement & operator++(int)
    {
        value++;
        cout << "Post-increment operator called " << value << endl;
        return *this;
    };
};

  
void DoSomethingWithSillyIncrement( const SillyIncrement &i )
{
    cout << "DoSomething called " << i.value << endl;
}

  
int main( int argc, char **argv )
{
    SillyIncrement i;

  
    cout << "First value " << i.value << endl;
    cout << "Calling pre-increment" << endl;
    DoSomethingWithSillyIncrement( ++i );
    cout << "Pre-increment value " << i.value << endl;
    cout << "Calling post-increment" << endl;
    DoSomethingWithSillyIncrement( i++ );
    cout << "Post-increment value " << i.value << endl;
}

Doesn't call the copy constructor, with our without the const in DoSomeThing...(...).

#Comment Re: made: 2007-02-16 22:09:07.125795+00 by: spc476

What do you think of D?

#Comment Re: made: 2007-02-16 22:20:14.902179+00 by: Dan Lyke

I want to look at it further. I'm inclined to believe that anything with Walter Bright[Wiki]'s name on it is likely to be somewhere between good and awesome.

And, to be fair, many of my complaints around C++ are of the "I used a sharp knife and it cut me" variety, there are lots of idioms that work around the sorts of bugs I end up tracking down at the "what order are static destructors firing in" level, and I'm starting to use them. It's just that C++ is either a better C, or a completely different language that you shouldn't treat at all like C even though it lets you shoot yourself in the foot if you really want to, and very few people use it in the latter way because it is so similar to C that they evolve C idioms to C++-like constructions.

#Comment Re: made: 2007-02-17 00:20:57.122102+00 by: Dan Lyke

Whoops: Addendum to that: Post-increment operators need to return a copy of the object, not a reference to it, and then all the bad things happen (which is pretty obvious at that point, because that's the behavior you asked for).

Never mind.

#Comment Re: made: 2007-02-17 07:43:39.940168+00 by: Dave Goodman [edit history]

I started programming in the mid-70's. Programming for personal computers back then meant if I wanted to divide by 2, I'd shift right 1 and if I wanted to multiply by 4 I'd shift left 2. I always optimized my code. Every byte counted, and the compilers weren't very good.

Fast forward 20 years, and my buddy, a former MS programmer, convinced me that the compilers were better now, and would optimize for me, sometimes better than me, and I should write what I meant and not play games like shift-right 1 when I wanted to divide by 2. I looked at the assembly of some compiler output and had to admit he was right. I write more clearly now, but I still think most programmers write bloated code. :)

When I learned to program, it was always "while (i > 0)" and not "while (0 < i)". I understand why, in later years, people were taught (0 < i) but it slows me down to read or write that. I haven't made a "while (i = 0)" error (instead of "==") in decades. Maybe when my mind starts to go I'll have to be more careful. Sad to think about.

#Comment Re: made: 2007-02-17 16:17:14.318004+00 by: Dan Lyke

It was sometime around 1997 when I could only beat the compilers by 5-10% by writing in assembly code that I gave up on assembly, or poring over the compiler output to see how I could alter the C code to beef up the assembly. Besides, by that point the different versions of the Pentium had changed enough that optimizing for one chip was pessimizing for another.

I too haven't made an "=" versus "==" bug in many years, partially because now the compilers warn you about 'em, but it's a habit that served me well once, and I'm loathe to leave it just for that.

But on some days I miss the level of using LEA to multiply by numbers which had two bits in them...

#Comment Re: made: 2007-02-17 20:04:30.670438+00 by: Mars Saxman [edit history]

But on some days I miss the level of using LEA to multiply by numbers which had two bits in them...

well, you could always take the career direction I did, and go into compiler development... then you get to worry about all that low-level stuff *for* everyone else.