Is C++ too complex?

Started by
121 comments, last by Vortez 11 years, 4 months ago

Ok when i saw that code i thought hmm pretty clear it should be the int constructor since the object doesn'exist yet....then i saw your explanation and i shrug...really? then i tried it just to be sure....first instinct was right your "spoiler" is wrong

Did you miss the comment about copy elision? The compiler is allowed to optimize away the temporary, even if there are side effects. It doesn't have to, but it may (and most probably will). Some compilers may, and some may not. Some may with certain optimization settings, some may not. But it's important to remember that you may be constructing two [font=courier new,courier,monospace]MyStruct[/font]s (the temporary, and the one you want) because, depending on what [font=courier new,courier,monospace]MyStruct[/font] is and what its constructor/destructor does, there may be side effects. It's another one of these "Betcha didn't see that coming, now did ya?!" that C++ can throw at you, because you test it a thousand times on a compiler/platform, and it works a specific way, and then you test the same code on another compiler/platform and it does something a little different.

If you don't believe me that both the [font=courier new,courier,monospace]int[/font] constructor and copy constructor are used in that code, try this:

class MyClass
{
private:
MyClass(const MyClass&) {} // It shouldn't need this, right?

public:
MyClass() {}
MyClass(int i) {}
MyClass& operator = (int) { return *this; }
};

MyClass s = 5;

As expected, compilation fails. So now we see that the copy constructor is indeed needed and used. But what happens if we run this code:

#include <iostream>

class MyClass
{
public:
MyClass() { std::cout << "MyClass()" << std::endl; }
MyClass(int) { std::cout << "MyClass(int)" << std::endl; }
MyClass(const MyClass&) { std::cout << "MyClass(const MyClass&)" << std::endl; }
MyClass& operator = (int) { std::cout << "MyClass& operator = (int)" << std::endl; return *this; }
};

int main()
{
MyClass s = 5;
}


It's a big WTF if you ask me. The copy constructor is required for it to compile (because it's technically required to do [font=courier new,courier,monospace]MyClass s(MyClass(5))[/font]), but it's never even called in the final code! Classic example of copy elision.

