-
Notifications
You must be signed in to change notification settings - Fork 0
Classes & Structures
Design your objects to be as simple and straightforward as possible, and achieve one single goal as best as possible. If your junior software developer friend can't make sense of your high-level code, give these articles a read:
- http://stackoverflow.com/questions/550861/improving-code-readability
- https://blog.codinghorror.com/code-smells/
- http://mikamantyla.eu/BadCodeSmellsTaxonomy.html
Class/structure declarations are done on one line, with the body starting on the next line. Only use one space after the class and struct specifier.
struct ComposedObject
{
int x, y;
};
template<typename Type>
class Thing : public Type
{
};
Use standard #ifndef
/#define
/#endif
include guards, and not #pragma once
. Not all compilers support this feature.
- Format your classes' and structures' inherited objects in one column.
- Sort the inherited objects from broadest to tightest scope.
- Sort the classes in the scope alphabetically.
- Always respecify the scope.
- Only use one space before and after the colon (':') which is separating the class declaration and the base-list.
- Always place the comma after the class / struct name, against the name.
- This is to keep the code along the lines of regular English grammar.
class Foo : public Bar,
public SomethingUseful,
protected OtherBar,
private Rebar
{
};
- Unless there is only a need for the default scope which all variables and methods fall under, always specify the scope modifier.
- But never respecify the same scope modifier.
- Order the scope modifiers by most open, down to the most closed.
- Only specify scope modifiers once - never respecify
class
/struct
scope.- If there are custom constructors, either:
- Specify the default constructor.
- Hide the default constructor in the
private
section of theclass
/struct
. - For every scope:
- Always specify constructors first, in this order:
- Default constructor first.
- Custom constructors next.
- Copy-constructor afterwards.
- Move-constructor (if applicable).
- Destructor.
- Always specify custom operator after constructors.
- Always specify custom methods afterwards.
- Always specify constructors first, in this order:
Only specify the destructor if:
- The class is intended to be derived from.
- You must prefix it with virtual in this case.
- The class has a particular destruction sequence you need to adhere to.
- The class needs to kept within a container of some kind.
Always disallow copyability, unless the class design is entirely POD - which is more rare than you think.
If the design is not going to account for copyability or is going to disallow it, place the copy constructor and operator=
(and move editions where applicable) at the bottom most part of the private section of a class or struct.
Be sure to not fill in these methods - not even for private uses! That would be a confusing design!
If you can, mark the functions with = delete
. JUCE's JUCE_DECLARE_NON_COPYABLE
and JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR
do this for you already using JUCE_DELETED_FUNCTION
.
If copyability is an expected behaviour, then manually fill in all of the copying methods: copy constructor and operator= (and move editions where applicable). Doing all of them guarantees consistent logic, avoiding default generated code from a potentially poor compiler. It's a bit more effort on your end, but at least you're ready to fix it - and static analysers are available to help prevent mistakes!
For more information, see the Rule of Three/Five.
class IAmCopyable
{
public:
IAmCopyable() :
bar (0)
{
}
IAmCopyable (const IAmCopyable& other) :
bar (other.bar)
{
}
IAmCopyable& operator= (const IAmCopyable& other)
{
if (this != &other)
bar = other.bar;
return *this;
}
private:
int bar;
};
class IAmNotCopyable
{
public:
IAmNotCopyable() :
bar (0)
{
}
private:
int bar;
JUCE_DECLARE_NON_COPYABLE (IAmNotCopyable)
};
Whatever you do, don't hurt your designs by creating a base-class to designate copyability or cloneability! Ask yourself what it means to make your object copyable, and design high-level classes to generate non-copyable objects.
- Use the Factory Pattern.
This is only for the rare cases where you're passing the class instance down into other classes and functions.
The reasoning is to make obvious the segregation between class
/struct
/function dependencies.
Use =
for primitives and nullptr
associations, and { }
for anything else.
class Example
{
public:
Example()
{
}
private:
int bar = 0;
std::atomic<bool> foo { false };
JUCE_DECLARE_NON_COPYABLE (Example)
};