Jump to content

  • Log In with Google      Sign In   
  • Create Account


Is C++ too complex?


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
122 replies to this topic

#61 Cornstalks   Crossbones+   -  Reputation: 6974

Like
3Likes
Like

Posted 03 December 2012 - 09:49 PM

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 MyStructs (the temporary, and the one you want) because, depending on what MyStruct 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 int 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 MyClass s(MyClass(5))), 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.

Edited by Cornstalks, 03 December 2012 - 09:54 PM.

[ 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 ]

Sponsor:

#62 Grafalgar   Members   -  Reputation: 544

Like
0Likes
Like

Posted 05 December 2012 - 05:57 PM

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.

#63 ApochPiQ   Moderators   -  Reputation: 14673

Like
3Likes
Like

Posted 05 December 2012 - 06:08 PM

  • 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#.

#64 Álvaro   Crossbones+   -  Reputation: 12510

Like
1Likes
Like

Posted 05 December 2012 - 07:23 PM

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...).

#65 CS_   Members   -  Reputation: 225

Like
1Likes
Like

Posted 05 December 2012 - 07:56 PM

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.

#66 SiCrane   Moderators   -  Reputation: 9501

Like
3Likes
Like

Posted 05 December 2012 - 07:56 PM

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.

Edited by SiCrane, 05 December 2012 - 08:00 PM.
quotes


#67 CS_   Members   -  Reputation: 225

Like
0Likes
Like

Posted 05 December 2012 - 08:12 PM

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.

#68 Sik_the_hedgehog   Crossbones+   -  Reputation: 1549

Like
-2Likes
Like

Posted 06 December 2012 - 01:30 AM

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.

#69 Hodgman   Moderators   -  Reputation: 28658

Like
2Likes
Like

Posted 06 December 2012 - 02:31 AM

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.

Edited by Hodgman, 06 December 2012 - 02:47 AM.


#70 Olof Hedman   Crossbones+   -  Reputation: 2719

Like
0Likes
Like

Posted 06 December 2012 - 02:50 AM


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.


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...

Edited by Olof Hedman, 06 December 2012 - 02:52 AM.


#71 Sik_the_hedgehog   Crossbones+   -  Reputation: 1549

Like
0Likes
Like

Posted 06 December 2012 - 03:39 AM

I think the main issue with header files is that you're writing the same stuff twice (e.g. function prototypes vs. function definitions).

Also tried to look up the pointer stuff in C++, and ended up stumbling upon this instead:
http://en.wikipedia.org/wiki/C%2B%2B11#Null_pointer_constant

Gotta admit I didn't think of that one. That's really annoying. (then again, why would you have a function overloaded both to an integer and a pointer at the same time?)
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.

#72 mrbastard   Members   -  Reputation: 1573

Like
0Likes
Like

Posted 06 December 2012 - 06:33 AM

People who dislike the compilation model may want to look out for (or even give feedback to the standards committee on) proposals for Modules. There have been proposals mentioning partial classes for easier code-gen tooling as well, but I don't know whether this is part of the Modules working group or separate at this point.

Edited by mrbastard, 06 December 2012 - 06:37 AM.



#73 Yrjö P.   Crossbones+   -  Reputation: 1412

Like
0Likes
Like

Posted 06 December 2012 - 07:27 AM

Gotta admit I didn't think of that one. That's really annoying. (then again, why would you have a function overloaded both to an integer and a pointer at the same time?)

void processListNodes(node *begin, node *end);
void processListNodes(node *begin, int howMany);

I'm not arguing that this is or isn't good design, but it is something many people could plausibly write.

Edited by Stroppy Katamari, 06 December 2012 - 07:31 AM.


#74 Álvaro   Crossbones+   -  Reputation: 12510

Like
0Likes
Like

Posted 06 December 2012 - 07:33 AM


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.


All true. Although probably no compiler is going to give you trouble for this one, I was recently surprised when gcc turned this code into an infinite loop:
for (int i = 0; i >= 0; ++i) {
}

A more naive compiler would stop once i overflows and becomes a large negative number, but what happens after overflow is undefined behavior, so the compiler can assume that it won't happen (if it does, it's allowed to do anything it wants so any behavior is correct) and can therefore optimize `i >= 0' to `true'.

This kind of strict interpretation of the standard by the compiler makers is annoying. For instance, a C++98-compliant compiler can put an infinite loop at the end of a hello-world program, because the standard doesn't specify "finishing in a finite amount of time" as part of the observable behavior of the program. The return value of the program is also not part of the observable behavior, so I think a compiler could stay compliant while returning 3 from main every time, regardless of what the code says. Of course nobody would use a compiler that did that, but it illustrates the point that simply satisfying the standard is a low bar and I expect my compiler to be more reasonable than that.

