Skip to main content
GameDev.net gamedev.net
🔒 Locked

C++ wish-list/rant

Started by kRogue Feb 21, 2007 at 5:43 AM 49 replies 9.8k views
Original Post
kRogue
kRogue
oh well, I just have to complain/rant about C++ (I do prefer it, but sometimes it irrirates the hell out of me!) 1) C++ class and struct They are the same bloody thing (except that defualt acces fro class is private and public fro struct), what I _WISH_ was that struct would only contain elementary data type (i.e. poitners, ints, floats, ect) and other structs and NOT have any member functions (not even constructors and deconstructors) i.e. in C++ a struct is the same as a C struct, nad only classes can have class members and memeber functions. Why you ask? becuase that C++ language makes a default copy constructor for every class/struct which is just a bitwise copy... how amny of you guys have forgotten to write a copy constructor and has issues from it? with this it would _force_ one to write a copy constructor! 2) C++ ISO shows scars of evolutions: i.e. the "must" v.s. "should" for some of the time complexities for some methods in STL, irritates me to no hell. 3) I _wish_ member functions would have to be called via this->MemberFunction, along with memer variables, i.e. this->m_myMember, because: it will remind you that you are probably accessing the variables through a pointer and you don't have to worry about name collision (yes there are namesapces and doing ::function to guarnatee the global function, but it still irritates, how many of you out there always type ::function() for calling global functions? ok rant over, back to work.
Close this Gamedev account, I have outgrown Gamedev.
Aardvajk
Aardvajk
1) The default copy constructor is not a bitwise copy. It calls the copy constructors for all members. I'm very glad I am not "forced" to write a copy constructor for simple classes.

2) It is generally agreed that C++'s evolution has led to many language foibles that newer, designed-from-scratch languages do not. It is, however, doubtful C++ would have become the commercial success it has without its backwards compatibility with the C compilation model and the "don't pay for what you don't want" philosophy.

3) There is nothing to stop you explicitly dereferencing this to refer to object members if you choose. I, however, find this habit redundant and annoying so I'm personally glad the language does not enforce this.
ZQJ
ZQJ
Oh, there's plenty of things wrong with C++. Not sure I agree with any of your points though.

1) Two keywords is silly, but hardly damaging. The default copy constructor is NOT a bitwise copy however - it calls the copy constructors of each member which in the case of POD types is a bitwise copy, however types like std::string will be copied correctly. The same goes for the assignment operator. I think the lesson for this one is that if you are holding resources which need special handling to be copied, write a small wrapper class to manage the copying/freeing of that resource. This also makes it easier to make your code exception safe.

2) I might be wrong on this, but I think "must" and "should" are fairly normal terminology in technical standards. Besides, I think the only time this question ever really arises is in std::list::size() vs. std::list::splice(), and yes, in that case it might well have been a good idea for the standards committee to have actually made a decision rather than sat on the fence. IMHO constant time splice is the right call because constant time size can be implemented via a wrapper, whereas constant time splice can't.

As for some more things wrong with C++:

1) Templates: what are they for? They're part premature optimization (so the programmer can force the compiler to specialize code for a particular type rather than use inheritance and interfaces), part compile-time calculation (although using them that way is messy), and part generic type handler, and at the end of the day I don't think they're very good at any of those things. If they're for handling generic types they should be able to specify the interfaces those types have to conform to so they can be checked for correctness.

2) Syntax: the dreaded 'typename' and '. template' constructions, and use of < and > as brackets. C, while not completely context free, is relatively straightforward to parse with a small parser hack. C++ is a convoluted nightmare. GCC was battling this for years under the old 2.x versions until they finally used a handwritten parser for 3.x. Take the statement "a < b, c > d;". Normally you'd say that's obviously a declaration, but if a,b,c,d are variables then it's a perfectly valid C statement.

However, C++ does have a few major advantages, which are what really keep it in business. First, it works with C libraries with minimal effort - just one 'extern "C"' declaration and you can include C library headers straight off the bat, with no glue code to write. Second, unlike Ruby, Python, etc. it is a compiled language, which means you can write low level algorithms in it and they will be fast. It's also relatively simple to have an idea what sort of assembler the code you write is going to produce, and speed-obsessed programmers like that.
kRogue
kRogue
Quote:

The default copy constructor is NOT a bitwise copy however - it calls the copy constructors of each member which in the case of POD types is a bitwise copy, however types like std::string will be copied correctly.


is that really true?! hmm... maybe when I was using gcc 2.95 it was not true... I always painfully remember to write the copy constructor for any class that contains dynamicly allocated stuff, i.e. strings, vectors, etc... I ahve memory of that when I did not, I got all sorts of memory issues (I saw this when I wrote a very simple base class which jsut held a pointer to itslef and was inited always as the this, even in the copy constructor) and when I did not write the stuff out by hand, the addess was bad... i.e. it copied it... although the base class had a copy constructor (which jsut did m_ptr=this).

Quote:

There is nothing to stop you explicitly dereferencing this to refer to object members if you choose. I, however, find this habit redundant and annoying so I'm personally glad the language does not enforce this.


when all or most of the code is your own, then it seems ok, until you have to cleanup and deal with other peoples code.. then one must chech to see what is getting called... you cannot jsut glance at line 20 of source.cpp and say "that is a member function/variable or global" you have to go back and check the header file. but the autmoatic scping atleast saves one from typing...


Quote:

Templates: what are they for? They're part premature optimization (so the programmer can force the compiler to specialize code for a particular type rather than use inheritance and interfaces), part compile-time calculation (although using them that way is messy), and part generic type handler, and at the end of the day I don't think they're very good at any of those things. If they're for handling generic types they should be able to specify the interfaces those types have to conform to so they can be checked for correctness.



I actually like *using* templtes from other libraries, I have to admit that I have writing them my self, one particular thing I hate is that the template functions don't cast pointer to base class for you automagically, atleast in g++ 4.x:

eg: consider (useful when one wants to write GUIs and send signals)

template
void myfunction(T *obj, (T::*fptr)(void) )
{
obj->fptr();
}

and if one has base class baseClass and its derived class derivedClass and base class has the virtual method, myThingy(), but derivedClass does not overide it, the following code will give syntax error:

derivedClass *ptr;
myFunction(ptr, &dervivedClass::myThingy)

highly irritating, either cast the pointer explictiy or write this template:
template
void myfunction(S *obj, (T::*fptr)(void) )
{
T *anotherPtr(obj)
anotherPtr->fptr();
}


just more typing...

and I do wish they sued a differetn symbol besides < > to do templates, as that symbol was kind of busy, why not say jsut @ and @ or make a new "symbol" @< and @>
err....



and because most C stuff can be called from C++ with no hassle, C++ is still king to me, it is better than C because it auto gnerates function calls for you... but at times... errr...



Close this Gamedev account, I have outgrown Gamedev.
ToohrVyk
ToohrVyk
Quote:
Original post by kRogue
is that really true?! hmm... maybe when I was using gcc 2.95 it was not true... I always painfully remember to write the copy constructor for any class that contains dynamicly allocated stuff, i.e. strings, vectors, etc... I ahve memory of that when I did not, I got all sorts of memory issues (I saw this when I wrote a very simple base class which jsut held a pointer to itslef and was inited always as the this, even in the copy constructor) and when I did not write the stuff out by hand, the addess was bad... i.e. it copied it... although the base class had a copy constructor (which jsut did m_ptr=this).


This is because the copy constructor for a pointer member is a bitwise copy of that member. However, if you have members which implement their own copy constructor, then the copy will most definitly not be bitwise, but will rather use the provided member copy constructors. In particular, using std::string or std::vector does not require writing your own copy constructor, the default one handles them just fine.

Aside from that, what is the point of storing this as a member variable, anyway?
kRogue
kRogue
this is what I had:

class memChecker{private:  memChecker *m_ptr;public:  memChecker(void) { m_ptr=this; }  memChecker(const memChecker &) { m_ptr=this; }  void Check(void) const { assert(this==m_ptr); } };


so now if I did

class myType:public memChecker{private:  type members;public:  myType(void) { do stuff; }  ~myType() { kill stuff; }  otherMemeberFunctions();  //no copy constructor!};vector<myType> lotsOfStuff(10);lotsOfStuff.resize(20);for(i=0;i<20;++i)  lotsOfStuff.Check();



that code would (or used to atleast) trigger and assert.... or was it just that way a long time ago, and I have never forgiven or forgotten?

the reson what I saved the this pointer value as a member was because I getting suspicious when I resized arrays vectors of a type which had vector members, the crashes were tandom and strange, but when I added this memChecker business, I started to see that when resize() was called, the copy constructors were acting fishy, for example:

class myType{private:  vector<T> data;public:  myType() {}  //no copy constructor}vector< myType > object;object.resize(20);


would start to corrupt memory because data would go out of scope, free the memory, on resize, but the "copying" of the myType into the memory just alloacted by the vector would do a bitwise copy of the data member, not a constructor... so memory corruption, the issue went away when I added a copy constructor that explictily copied data:

myType::myType(const myType &obj): data(obj.data) {}


so perhapds back then the copmiler was dodgy? I am tempted to try to see what happnes now on g++ 4.x, for sick curiousity... so that would kill my gripe#1...

[Edited by - kRogue on February 21, 2007 8:33:07 AM]
Close this Gamedev account, I have outgrown Gamedev.
Aardvajk
Aardvajk
As far as I can see,

myType::myType(const myType &obj) : data(obj.data) { }


should be identical to the compiler-generated default copy constructor, so if you were getting odd behaviour without it then that would, in isolation, suggest some kind of compiler bug.
Emmanuel Deloget
Emmanuel Deloget
Quote:
Original post by EasilyConfused
As far as I can see,

myType::myType(const myType &obj) : data(obj.data) { }


should be identical to the compiler-generated default copy constructor, so if you were getting odd behaviour without it then that would, in isolation, suggest some kind of compiler bug.


Or that the OP is using an old compiler.

The C++ standard was published in 1998. Any compiler which is older can't be standard compliant, and can show strange behavior in some particular cases. You'll have to use newer compilers to verify this code.

If you eat food which is 9 years old, you have a good chance to get ill [smile]
Bregma
Bregma
Quote:
Original post by Emmanuel Deloget
The C++ standard was published in 1998. Any compiler which is older can't be standard compliant, and can show strange behavior in some particular cases. You'll have to use newer compilers to verify this code.


Just a nit: the C++ standard was published in 1997 (on November 17). It just wasn't formally declared by the ISO until 1998, after a certain propotion of member national standards bodies had ratified it. No vendor, including a cerain well-known one, has an excuse for not following the published standard, formally declared or not.

Now, back to the original complaint: GCC 2.95 predates publication of the standard. It did not ship with the C++ standard library. There's no reason to believe its implementation of ::vector<> would do anything sane. I have very good reason to believe it did not. Don't blame the C++ language over this one.

You will find that current versions of both GCC and Microsoft's Visual C++ are pretty good implementations that follow the published standard pretty closely (not perfectly, but more than good enough for almost any useful application). I am unfamiliar with other compilers. With these modern widely tested tools it's almost certain that any problem you encounter is your own.

--smw
Stephen M. Webb
Professional Free Software Developer
ToohrVyk
ToohrVyk
Quote:
Original post by Anonymous Poster
There's this little thing called naming conventions, one of which is hungarian and serves as a much better way to distinghuish local/global/member variables.


The legendary "my prefix is better than your prefix" argument. m_var, var_ and this->var are just similar ways of representing the same information, and any argument advocating the use of one over another is usually purely cosmetic, like indentation styles and bracket positioning. You can go down the slope of religion wars over cosmetic details and call others idiots, but that's not quite convincing.

At best, one could argue that using this->var avoids redundancy of information. m_var or var_ decoration also appears in member initialization lists (where it's obvious that they are members) and when accessing the members of a variable using a.m_var or a->m_var (where it's also already obvious that they are members). So, it could be argued that this->var is a superior solution because it provides as much information as m_var or var_, but does not pollute the code as much with irrelevant information.

At worst, the argument could boil down to "a variable name prefix is compiler-enforced", since prefixing with this->var isn't mandatory as in other languages. This would end up being a discussion over enforcing coding practices (forcing users to prefix either variable declarations or variable usage).

As usual, it ends up being a fight between the annoyance of adding useless information in places where it isn't needed, versus the danger of using a local variable instead of a member one because a convention was violated. And as usual, there is no best decision to be taken, and certainly no rational justification for calling the other side of the fence idiots.
Aardvajk
Aardvajk
Quote:
Original post by Anonymous Poster
If I ever see code where some idiot has written "this->" for no reason (yes it can be necessary in a very rare template function case), I'll go rip that piece of crap out of there promptly.

From the looks of your posts I wouldn't be surprised if your code was full of mispelt variables too!


I would just like to make it clear that though I was disagreeing with kRouge about the use of the this-> prefix, I find the above comments by the AP quite rude and unnecessary.

Back on topic, a variable used in a member function that is not a local or a parameter (which you can locally confirm quite easily) must either be a member or a global, unless I'm missing something.

With that in mind, would it not make more sense to adopt a convention for identifying globals rather than members, given that globals "should" be far less common?

I don't personally like name prefixes or hungarian notation, but I see a lot more worth in tagging a g_ onto every global than in tagging a m_ onto every member.

I agree with Toohvyk though in that we are out of the bounds of good or bad coding practice and into the realm of opinion. I like the fact that C++ leaves an individual or a team able to choose their own conventions.
arithma
arithma
I would have liked the break to be able to break from multiple loops.
for(int i = 0; i < 10; i++)  for(int j = 0; j < 10; j++)    break for for;
Bregma
Bregma
Quote:
Original post by arithma
I would have liked the break to be able to break from multiple loops.
for(int i = 0; i < 10; i++)  for(int j = 0; j < 10; j++)    break for for;


It's spelled g-o-t-o.

--smw
Stephen M. Webb
Professional Free Software Developer
penwan
penwan
Quote:
Original post by ZQJ
1) Templates: what are they for? They're part premature optimization (so the programmer can force the compiler to specialize code for a particular type rather than use inheritance and interfaces)

That's not premature optimization, it's good sense. If you always know the type at compile time and so you don't need to pay the run-time penalty for making virtual function calls then why would you want to? (edit: it is a trade-off, of course, as the size of the binary is larger and linear in the number of types for which the function is instantiated)

Quote:
and part generic type handler, and at the end of the day I don't think they're very good at any of those things. If they're for handling generic types they should be able to specify the interfaces those types have to conform to so they can be checked for correctness.

You are specifying the interface, though. Example:

template <typename T>void SomeFunction(const T& Something){    // ... some stuff ...    Something.DoSomethingUseful(true);    // ... some more stuff ...}


Here I have specifed that void DoSomething(bool) must be defined for type T. If the type does not define this member function the compiler complains.

template <typename T>T SomeComputation(const T& A, const T& B){    // ... some stuff ...    T Result = A + B;    // ... some more stuff ...    return Result;}


Here I have specified that operator+ must be defined for type T. If type T does not define this operator, the compiler will complain and tell you that. The beautiful thing about this is it means that type T is allowed to be either a built-in type or a user defined class. It doesn't have to inherit from IAddable or whatever like generics in other OO languages require. What's the point?
ToohrVyk
ToohrVyk
Quote:
Original post by penwan
It doesn't have to inherit from IAddable or whatever like generics in other OO languages require. What's the point?


Two reasons: verification and exporting. In C++, verification happens when the template is instantiated, which forces the compiler to perform a new verification for each different instantiation. Using constraints, verification only occurs once, using the constraints, and then instantiation only requires to check the argument against a short list of constraints.

Also, verification is not performed as long as instantiation was not done (although some compilers might attempt to work a bit about that). As a consequence, code such as the following will compile, although it can simply not work:

template <typename T>T SomeComputation(const T& A, const T& B){    // ... some stuff ...    T Result = A + B;    // ... some more stuff ...    return Result;}template <typename T>class Foo{public:   Foo() {    SomeComputation(*this,*this);  }};


As for exporting, generic code is simpler to export than template code, because most of the job in generics is done when the generics are compiled (with instantiation being a simple matter of binding function calls specified in the constraints), while most of the job in templates is done when the template is instantiated (so most of the template code is kept around, which makes exporting much more difficult).

The exporting part also makes sharing easier. For instance, implementation of an std::pair should be the same for any T, yet it is difficult to determine that this is the case, and so the compiler generates new code that does nearly the same thing. Using constraints, the only difference between instantiations is the binding of the functions described in the constraints, which makes it easier to determine that two instantiations use the same code.
penwan
penwan
Verification will just cause an increase compile time. For large projects this sucks (believe me, I know, at work some projects can take 30+ minutes to do a full rebuild and link for some platforms). But I think the increased flexibility of templates is worth it.

Quote:
Using constraints, the only difference between instantiations is the binding of the functions described in the constraints, which makes it easier to determine that two instantiations use the same code.

This is mostly to reduce the size of the binary and compile time as well as simplify the compiler, right? I guess I don't see this as a big deal from a programmer's perspective, but rather as a problem for the authors of the compiler.

Though, I must confess that I have very little experience with Java/C#/generics. In fact, from my minimal Java experience, the purpose of generics as far as I can tell was so that I didn't have to cast items held in containers from Object when retrieving them. Other than that the difference between generics and an interface seem superficial in Java. I'll have to do some reading on it.
penwan
penwan
Quote:
Also, verification is not performed as long as instantiation was not done (although some compilers might attempt to work a bit about that). As a consequence, code such as the following will compile, although it can simply not work:

True. However, clearly the class is never instantiated and so the class Foo is never used. What's the point of verifying it's written correctly? If it's for a library, then you are testing the code, right? The test cases will cause an instantiation and should uncover such bugs.
ToohrVyk
ToohrVyk
Quote:
Original post by penwan
True. However, clearly the class is never instantiated and so the class Foo is never used. What's the point of verifying it's written correctly? If it's for a library, then you are testing the code, right? The test cases will cause an instantiation and should uncover such bugs.


The point of verification is that you should be sure a module works before using it. Although tests are a good step in that direction, they are no replacement for a compiler-enforced verification. Tests can only prove that there is an error: only the compiler can prove that there are no errors.

As for expressiveness, properly implemented generics (O'Caml functors come pretty close to perfection, and C# 2.0 generics are not that bad either) do not restrict it, while allowing 100% compile-time verification, even when only the generic interface is available (which helps modularity). What is this "increased flexibility" of templates that you speak of? Non-type template arguments?
penwan
penwan
Off the top of my head, the "Curiously Recurring Template" and the traits idioms come to mind. Looking through boost, Loki, etc. you can see many elegant examples of these patterns in practice. To my knowledge neither is doable with generics, though, like I said, I am not very knowledgeable about generics.
MumbleFuzz
MumbleFuzz
Quote:
Original post by ToohrVyk
What is this "increased flexibility" of templates that you speak of? Non-type template arguments?


I gotta say, that's always seemed kinda funky to me. I've never had any need to do dimensional analysis in my code (and possibly I never will), but it's still pretty neat knowing that you can build it into the typing system. And templates are turing-complete too! How cool is that?

On a related note, I like being able to have a class that looks like Vec. (Incidentally, last time I used C#, I recall that I wasn't even able to write a Vec class. Can you use generics with primitive types these days, and perform useful operations like + at the same time)?

To be honest, what I'm waiting for is a popular imperative, OOP language that implements aspects, coroutines, multimethods, contracts and first-class procedures. And possibly some functional notation for kicks. Oh, and I wouldn't say no to having named parameters, but that one's not a biggie.

However, given the stack-based nature of C++, I'm not going to hold my breath waiting.. [wink]
MumbleFuzz

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.