The JUCE codebase has a very strict and consistent coding style. The style evolved gradually over the years, incorporating generally acknowledged best-practice C++ advice, experience, and personal preference.
In response to various requests for a set of rules that the codebase follows, I've made an attempt here to list some of the guidelines that I think are most important.
Some of these rules are basic C++ good practice. Some of it's JUCE-specific. A lot of it is just my personal taste! I'm certainly not claiming this to be a definitive set of rules by which you should live, and I'm not particularly interested in arguing or defending any of the points here – it's just a description of what I ended up doing after many years of coding. YMMV!
Don't Repeat Yourself!
This principle pretty much summarises the essence of what it means to write good code, in all languages, at all levels. Other people have explained it better than I can – click here for Wikipedia's D.R.Y. article. If you only pay attention to one piece of advice in this article, make it this one!
Superficial things – layout and whitespace
The following rules won't make any difference at all to what the code actually does, but aesthetics and consistency are really important in making your code understandable.
- No tab characters!
- Tabs are 4 spaces!
- Braces are indented in the Allman style.
- Always put a space before and after all arithmetic binary operators!
- The ! operator should always be followed by a space.
- The ~ operator should be preceded by a space, but not followed by one.
- The ++ and -- operators should have no spaces between the operator and its operand.
- Never put a space before a comma. Always put a space after a comma.
- Always put a space before an open parenthesis! (One exception to this is if you've got a pair of empty parentheses)
- Don't put spaces after a parenthesis. So a typical method call might look like this: foobar (1, 2, 3);
- In general, leave a blank line before an 'if' statement.
- In general, leave a blank line after a closing brace '}'.
- Do not write 'if' statements all-on-one-line.Except when you've got a sequence of lines containing similar if statements, and are aligning them all vertically to highlight their similarities.
- Some people advocate always using braces around an if-statement, even very short, simple ones. I prefer to omit them for trivially simple one-line statements where braces would just waste space without adding any benefits. My preferred rule-of-thumb is:
- If the statement spans multiple lines or is not trivially simple, use braces
- In a multi-way if-else statement where there is more than one section, they should all be the same, so should either use braces, or none of them should use braces.
- When writing a pointer type, my preferred spacing is like this: "
SomeObject* myObject". Yes, yes, I know that technically, the more correct spacing for a declaration would be "
SomeObject *myObject", but to me it makes far more sense for the asterisk to be grouped with the type name, since the fact that it's a pointer is part of the type, not the variable name. The only time that this can lead to any problems is when you're declaring multiple pointers of the same type in the same statement – which leads on to the next rule...
- When declaring multiple pointers, never do so in a single statement, e.g. "
SomeObject* p1, *p2;"– instead, always split them out onto separate lines and write the type name again, to make it quite clear what's going on, and avoid the danger of missing out any vital asterisks.
- The previous point also applies to references, so always put the '&' next to the type rather than the variable, e.g. "
void foo (const Thing& thing)". And don't put a space on both sides of the '*' or '&' – always put a space after it, but never before it.
- This one's really just a matter of taste, but for consistency, whenever I have a const object pointer or reference, I write the 'const' first – e.g. "
const Thing&" rather than "
Thing const&". Both are equivalent, but I prefer the former style because it's closer to the way you'd describe it in English, i.e. verb-before-noun order.
- Template parameters should follow their type without a space, e.g. "
- When splitting expressions containing operators (or the dot operator) across multiple lines, I prefer to make each new line begin with the operator symbol, e.g.
xyz= foo + bar
// I like this because it's clear at a glance
+ func (123)
// that these lines must be continuations of
- def + 4321;
// the preceding ones.
xyz= foo + bar +
// Not so good.. It takes more effort here
func (123) -
// to see that "func" here does not begin
def + 4321;
// a new statement.
AffineTransform t = AffineTransform::translation (x, y)
AffineTransform t = AffineTransform::translation (x, y).
- Parameter order... Someone asked me which order is better when you have a mixture of in/out parameters:
..but I don't think I have an answer to this! Let me know if you have a good argument either way!
- Long lines... Many coding standards enforce a maximum line-length of something like 80 characters, and forbid any exceptions to this.
Obviously keeping lines short and clear is good as a general rule, and we don't want to make people reach for the horizontal scrollbar unless there's a good reason for it. And I do strongly dislike single-line if/while/for-statements, even if the resulting line is short.
But! I've seen lots of examples where code has ended up with confused-looking indentation because long expressions have had to be awkwardly broken across multiple lines, or nudged backwards by a few spaces just to keep the line-end below the limit, making them look like they've been badly indented.
There are some situations where long lines can vastly improve readability and help to highlight errors. When you have a series of similar statements which can be made to line-up vertically by putting each one onto a single line, it makes the vertical similarities jump out, and makes the overall symmetry of the code visually obvious.
- Member variables and method names are written with camel-case, and never begin with a capital letter, e.g. span.monospaced myVariableName
- . Class names are also written in camel-case, but always begin with a capital letter, e.g. span.monospaced MyClassName
- . For global variables... well, you shouldn't have any, so it doesn't matter. When you really must have a file-scoped static, I prefer to make it start with a lower-case letter, so that it doesn't look like a type name.
- I've always hated Hungarian notation. The internet already contains lots of arguments for/against this style, so I won't add to it here, but I think the important thing about a name is for it to clearly describe thepurposeof the variable, not its physical properties.
- Avoid underscores in your names, especially leading or trailing underscores. In particular, leading underscores should be avoided, as these are often used in standard library code, so to use them in your own code looks quite jarring. (In the past I got into a habit of using trailing underscores as a quick way of making a parameter name unique when a similarly-named member variable clashed with it, but I've been gradually removing this from my code in recent years).
- If you really have to write a macro for some reason, then make it ALL_CAPS_WITH_UNDERSCORES. And obviously since macros have no namespaces, you need to make sure that its name won't clash with symbols used in other libraries or 3rd party code. Macros should be the only symbols that are written in all-caps, so that they're easy to spot.
- For enums, I prefer to use camel-case with the same capitalisation that you'd use for a class and its member variables, e.g.
enumValue1 = 0,
enumValue2 = 1
- When writing templates, avoid using 'T' or other meaningless one-character template parameters. It doesn't take much effort to give them a helpful name, and I've seen cases where 'T' clashes with macros defined in 3rd party libraries.
Types, const-correctness, etc
- If a method can (and should!) be const, make it const! Herb Sutter has an interesting take on the meaning of const with respect to the thread-safety of a class that's worth reading about here
- . If you override a virtual method in a sub-class, ALWAYS mark it with the new C++11
overridespecifier, and NEVER write it with a redundant
Even in older compilers, I've provided a non-functioning macro to allow the modern syntax to be used.
- If a method definitely doesn't throw an exception (be careful!), mark it as
noexcept. This can have a dramatic effect on performance in some situations - apparently up to 10x in extreme cases.
- When returning a temporary object, e.g. a String, the returned object should be non-const, so that if the class has a C++11 move operator, it can be used.
- If you declare a local variable which is used inside a non-trivial block of code, then if possible, declare it as const to make it obvious to people reading the code that it won't change!
- Remember that pointers can be const as well as primitives – for example, if you have a char pointer whose contents are going to be altered, you may still be able to make the pointer itself const, e.g. "char* const foobar = getFoobar();".
- Do not declare all your local variables at the top of a function or method (i.e. in the old-fashioned C-style). Declare them at the last possible moment, and give them as small a scope as possible.
- When you're testing a pointer to see if it's null, never write "if (myPointer)". Always avoid that implicit cast-to-bool by writing it more fully: "if (myPointer != nullptr)". And likewise, never ever write "if (! myPointer)", instead always write "if (myPointer == nullptr)". I find it much more readable like that.
- Avoid C-style casts except when converting between primitive numeric types. Some people would say "avoid C-style casts altogether", but I find writing static_casts can be a bit cumbersome when you just want to trivially cast an int to a float. But whenever a pointer is involved, or a non-primitive object, always use modern casts. And when you're reinterpreting data, always use reinterpret_cast.
- For 64-bit integer types: until everyone is using C++11 and can use int64_t, it's best to stick to the juce::int64 and juce::uint64 typedefs.
- Absolutely do NOT use 'delete', 'deleteAndZero', etc. There are very very few situations where you can't use a ScopedPointer or some other automatic lifetime management class.
- Do not use 'new' unless there's no alternative. Whenever you type 'new', always treat it as a failure to find a better solution. If a local variable can be allocated on the stack rather than the heap, then always do so.
- Never use 'new' or malloc to allocate a C++ array. Always use a HeapBlock instead.
- ..and just to make it doubly clear: You should almost never need to use malloc or calloc at all!
- If a parent object needs to create and own some kind of child object, always use composition as your first choice. If that's not possible (e.g. if the child needs a pointer to the parent for its constructor), then use a ScopedPointer.
- Whenever possible, pass an object as a reference rather than a pointer. If possible, make it a const reference.
- Obviously avoid static and global values. Sometimes there's no alternative, but if there is an alternative, then use it, no matter how much effort it involves.
- If allocating a local POD structure (e.g. an operating-system structure in native code), and you need to initialise it with zeros, use the "= 0 ;" syntax as your first choice for doing this. If for some reason that's not appropriate, use the zerostruct() function, or in case that isn't suitable, use zeromem(). Don't use memset().
- Treat Component::deleteAllChildren as a last resort – never use it if there's a cost-free alternative.
- The juce::ScopedPointer class is written to be compatible with pre-C++11 compilers, so although it does offer C++11 move functionality for supported compilers, it's not as versatile as std::unique_ptr. So if you can use std::unique_ptr in your own code, that's probably a better bet.
- Declare a class's public section first, and put its constructors and destructor first. Any protected items come next, and then private ones.
- My preferred positioning for any inherited classes is to put them to the right of the class name, vertically aligned, e.g.
- Put a class's member variables (which should almost always be private, of course) , after all the public and protected method declarations.
- Any private methods can go towards the end of the class, after the member variables.
- If your class does not have copy-by-value semantics, always use the JUCE_DECLARE_NON_COPYABLE macro. This should be the last item in your class's declaration.
- If your class is likely to be leaked, then you can use the JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR macro. If your object is copy-by-value but also likely to be leaked, then you can use JUCE_LEAK_DETECTOR instead – but be aware that this combination of behaviours is a bit suspicious, since copy-by-value objects should probably never be allocated using 'new', so shouldn't ever be leaked..
- Constructors that take a single parameter should be default be marked 'explicit'. Obviously there are cases where you do want implicit conversion, but always think about it carefully before writing a non-explicit constructor.
- Do NOT use span.monospaced NULL , span.monospaced null, or0for a null-pointer. (And for god's sake don't be tempted to use span.monospaced 0L, which is a horribly confused misunderstanding of what a pointer is!) Always use span.monospaced nullptr - this is the C++11 standard, so get used to it! I've implemented a fallback definition for nullptr in JUCE, so it's always possible to use it even if your compiler isn't yet C++11 compliant.
- All the C++ 'guru' books and articles are full of excellent and detailed advice on when it's best to use inheritance vs composition. If you're not already familiar with the received wisdom in these matters, then do some reading!
- Never put an "else" statement after a "return"!
The LLVM coding standardsgive a good explanation of this, but once you think about it, it's basic common sense. Since I first noticed this one, it has become one of my pet-hates when I see it done in other people's code!
- Earlier versions of JUCE used a T() macro to wrap string literals, but that has been deprecated for many years now. Just write your strings as plain old C++ string literals and the JUCE String and StringRef classes will handle this happily. For extended unicode characters, the only fully cross-compiler way to embed these into your code is as a UTF-8 encoded C++ string written as escape character sequences (this means that the source file is still pure ascii, so text editors can't mangle the encoding). If you do that, you should wrap the literal in the CharPointer_UTF8 class, so that when cast to a String, everything's nice and clear about the format that's being used. The Projucer has a built-in tool that will convert unicode strings to valid C++ code and handle all this for you.
- In general, I don't like unnecessary use of the = operator when creating an object. So rather than 'const String s = "foo";', I prefer 'const String s ("foo");'. I do understand that this is personal taste, but I guess I just don't 100% trust the compiler to optimise-away that = operator...
- ...but having said that, if you do use the = operator, at least avoid writing nonsense like "const String s = String ("XYZ");" !
- Don't use macros! OK, obviously there are many situations where they're the right (or only) tool for the job, but treat them as a last resort. Certainly don't ever use a macro just to hold a constant value or to perform any kind of function that could have been done as a real inline function. And it goes without saying that you should give them names which aren't going to clash with other code. And #undef them after you've used them, if possible.
- When using the ++ or -- operators, never use post-increment if pre-increment could be used instead. Although it doesn't matter for primitive types, it's good practice to pre-increment since this can be much more efficient for more complex objects. In particular, if you're writing a for loop, always use pre-increment, e.g. "for (int = 0; i < 10; ++i)"
- \..and a couple of other very good pieces of advice from the LLVM standards are hereand here
- . When getting a possibly-null pointer and using it only if it's non-null, limit the scope of the pointer as much as possible – e.g. Do NOT do this:
Foo* f = getFoo();
(f != nullptr)
...lots of intervening code...
// oops! f may be null!
..instead, always prefer to write it like this, which reduces the scope of the pointer, making it impossible to write code that accidentally uses a null pointer:
(This also results in more compact, cleaner code).
Most experienced C++ coders, myself included, fall into the habit of always passing function parameters as const-references, e.g. "const Foo&". This is the right thing to do for complex objects (e.g. Array, String, etc), but when you pass a reference, it prevents the compiler from using a whole slew of optimisation techniques on the call-site. For example, it means that there's no way for the compiler to really know whether the function will modify the original value (via const_cast) or whether it will modify a memory address which is an offset from the location of the object.
So, the best-practice advice from the guys who actually write the optimisers is: Always stick to pass-by-value if possible, and only use references if the price of calling the copy constructor is very high.
This is particularly true in the case of small objects whose overall size is actually not much bigger than the size of a pointer. Some juce classes which should always be passed by value include: Point, Time, RelativeTime, Colour, all of the CharPointer_XYZ classes, Identifier, ModifierKeys, JustificationType, Range, PixelRGB, PixelARGB.
(I suspect that even larger structures like Rectangle<int> and Rectangle<float> may benefit from this technique too, especially on 64-bit CPUs.. If anyone has the time to create benchmarks to find out for sure, let me know!)
I'm sure this list is nowhere near complete, (and probably never could be), but I'll keep adding to it when things occur to me...