#75 Sik_the_hedgehog   Crossbones+   -  Reputation: 1549

Like
0Likes
Like

Posted 06 December 2012 - 10:13 AM

Yeah, GCC generates that kind of code on purpose. Try to check for overflow after the fact (e.g. by checking if the value went negative), GCC will explicitly optimize out (i.e. remove) the check. The reasoning behind this is that "it's undefined behavior so we'll try to break the code so you can't rely on it", except sometimes that can backfire since that kind of bugs on purpose may not pop up in ages, depending on the situation.


Gotta admit I didn't think of that one. That's really annoying. (then again, why would you have a function overloaded both to an integer and a pointer at the same time?)

void processListNodes(node *begin, node *end);
void processListNodes(node *begin, int howMany);

I'm not arguing that this is or isn't good design, but it is something many people could plausibly write.

Fair enough.
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.

#76 Álvaro   Crossbones+   -  Reputation: 12510

Like
1Likes
Like

Posted 06 December 2012 - 10:38 AM

Yeah, GCC generates that kind of code on purpose. Try to check for overflow after the fact (e.g. by checking if the value went negative), GCC will explicitly optimize out (i.e. remove) the check. The reasoning behind this is that "it's undefined behavior so we'll try to break the code so you can't rely on it", except sometimes that can backfire since that kind of bugs on purpose may not pop up in ages, depending on the situation.


It takes a bit of a sadist to come up with that method to educate programmers.

How about a warning saying something like "this conditional expression evaluates to true unless you invoke undefined behavior"? If I am compiling some old piece of code that is now broken because the compiler is now a lawyer, I would much rather get an informative message than a bug.

#77 SiCrane   Moderators   -  Reputation: 9501

Like
0Likes
Like

Posted 06 December 2012 - 10:48 AM

From what I've heard, it wasn't a deliberate attempt to punish people who rely on undefined behavior, but just a consequence of a change to the optimizer to generate faster code. It just so happens that it looks like an attempt to piss off people who rely on undefined behavior. Fortunately, gcc offers a flag to disable this optimization: -fwrapv.

Anyways, for a less contrived example of overloading a pointer and an integer:
ostream & operator<<(int);
ostream & operator<<(const void *);
// along with half a million other member overloads and some non-member overloads


#78 Álvaro   Crossbones+   -  Reputation: 12510

Like
0Likes
Like

Posted 06 December 2012 - 12:15 PM

Anyways, for a less contrived example of overloading a pointer and an integer:

ostream & operator<<(int);
ostream & operator<<(const void *);
// along with half a million other member overloads and some non-member overloads


Yeah, that one is responsible for this fun piece of code:
#include <iostream>

int main() {
  volatile char s[] = "Hello, world!";
  std::cout << s << '\n';
}

At least recent versions of g++ give you a reasonable warning about this one.

#79 ChaosEngine   Crossbones+   -  Reputation: 2251

Like
0Likes
Like

Posted 06 December 2012 - 02:14 PM

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.

Except that the header file also contains information about private member variables and methods. So you either give the client all the details about the class, or you end up using something like pimpl, which is yet another hoop to jump through that other languages have realised provides no tangible benefit.

Also, this separation is not mandatory, if you want, you can write everything in the same file too. Just gets more messy Posted Image
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...


So? This isn't the 80's anymore. We don't have to program in vi and cc on the command line. Realistically, managing any project of a decent size without an IDE is just making work for no reason.
if you think programming is like sex, you probably haven't done much of either.-------------- - capn_midnight

#80 NightCreature83   Crossbones+   -  Reputation: 2705

Like
0Likes
Like

Posted 06 December 2012 - 02:28 PM


Anyways, for a less contrived example of overloading a pointer and an integer:

ostream & operator<<(int);
ostream & operator<<(const void *);
// along with half a million other member overloads and some non-member overloads


Yeah, that one is responsible for this fun piece of code:
#include <iostream>

int main() {
  volatile char s[] = "Hello, world!";
  std::cout << s << '\n';
}

At least recent versions of g++ give you a reasonable warning about this one.

I ran into one of these on a function call in optimised code where the compiler had optimised away the actual instance we were interested in being passed to the function, that was a fun few days to track that down.
Worked on titles: CMR:DiRT2, DiRT 3, DiRT: Showdown, GRID 2, Mad Max




Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS