C++ coding guidelines

Discussion of chess software programming and technical issues.

Moderators: hgm, Dann Corbit, Harvey Williamson

User avatar
lucasart
Posts: 3232
Joined: Mon May 31, 2010 1:29 pm
Full name: lucasart

Re: C++ coding guidelines

Post by lucasart »

ilari wrote:
wgarvin wrote:
ilari wrote:C++ should not be thought of as better C - it's a different language and it should be used differently. Fruit is a good example of how to use C++ without really using C++, and Stockfish is a great example of how C++ should be used.
I completely disagree with this. :lol: C++ is a horrible language. If you have to write a high-level program and you can't just use something like Python, then I guess C++ can get the job done. But for low-level code, most of its language features are a double-edged sword, making it easy to bleed away the performance advantage of C without even noticing.
I think C++ is a very good tool when it's used by people who know what they're doing. It has served as quite well for Cute Chess where we need a good combination of low-level control and high level features. The bottlenecks were easy to identify and thanks to C++ they were also easy to optimize.
If you want to write "nice C++" then go ahead, but writing "proper C++ code" with lots of RAII, virtual functions, default constructors that initialize everything to zero or NULL, etc. is a good way to get slow programs. Plus nobody, and I mean nobody, can reliably write exception-safe C++ code that does anything tricky. Exception-safety in C++ is ridiculous and difficult. If you care about having correct programs, a much safer way to write them is to turn exceptions off and write all of your code without them!
You don't have to use every feature of C++, that's not what I was trying to say. For example we don't use exceptions at all. But we do use a lot of RAII, polymorphism, generic containers, Qt's unicode strings, etc. And in the very few places where we needed to minimize CPU or memory consumption, we did it with very C-like code. Rejecting C++'s high level features completely wouldn't have made our program more efficient in any noticeable way.
Anyway, for a programmer familiar with C it is very easy to use C++ as just a "slightly more convenient C". You can put little inline accessor methods in your structs, you can declare your variables in the middle of a function, and you can use a little bit of RAII style for some convenient things (such as profiling, or making sure a block of memory gets freed when exiting from a function that has a complex control flow). Sometimes it might make sense to use C++ templates a little bit, to reduce source code duplication (you'll still have near-duplicated code in the binary, but sometimes that is what you want, and templates might be less ugly than macros for your specific use-case.. it varies). For some kinds of code, std::vector is a much simpler way to get obviously-correct code than some manual malloc/free would be. Another pure win is std::sort, you can use it on ordinary arrays or std::vector or whatever else, and it will usually be faster than qsort() or anything else.
So you do advocate using C++'s features and even STL where they make sense. That was exactly my argument. There's no good reason why Fruit couldn't use std::string instead of char* for example. And I doubt you could make Stockfish much faster by converting the code to the kind of "better C" used in Fruit. BTW, you can declare variables in the middle of a function in C, unless you're using some ancient C89 compiler.
A big benefit of using C++ as a "better C" is that performance-wise, it doesn't cost any more than the equivalent C code. Be wary of constructors that initialize everything in a performance-critical struct (e.g. something you'll allocate a vector of thousands of, should have a do-nothing ctor or just a default ctor). Don't use STL (other than for convenience in non-performance-critical code), don't ever use the iostreams garbage, turn of RTTI and don't ever use dynamic_cast, and DISABLE EXCEPTIONS at least for your 32-bit builds. (In 64-bit builds the performance cost of having them enabled is pretty much zero, just a tiny bit of lookup-table bloat). Don't even use virtual methods unless you would have used some sort of dispatch anyways in your C code (but generally you should try not to use them in any performance-sensitive code). C++ is just as fast as C if you don't use any of the language features that have some extra cost. If you stick to "C subset" plus some conveniences like inline methods, then you can't really go wrong!
I don't really like this "be wary of this and that" mentality of premature low level optimization. I think it's better to just first write the code without worrying too much about low level performance and then profile the code. After profiling the bottlenecks can be optimized.
ilari wrote:And not all code has to be fast. In a typical desktop application only a very small percentage of the code is performance-critical.
This really varies with application domain. Its mostly true in computer chess, but (for example) mostly false in games. Modern games spend about 90% of their time spread across 50% of the code (millions of lines of it).
It's clear that we are developing completely different kinds of programs. I don't view modern hardcore computer games as typical desktop applications. But I know how crazy it is at companies like Naughty Dog where they're trying to squeeze every drop of performance out of very complex hardware using Assembly or whatever. I recently read an article about the development of Starcraft. The stuff about linked lists sounds exactly like the kind of hell that I will be avoiding like the plague.
You're absolutely right. Using some C++ features, when appripriate, really helps. Of course you can do it all in C, but some things are really painful to do in C (for example handing dynamic reallocation when you have to use things like vector of string and the likes).
But to actually see any benefit from using C++, you need:
1/ to understyand deeply the language, and it's immensely complicated. I don't think there is another language that is even close in terms of complexity. Just reading Bjarn Stroustrup's book on C++ (although very well written) is a reminder of that fact. So there is a pretty steep learning curve. The C++ FQA link that I posted previously is also a very good reference of all the traps that C++ tries to set for beginners (and even for people who don't think of themselves as beginners, but actually are, because C++ is more complex than they imagine).
2/ you need to know what to use, and what not to use, and when. In other words, have coding guidelines, that are based on knowledge and experience. You can gain that knowledge and experience yourself, by years of suffering and rewriting your code bcause it was badly designed etc. Or you can trust others: the Google coding guideline is very good IMO. It has already helped me to refactor my code a little, and make it more flexible, easy to extend and reuse.

So, C++ is now useful for me, although not without pain. Hopefully this pain is teaching me how to use it better, and there will be less and less pain as I understand how to make efficient C++ design. And it does help you do things that would be a nightmare to handle in C.

But it is horribly complicated, and full of tricks and traps. This is mostly because of historical reasons: started as C with classes, things were added, and retrocompatibility had to be preserved, and after years of evolution it became so complex no human can *fully* understand it (perhaps Bjarn himself?).

What I'm really hoping for is a new language that adresses these issues, and keeps the high level potential of C++, while keeping the low level potential of C. And this language exists, it's called D. The only problem is that it's really so new and scarcely used. And it's hard to know if it will be popular in years to come: is it worth switching to D now ? Or learning C++, which is a really hard for someone like me who really "thinks like a C compiler" ? We'll see :D

PS: One of the languages that I really liked, which is also my first compiled language, is Pascal (before I'd only written in Basic and Assembly). As a high level language, it is a lot easier and a lot better IMO than C++. But when it comes to the low level, nothing comes even close to C (in terms of speed & memory, but also in terms of having the control of what exactly happens and being able to hack things manually byte by byte when you want/need to). And the fact that C++ kept it's C subset (although slightly modified, still useable) makes C++ simply unavoidable (however ugly and complex it may be).

Lucas (a partially repented C++ hater)
Theory and practice sometimes clash. And when that happens, theory loses. Every single time.
AlvaroBegue
Posts: 931
Joined: Tue Mar 09, 2010 3:46 pm
Location: New York
Full name: Álvaro Begué (RuyDos)

Re: C++ coding guidelines

Post by AlvaroBegue »

C++ is indeed very complex. I think I have met only two people that fully understood the language. And yes, one of them was Barjne himself. :)

If you think you understand the language, try to predict what this piece of code does:

Code: Select all

#include <iostream>

int main&#40;) &#123;
  volatile char s&#91;&#93; = "Hello, world!";
  std&#58;&#58;cout << s << '\n';
&#125;
Every few years I find an obscure corner of the language like that one.

It took me several years to feel comfortable programming in C++, but now I would hate to go back to C. The things I would miss most are:
* A lot of the standard library that is made possible by templates (vector, unordered_map, priority_queue, string, sort...).
* Describing interfaces cleanly using classes.
mcostalba
Posts: 2684
Joined: Sat Jun 14, 2008 9:17 pm

Re: C++ coding guidelines

Post by mcostalba »

AlvaroBegue wrote: Every few years I find an obscure corner of the language like that one.
This is due to how ostream library (std::cout) has been defined more than to some intrinsic pitfall in the language:

http://stackoverflow.com/questions/2501 ... rs-to-bool

It happens that for std::cout the operator<<(volatile char*) was not defined and so C++ tries to fall back on something "similar" and so ends up picking up operator<<(bool)

It is tricky but still parsable after some tought....instead the next one it is really hard-core stuff :-)

How would you check if a given function object has a specific signature? IOW how is the function signature of its operator() ?

Next code does the above (at compile time). Defenitly not for beginners :-)

Code: Select all

#include <iostream>

// Detect if functor class Fun has signature Sig
template<typename Fun, typename Sig>
struct is_same
&#123;
  template<class U, Sig U&#58;&#58;*> struct helper;

  template<class U>
  static char check&#40;helper<U, &U&#58;&#58;operator&#40;)>*);

  template<class U>
  static int check&#40;...);

  static bool const
  value = sizeof&#40;check<Fun>&#40;0&#41;) == sizeof&#40;char&#41;;
&#125;;


// Our test functor&#58; a class for which operator&#40;) is defined
struct Test &#123;
  char operator&#40;)&#40;int&#41;;
&#125;;


int main&#40;int, char**) &#123;

  std&#58;&#58;cout << is_same<Test, char&#40;int&#41;>&#58;&#58;value << ", "
            << is_same<Test, void&#40;int&#41;>&#58;&#58;value << ", "
            << is_same<Test, char&#40;int, char&#41;>&#58;&#58;value << std&#58;&#58;endl;
&#125;
Rein Halbersma
Posts: 741
Joined: Tue May 22, 2007 11:13 am

Re: C++ coding guidelines

Post by Rein Halbersma »

mcostalba wrote: It is tricky but still parsable after some tought....instead the next one it is really hard-core stuff :-)

How would you check if a given function object has a specific signature? IOW how is the function signature of its operator() ?

Next code does the above (at compile time). Defenitly not for beginners :-)
This is indeed a serious piece of WTF-code if you have never seen it. For the curiuous: google for "sfinae elllipsis"
User avatar
Rebel
Posts: 6946
Joined: Thu Aug 18, 2011 12:04 pm

Re: C++ coding guidelines

Post by Rebel »

mcostalba wrote: Next code does the above (at compile time). Defenitly not for beginners :-)
Seriously, assembler is a lot simpler :lol:
User avatar
lucasart
Posts: 3232
Joined: Mon May 31, 2010 1:29 pm
Full name: lucasart

Re: C++ coding guidelines

Post by lucasart »

Don wrote:
rreagan wrote:
rreagan wrote:
mcostalba wrote:Isn't this the case for which namespaces have been invented for?
Yes, but as someone else mentioned, there are some differences. With the approach of thinking in terms of components, it's actually not a good thing to split things among multiple files. With a struct, all code in Magic:: is in one of two files (.h/.cpp), which is what I would want. I also think it's advantageous that you can't use "using namespace Magic" and lose the context, and potentially cause more name conflicts.

To me, a namespace is more appropriate for a big library or framework, like std:: or boost::, where it's not reasonable to expect everything should fit into a single pair of .h/.cpp files.
But my long-winded point was not to debate these specific details. We could debate this, and where we place our braces, all day long.
You don't use the "one true" brace style?

My point was, Lucas said the complexity of C++ was causing him problems, and I had felt the same in the past and found a decent solution in Large Scale C++ Software Design. It's not perfect, but it is something you can say, "here, use C++ this way, we tried it, it works, you can build big things and won't get lost in the complexity".
Here's a very good document on coding standards, for C. Plus it's full of jokes, and deliberate provocation, a fun read.
https://computing.llnl.gov/linux/slurm/coding_style.pdf
I pretty already follow all these rules, except for the tab=8 (I prefer tab=4). But I really like his argument about it (the 3-indentation rule).
Theory and practice sometimes clash. And when that happens, theory loses. Every single time.