@Grafalgar: My posts show one example of exactly what you're looking for. Beginners are taught early on about constructors, but this little detail can potentially be a big pain in the butt that they don't learn until years later.
[size=2][ I was ninja'd 71 times before I stopped counting a long time ago ] [ f.k.a. MikeTacular ] [ My Blog ] [ SWFer: Gaplessly looped MP3s in your Flash games ]
Advertisement

That question makes no sense to me.

The problem is that no first-year CS course will cover all the things you have to know to use C++ proficiently. It's like asking "what's so hard about being a chef? Limit your answers to the subject of boiling water."

Well, yeah, boiling water isn't hard. There's a lot more to being a chef than boiling water.


OK, that's fair, but the point is that the stuff I deal with regularly with regards to C++ I, personally, do not find very hard nor complex. Instead of 1st year students, let's expand to what one might learn throughout your 4-year degree. Or maybe in your first year being a software engineer. A lot of things that are considered "too complex" I very rarely encounter, and that's clear across 14 years of dedicated C++ development. Moreover, what I'm particularly interested in is what makes C++ more complex than Java (or C#), at the beginner/novice level, without going into language obscurities that most people may not deal with.
  • Dynamic memory management of any form
  • Type coercion rules
  • Various syntactical "quirks" (semicolon placement is one I hear a lot)
  • Translation unit model and linking/symbol visibility/etc.
  • Exception safety
  • Const correctness
  • Multiple inheritance
  • How templates really work (critical if you want to use the standard library at all)
  • Undefined behavior


That's just a small selection, too, based on your request for things that are virtually unavoidable in the language.

These are all things that I see bite just about every C++ newbie I've ever worked with, and routinely bite people all over this community. None of those problems exist in, say, C#.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

I've never heard any complaints about semicolons. The fact that you should put one after a struct or class block but not after a namespace block is kind of weird, though.

Multiple inheritance is avoidable (and should be avoided).

I would add making sure code is portable to your list. There are quite a few complexities to handle there (type sizes, byte order, alignment, padding, standard headers that may or may not include other standard headers...).
My pet peeve: that whole header file / code file separation. Why does C(++) want me to juggle those two when every other language I've ever been exposed to conveniently uses one file for both? That's why I decided to make C# my primary language. I just try to design my projects around the supposed ~5% loss of overall speed.

Exception safety


Well, to be fair, exception safety can be a pain in any language. It's true that in C# you don't have to worry about leaking managed resources due to an exception, but it's still easy to stick your program in an inconsistent state especially if you don't follow DRY.

My problem with C++ as a language for beginners is that beginners normally learn by experimentation. They may read a book or take a class but at the end it's them with a compiler trying things out. The first consequence is that in this beginning stage it's very easy for them to think they've learned something about C++ that's actually incorrect. It's not like as beginners they have the experience to generate full coverage tests for their mini-experiments. Even if they do discover something correct about their own compiler they often mistake implementation specific behavior for behavior of the language as a whole. Heck, I've seen teachers do the same thing (evaluation order springs to mind).

The second consequence is that languages with clean grammars and good errors make for much better beginning languages because they can get logical immediate feedback. The semi-colon placement issue is usually a problem because the errors generated frequently point at the "wrong" line of code. Related to this is times when C++ will compile code into something very different than what was intended. The most vexing parse is one of these, but then you've got temporaries created when people wanted to actually wanted to call the base constructor in a derived constructor body, and similar.

Finally, C++ as a first language seems to discourage developing problem solving skills. Beginners learn that when they get stuck against a brick wall, it's probably the language they're fighting and rush off to get help with the latest syntax error or undefined behavior problem. And for people who learned a different language first, that's probably true 99% of the time; however, for someone who's trying to learn C++ at the same time as learning the basics of programming that may not be true in the majority of cases, but true often enough that running off to get help is positively reinforced.

There are people who have successfully learned to program with C++ as their first language. However, it seems like people who start with a different language and then learn C++ later generally get to the level of a proficient C++ programmer faster, and I've seen plenty of both.


My pet peeve: that whole header file / code file separation. Why does C(++) want me to juggle those two when every other language I've ever been exposed to conveniently uses one file for both?

It's basically inherited from C, which was developed in a time when computers were sufficiently low on memory that they literally couldn't fit all the symbols necessary for a complex program in memory all at the same time and still have room for actual code. Programmers would be able to identify which symbols were really needed and include only those headers.

It's basically inherited from C, which was developed in a time when computers were sufficiently low on memory that they literally couldn't fit all the symbols necessary for a complex program in memory all at the same time and still have room for actual code. Programmers would be able to identify which symbols were really needed and include only those headers.


Thank you, though I should have been more clear in indicating that I do understand the reason, but simply cannot stand the additional handling complexity. Of course it has its raison d'etre even today (embedded systems come to mind), but I might see the language in a more positive light if the .h/.c(pp) separation wasn't all but mandatory when targeting systems with plenty of resources. My apologies.

Take the infamous C++ quiz by Washu and tell me C++ is not problematically bad.

Actually, decided to take a look to it, and I think the answer for 1.1 (and thereby 1.2 potentially) may be wrong. That thing is pure pointer arithmetic which should be well defined really (it would make it point one byte after the array if overflow doesn't happen, whatever that address turns out to be - doesn't need to be memory). There's the problem of overflow though, which depending on how pointers are stored behaves differently (no idea what the standard says about how pointers can be stored).

  • If pointers are unsigned integers, then it'll just roll over back to the beginning in case of overflow.
  • If pointers are signed integers, then it's undefined behavior (though on two's complement systems it works the same way as for unsigned integers technically)
  • If pointers are something else (which was the case for really old systems), then... who knows what could happen, this is even worse than your usual undefined behavior.

And even in either case it's still wrong: if the allocated block of memory happens to be exactly aligned to the top of the address space, that would mean p + 10 is overflowed, and thereby we can't rely on the value of that address (in particular, it won't be after p + 9 at the very least, and most likely would end up before it). So the actual defined range in that sense would be p to p + 9, not p to p + 10.

Needless to say, answers 1.2 and 1.3 are both affected by this.
Don't pay much attention to "the hedgehog" in my nick, it's just because "Sik" was already taken =/ By the way, Sik is pronounced like seek, not like sick.

Actually, decided to take a look to it, and I think the answer for 1.1 (and thereby 1.2 potentially) may be wrong. That thing is pure pointer arithmetic which should be well defined really
Yes, it should be well defined, but it's not (maybe this has changed in C++11, I've not re-checked the spec). You're allowed to point to "one past the end" of an array, but no further than that.
In practice, it works fine, but a compliant compiler is allowed to do whatever it likes if you ask for a pointer that's "two past the end" of an array.

[quote name='SiCrane' timestamp='1354759002' post='5007599']
It's basically inherited from C, which was developed in a time when computers were sufficiently low on memory that they literally couldn't fit all the symbols necessary for a complex program in memory all at the same time and still have room for actual code. Programmers would be able to identify which symbols were really needed and include only those headers.


Thank you, though I should have been more clear in indicating that I do understand the reason, but simply cannot stand the additional handling complexity. Of course it has its raison d'etre even today (embedded systems come to mind), but I might see the language in a more positive light if the .h/.c(pp) separation wasn't all but mandatory when targeting systems with plenty of resources. My apologies.
[/quote]

Interesting.
The clear separation of a declaration and an implementation is one of the things I really like with C and C++.
The header file has (should have) everything I need as a _user_ of the class, and nothing more.
Makes the intention of the class very clear, and then I can "hide" the implementation in a cpp file.

Also, this separation is not mandatory, if you want, you can write everything in the same file too. Just gets more messy :P
I really dislike how Java forces me to put everything in the same file...
Very hard to get an overview of the class without additional tools like a smart IDE or doxygen...

This topic is closed to new replies.

Advertisement