The argument boils down to the fact that C++ can express more in a single line of code than C can.
a = func(b,c);
....
Is it a function call, a member function call, or is it an anonymous constructor? Are b and c implicitly invoking copy constructors for other classes as part of type coercion? Is that a normal assignment, or an assignment operator? Is there a cast operator involved?
If it's such a complicated expression, then the equivalent C code would be correspondingly large and hard to understand, and it would be just as complicated to figure out its cost. The not-so-hidden cost of C is that even simple things end up being many, many lines of code. C++ was invented because certain kinds of C programs -- programs that were already being written in C -- were painfully verbose to express and complicated to change.
If writing your code in C would be simple and clear, then you would have to be stupid to write it as complex, obscure C++. When your C++ code gets complex and you start banging your head against a wall, imagine re-expressing it in C. If the result would be an improvement, then you screwed up. If you blame C++ for screwing up, then you're a language feature junkie. Admit it and check yourself into rehab.
That is the hidden cost. The mental model for a simple function call became incredibly large and complex, and every function call is potentially as complex. Which makes reasoning about performance a rather hard thing to do.... All that translates ultimately into either worse performance or longer development time. Neither one is something you like to hear about.
I don't know how it is a "hidden cost," because it's quite well understood that any reasonably expressive language can say more in a single line of code than C. Anyway, the size of the mental model of your program is what you should be worried about. C++ doesn't give you worse performance or longer development time. It gives you more choices for how you express your program. It means more different problems, because you can write C-style code and have C-style problems, or you can use the possibilities C++ offers and have different problems. If you already know that the choices forced on you by C are usually the right ones for your domain, then by all means apply that knowledge to how you write C++. For instance:
Worse, it makes profiling harder than necessary. All the type coercions that happen at the API level will show up as separate functions, not attributed to the callee, but the caller.
If you want them attributed to the callee, it's pretty simple. Do the coercions in the callee, just like you would have in C.
Your counterargument boils down to "use C instead". That is exactly what I am currently advocating.
I don't blame C++ for screwing up - I blame it for being an ill-designed language that makes it easy to screw up.
Any of the high-level features of C++ are well-implemented in any number of decent languages that don't obfuscate your code and incur horrible link times. And my argument (for game development) is that C++ is indeed the wrong language for the domain. In fact, I'd argue it's wrong for most, if not all domains. And I'm not exactly alone - I can't recall any prominent figure that actually thinks C++ is a decent language, except Bjarne Stroustrup. (Correct me if I'm wrong - I'd love to hear about it!)
<blockquote>
If you want them attributed to the callee, it's pretty simple. Do the coercions in the callee, just like you would have in C.
</blockquote>
The issue is that in many instances, I own the caller, but not the callee. And the person owning the callee can unintentionally make my code perform worse by simply changing the API. Or adding a destructor.
No, my counterargument is to use the features of C++ when they help and not when they hurt. I claim that C++ doesn't hurt C programmers. It just brings out their deficiencies in different ways. For instance, embracing features you don't understand and then complaining about the consequences is stupid, and stupid people don't write good code in any language. You understand C++ well enough to know the pros and cons (or at least the cons) of various C++ language features, but you complain that your fellow programmers might not:
The issue is that in many instances, I own the caller, but not the callee. And the person owning the callee can unintentionally make my code perform worse by simply changing the API. Or adding a destructor.
This is just bad programming practice on their part. The performance cost of a destructor shouldn't be a surprise to the person who writes it. If someone is messing with the performance of types you use or removing functions(+) you call without consulting you and without themselves taking responsibility for calls to that functionality, then obscure features of C++ is just one of the many ways they're going to screw you on a regular basis. How would those programmers screw you in C? I don't know, but I know they would.
Nobody who knows C++ well enough to use it effectively would call it a "decent" language in the sense of "not obscene," but it is definitely "decent" in the sense of "adequate." It's easiest to define C++'s deficiencies with respect to other languages (for instance, memory management in C++ is much more intrusive in source code than in Java) but comparing it to C is way too simple. After all, the primary problem with C++ is that it doesn't remove any of C's dangers; it just gives you better ways of abstracting them away in some cases. The second problem with C++ is that it's extremely complicated and takes a long time to learn. And that's it. Every other supposed misfeature of C++ (relative to C) seems to stem from people hitting the second problem without realizing it. In your case, your coworkers should be more conservative about messing with things (such as defining expensive automatic type conversion functions) when they don't understand the consequences (such as those type conversions actually being invoked.)
(+) This is the most plausible explanation for different type conversions suddenly being invoked. It could also happen by adding a function that invokes a more expensive type conversion to a type that is more closely related to the type declared by the caller, thus making the new function a better match than the old one, but it's unlikely that a conversion between two types that are more closely related would actually be more expensive.
No, my counterargument is to use the features of C++ when they help and not when they hurt.
That was Stroustrups reasoning: "Only pay the cost when you use it". In retrospect, believe that that's a bad choice for language design, because there's a good chance somebody will use a feature without fully understanding the ramifications it has over the entire code base.
C opts for the opposite and uses annotation to treat certain code segments as "special" and make them faster - "inline", and (in the distant past) "register" come to mind.
(or at least the cons)
I believe I do get the pros as well, at least to some extent. I've been using it since CFront came out ;) And I'd still advocate it for use in a small team (<5 people?) of experienced programmers - you can make it sing.
But gamedev engineering teams are at least 15+ people, with the occasionally less-experienced ones thrown into the mix. C++ is, in a sense, like a professional power tool. It sure can get stuff done, but the wrong person uses it and its a blood bath, and it's not appropriate for single small home repairs. (Gah. Bad analogy, but I can't come up with a better one right now)
This is just bad programming practice on their part. The performance cost of a destructor shouldn't be a surprise to the person who writes it
Possibly. I'd argue that it's hard to always keep all performance ramifications in mind. But let's for a moment say it's indeed simply bad programming practice - the issue that makes this a problem is that my code pays the cost, not the offending code. Which is a rather indirect.
C++ changes often have tendrils all through the system. It is easy to make inadvertent mistakes.
* but it is definitely "decent" in the sense of "adequate.*
It clearly gets the job done, yes. We are shipping games occasionally, after all. I'm looking for better ways to ship games, and I think that abandoning C++ might bring gains.
The second problem with C++ is that it's extremely complicated and takes a long time to learn.
That's what my hidden cost is referring to - C++'s complexity makes it hard to understand. You certainly can write code that performs well in C++, but it is arguably harder to get it right than C.
I like to think there's a better solution than either one hiding somewhere, but I haven't found it yet.
And you can make your code slower by changing it! Oh the horror. C++ is, imo, a good language for a lot of domains. What goes wrong is when people try to use all of the features that C++ offers instead of just using the ones applicable to their domain, eg. I don't think you would really want to use exceptions in embedded programming. If you are on a platform where performance is the key, then you should be careful of what you are doing.
I think people are mostly blaming C++ instead of bad programmers.
The point is not that I can make the code slower by changing it. The point is that it can get slower by changes to the API, without any changes to functionality.Made by somebody else, unintentionally impacting me.
Let me ask you this: Have you ever worked on a large scale C++ project that didn't have performance issues? Or memory issues?
The difference is that the API can change without visible API changes. If you add a virtual destructor to your class, I won't notice that by looking at the call site. I've just been burdened with additional overhead, without knowing about it.
And yes, large projects have large project issues. It is kind of telling that C++ needs an entire tome on large project issues, though... (Lakos, Large Scale C++ Software Design)
The difference is that the API can change without visible API changes.
I don't understand this at all. The performance of a C function can change without any change to the function signature. Even the destructor scenario can happen invisibly in C code for any type that already has a cleanup function. No matter whether the cleanup function is defined as "int myproj_FooCleanup(struct Foo* foo) {...}" or "myproj::Foo::~Foo() {...}", you won't notice implementation changes until runtime.
I'm not talking about the performance of the function itself, I'm talking about the overhead incurred. That can't change in C.
Also, just adding a virtual destructor (without any functionality) adds cost. And unfortunately, you need to if your class happens to become a base class.
The not-so-hidden cost of C is that even simple things end up being many, many lines of code
Yes. If I care about performance, that is a good thing, because I immediately know the cost.
>C++ was invented because certain kinds of C programs -- programs that were already being written in C -- were painfully verbose to express and complicated to change
That is certainly true, but I think C++ has outlived its usefulness and is coasting on momentum.
> Anyway, the size of the mental model of your program is what you should be worried about.
If you can keep 2.5 million lines of code in one single mental model, congratulations. I can't. I don't know anybody else who can. Game code bases, for better or worse, are fairly large, so we need to reason about subsets of it.
This is where the C++ problem comes to bear - I have no direct control and knowledge of all classes involved in a particular computation. Yes, I can look them up - but not everybody does. And I'm concerned with building shipping products with a normal team, not some hypothetical team of superstars.
But the amount of code, modulo repetition and redundancy, that you need to look at to understand performance remains the same. You admit that in C you have to understand the performance of a function to understand the performance of any line of code that calls that function. That's just common sense. In C++ you have to understand the performance of functions, member functions, constructors, and destructors. That's more different kinds of things, but it doesn't mean there's more complexity in the program to actually understand.
This is where the C++ problem comes to bear - I have no direct control and knowledge of all classes involved in a particular computation. Yes, I can look them up - but not everybody does. And I'm concerned with building shipping products with a normal team, not some hypothetical team of superstars.
There's no difference in time or difficulty between looking up a C function and looking up a C++ member function, constructor, or destructor. Working on a codebase full of C++ language constructs and acting like only C language constructs matter is passive-aggression and should be treated as a morale problem, not a programming problem. You don't need superstars, just normal programmers who are willing to learn the language features used in the code they're hired to work on. C programmers who are happy to take a job using a different language but refuse to actually learn the language is such a 1990s problem. It shouldn't be tolerated anymore. It was a big problem in the Java community, with predictable results, and resulted in all kinds of ludicrous complaints being leveled against Java (lack of macros was raised many times as a fatal objection to Java -- there were so many things you simply couldn't do in Java because of the lack of macros), but the problem was solved -- people learned how to recognize and not hire those people.
There's no difference in time or difficulty between looking up a C function and looking up a C++ member function, constructor, or destructor
There clearly is. In my hypothetical C example, there is one function I need to look up. In the C++ example, there are up to four classes:
* Anonymous constructor class
* Class for each in-parameter
* Class for the return value.
just normal programmers who are willing to learn the language features used in the code they're hired to work on
The problem is that the set of language features in C++ is huge. The issue is not programmers who don't want to learn, but programmers who haven't learned the whole language yet.
I can't solve that in the hiring process unless I exclusively rely on senior-level programmers. And those are hard to come by...
Expressiveness in a single line of code is such a stupid metric.
magic()
What's that? It's a function call. What does it do? Anything. Everything. Why does it need a complex grammar? It doesn't. Spend your time thinking about the problem, not the language. What a concept!
Do simple things take many lines of code in C? Yes. If you only have a few data structures and algorithms in your mental toolbox, simple things will take many lines of code in C, or C++, or in any language. Why do people advocate baroque languages by claiming that you cannot do simple things simply except with baroque languages, and then blame people for complicating their programs by using that baroqueness?
Yeah, C++ programmers are feature junkies; the language is designed by a feature junkie and promotes language features as the solution to every problem. If you go against that, you are going against the whole culture.
If PL/I was the fatal disease, C++ is the shambling corpse.
Wrong: C++ promotes library features before language features. Language features are carefully considered before they are included or dropped.
In fact the entire language is designed with care, which should be obvious to anyone who has read The design & evolution of C++ or has followed C++'s evolution.
But kidding aside - the constraints imposed on C++ during its design (C backwards compatible) combined with a desire to have every feature under the sun available has led to an overly complex beast.
Don't tell me you looked at e.g. C++ lambda functions and thought that was "good design". It gets the job done, but that's the best you can say about it.
a = func(b,c);
....
Is it a function call, a member function call, or is it an anonymous constructor? Are b and c implicitly invoking copy constructors for other classes as part of type coercion? Is that a normal assignment, or an assignment operator? Is there a cast operator involved?
If it's such a complicated expression, then the equivalent C code would be correspondingly large and hard to understand, and it would be just as complicated to figure out its cost. The not-so-hidden cost of C is that even simple things end up being many, many lines of code. C++ was invented because certain kinds of C programs -- programs that were already being written in C -- were painfully verbose to express and complicated to change.
If writing your code in C would be simple and clear, then you would have to be stupid to write it as complex, obscure C++. When your C++ code gets complex and you start banging your head against a wall, imagine re-expressing it in C. If the result would be an improvement, then you screwed up. If you blame C++ for screwing up, then you're a language feature junkie. Admit it and check yourself into rehab.
That is the hidden cost. The mental model for a simple function call became incredibly large and complex, and every function call is potentially as complex. Which makes reasoning about performance a rather hard thing to do.... All that translates ultimately into either worse performance or longer development time. Neither one is something you like to hear about.
I don't know how it is a "hidden cost," because it's quite well understood that any reasonably expressive language can say more in a single line of code than C. Anyway, the size of the mental model of your program is what you should be worried about. C++ doesn't give you worse performance or longer development time. It gives you more choices for how you express your program. It means more different problems, because you can write C-style code and have C-style problems, or you can use the possibilities C++ offers and have different problems. If you already know that the choices forced on you by C are usually the right ones for your domain, then by all means apply that knowledge to how you write C++. For instance:
Worse, it makes profiling harder than necessary. All the type coercions that happen at the API level will show up as separate functions, not attributed to the callee, but the caller.
If you want them attributed to the callee, it's pretty simple. Do the coercions in the callee, just like you would have in C.