Language Directives and Best Practices
Chapter 6
Take benefit from C++
1. Be Const Correct
C++ provides the const keyword. This makes it possible to indicate that a method doesn’t modify
the objects that it receives as parameters. Using const in all the right places is called "const
correctness". It’s hard at first, but using const really tightens up your coding style. Const
correctness grows on you.
If you’re in the darkness about what Const Correctness exactly is, read the relevant section in the
C++ FAQ
2. Use Streams
Programmers that move from C to C++ find IO stream strange and prefer the familiarity of good old
stdio. printf and its derivates seem to be more convenient since they are well understood.
However when you use these old idioms, you throw away one of the most powerful features of
C++.
2.1. Justification
2.1.1. Type Safety
Stdio is not type safe, which is one of the reasons you are using C++, right? IO stream is type safe.
2.1.2. Standard Interface
When you want to dump an object to a stream there is a standard way of doing it: the <<>(e)
Casts away the const−nessof objects, variables or pointers. It gives an error when the types differ
more than in const and volatile modifiers.
Don’t use this to cast away const−nessof objects that were originally defined as being const and on
which non−constoperations are being executed. Doing this, results in undefined behaviour.
You typically use this when you need to access an API that incorrectly defines a function signature.
When you are 100% sure that the function you want to call doesn’t perform non−constoperations
on the argument, you can safely cast away the const−nessof the argument that was initially defined
as being const.
Example 6.2: const_cast Example
int countChars(char *pString, char character);
const char *p_somestring = "Let’s make things better";
int result = countChars(const_cast
4.2. dynamic_cast
Makes it possible to safely downcast pointers and references to base classes. It thus returns the
appropriate sub−objectin the hierarchy chain. It returns 0 if this cast wasn’t possible on pointers
and throws the bad_cast exception if the cast wasn’t possible on references. This effectively says
"convert this Object into a Penguin or give me 0 if its not an Penguin,". This provides dynamic
typing, you don’t know what will happen until run−time.
Example 6.3: dynamic_cast Example
class B { /* at least one virtual function */ };
class D : public B { /* ... */ };
B *p_b1 = new B;
B *p_b2 = new D;
D *p_d1 = dynamic_cast
D *p_d2 = dynamic_cast
4.3. reinterprete_cast
This type of casts treats pointers and references as incomplete types. Using the
reinterprete_cast yields values that are typically not guaranteed to be usable without casting
back to their original types. It’s difficult to say more about this kind of casts since their applicability
is very implementation dependent.
Example 6.4: reinterprete_cast Example
void someFunction(char *p_string) { *p_string = ’x’; }
typedef void (*FPType)(const char*);
FPType p_function_pointer = reinterprete_cast
/* calling someFunction through p_function_pointer is not guaranteed to work */
4.4. static_cast
This is very similar to the old C−stylecasts. Only use it when none of the above seem to fit the bill.
It will only succeed if there’s an implicit conversion possible either from T to the type of e, or from
the type of e to T.
Example 6.5: static_cast Example
Fraction fraction(1,2);
double d = static_cast
Chapter 7
Best practices
1. Commenting Out Large Code Blocks
Sometimes large blocks of code need to be commented out for testing. You can’t use /**/ style
comments because these can’t be nested. Surely a large block of your code will contain at least
one comment, won’t it?
1.1. Use #if 0
The easiest way to do this is with an #if 0 block. Don’t use #ifdef as someone can unknowingly
trigger ifdefs from the compiler command line.
Example 7.1: Commenting Out Large Code Blocks Example
void example()
{
great looking code
#if 0
lots of code
/* a comment */
some more code
#endif
more code
}
1.2. Use Descriptive Macro Names Instead of 0
The problem with #if 0 is that a while later neither you nor someone else has any idea why this
code has been commented out. Is it because a feature has been dropped? Is it because it was
buggy? Didn’t it compile? Can it be reinstated? It’s a mystery.
Therefor you can also chose to use descriptive macro names instead of #if 0.
Example 7.2: Commenting With Descriptive Macro Names
void example()
{
great looking code
#if NOT_YET_IMPLEMENTED
travel_through_air();
#endif
a bit of code
#if OBSOLETE
/* a comment */
travel_by_foot();
#endif
#if TEMP_DISABLED_OUT_OF_GAS
travel_by_car();
#endif
}
2. Prefer positive boolean comparisons
It’s much easier to think in a positive way about a situation than to be presented with the negative
alternative and having to transform it in your mind by yourself to positive. People tend to have a
’logical’ or ’the default behaviour’ feeling about true, which makes it easy to think about. On the
contrary, false is mostly regarded is the ’exception’, ’the error situation’ or the ’alternative way out’.
Therefor we prefer constructs like this:
Example 7.3: Positive boolean comparison, the right way
setup();
if(something == true)
{
dowork();
}
cleanup();
return;
above the following negative counterpart :
Example 7.4: Positive boolean comparison, the wrong way
setup();
if(something == false)
{
cleanup();
return;
}
dowork();
cleanup();
return;
3. Handle cleanup situations with boolean indicators
Often you’re presented with the problem that your code logic contains a series of initializations that
can all potentially fail. Typically you want to interrupt any further execution, cleanup and return an
error message. Such situations have been known to be resolved through the use of exceptions,
gotos, large if−then−elseconstructs and boolean indicators. From these options, it’s the last one we
prefer.
Below is an example of such a typical code cleanup situation :
Example 7.5: Cleanup with boolean indicators
void some_function()
{
bool file_setup = false;
bool dir_setup = false;
/* try to create a new file object and open it for reading */
QFile *p_file = new QFile("/path/to/file");
if(p_file != 0 &&
p_file−>open(IO_ReadOnly) == true)
{
file_setup = true;
}
QString dir_path("/path/to/default/dir");
if(file_setup == true)
{
/* if the file was setup, read its contents and use it for */
/* further processing */
QTextStream textstream(p_file);
QString dir_path = textstream.readLine();
dir_path = textstream.readLine();
}
/* try to create a new dir object and open it for reading */
QDir *p_dir = new QDir(dir_path);
/* some vars that are needed by the dir logic */
if(p_dir != 0 &&
p_dir−>exists() == 0)
{
/* do stuff with the dir */
dir_setup = true;
}
else
{
cout << dir_setup ="=" file_setup ="=">close();
}
delete p_dir;
delete p_file;
}
3.1. Justification
− You prevent unnecessary consecutive indentations as is the case with large if−then−else
constructs.
− You don’t have the maintaince hassle of local gotos which could point anywhere and present
a number of difficult to solve C++ issues such as accessing object whose initializations has
been jumped over.
− It’s very easy and clear to follow the logical flow, no jumps are executed as with gotos and
exceptions.
− You can perform context−sensitive cleanups that combine the states of several boolean
indicators.
4. Learn about enum classes
In a lot of cases regular enumerations suffice to define a group of valid integer constants as a
distinct type. A typical example follows :
Example 7.6: Classic enumeration example
typedef enum
{
ALPHA = −4, /* this maps to the "alpha" string in the code */
BETA = −3, /* this maps to the "beta" string in the code */
PRE = −2, /* this maps to the "pre" string in the code */
RC = −1, /* this maps to the "rc" string in the code */
NONE = 0, /* this maps to no string (or all the invalid strings) */
P = 1 /* this maps to the "p" string in the code */
} SuffixType;
This approach however presents a number of drawbacks :
− The values are limited to integers.
− It’s not possible to evolve the constants in time to provide for example custom output
methods, alternative synonimous values (strings for example), conversion from and to other
types, and so on ...
− Casting between integers and enums is very error prone, you could cast a value that’s not
available in the enum.
Therefore it’s often justified to write enum classes. These classes contain private constructors and
initialize constant instances of themselves as static class variables.
An advanced implementation example follows :
Example 7.7: Enumeration Classes Example
/*
* This creates a collection of the possible suffix type instances and
* neatly maintains the textual and numerical representation together.
* No additional instance can be created through the public interface.
* The instances are registered in an internal dictionary which makes it
* very easy to retrieve the exact object that corresponds to a textual
* representation.
* The type of the enum values is a pair, composed out of a string and a
* matching integer.
*/
class SuffixType : public QPair
{
public:
/**
* The enumeration’s values
*/
static const SuffixType ALPHA;
static const SuffixType BETA;
static const SuffixType PRE;
static const SuffixType RC;
static const SuffixType NONE;
static const SuffixType P;
/**
* Retrieve a SuffixType object instance according to its textual
* representation.
**
@param string The string to look up.
* @return A SuffixType that correponds to the provided string, or
* SuffixType::NONE if no match was found.
*/
static const SuffixType &get(QString string);
/**
* Outputs the textual representation to an output stream.
*/
friend std::ostream &::operator<<(std::ostream &rStream, const SuffixType &rSuffixType); private: /** * Private constructor that will only be called during the initialization * of the static class variables. */ SuffixType(QString string, int value); /** * Class wide collection that maps string representations of class * instances to the instances themselves. */ static QDict
};
/* Define and initialize the enumeration values. */
const SuffixType SuffixType::ALPHA("alpha", −4);
const SuffixType SuffixType::BETA("beta", −3);
const SuffixType SuffixType::PRE("pre", −2);
const SuffixType SuffixType::RC("rc", −1);
const SuffixType SuffixType::NONE("none", 0);
const SuffixType SuffixType::P("p", 1);
/* Define the static class−wide dictionary */