Best Practices for Portable Code¶
Source Code Line Endings¶
All source code line endings should be in "Unix" style "\n". Every text editor understands these line endings on every platform except the "Notepad" application on Windows.
Use of Tabs¶
Spaces should be used instead of hard tabs. This helps file portability across different editors. DREAM.3D uses a standard whereby all indents use two spaces.
Always Use an Include Guard¶
Always use an "include guard" in your headers:
##ifndef _MY_HEADER_H_
##define _MY_HEADER_H_
Your Code Goes Here
##endif
Include guards are ##define preprocessor directives used in header files that ensure the header is only ever included once during the compiling process for a given source file. If these guards are not included, it is up to the programmer to make sure that headers are only included a single time, which can become tedious or impossible in some situations with larger projects.
Use Only ANSI-C99 Integer Types¶
The ANSI-C99 standard defines typedefs for 8, 16, 32 and 64 bit integer types. These should be used if possible. Platform specific types such as int64 and ambiguous types such as
long int
signed long int
unsigned long int
should be avoided at all costs. This usage is important since the number of bytes that a "long" variable occupies will change depending on CPU type and compiler used. This can have an impact on many important operations, such as pointer arithmetic and binary file IO. All Unix systems have a stdint.h file included with the compiler that defines the proper types. The Microsoft Visual Studio Compiler does not have this file included. There are lots of examples of creating one on the internet. An exemplar file is also included with the Electronic Imaging software distribution that is generated using CMake.
C99 Integer Types¶
int8_t 8 Bit Signed Integer
uint8_t 8 Bit Unsigned Integer
int16_t 16 Bit Signed Integer
uint16_t 16 Bit Unsigned Integer
int32_t 32 Bit Signed Integer
uint32_t 32 Bit Unsigned Integer
int64_t 64 Bit Signed Integer
uint64_t 64 Bit Unsigned Integer
Special Constants¶
nullptr should be used in conjunction with only raw pointers and 0 (zero) should be used in conjunction with numeric values:
int* value = nullptr; // Good
int* value = 0; // Bad
int i = 0; // Good
int i = NULL; // Bad
Variable Initialization¶
Never rely on the compiler to initialize variables or pointers for you. Always initialize your variables before first use:
int i; // Bad, unless this is pure "C" then you have NO choice
i = 0; // Good. The variable has a known starting value
float* f = nullptr; // Good (if the compiler will let you do this)
char buffer[512]; // Allocation of char buffer but will be filled with junk
memset(buffer, 0, 512); // Good. Splat Zeros across the array
Rationale¶
Initializing your own variables ensures that you are starting from a known state when execution of the code reaches that point. Subtle bugs can occur if you do not initialize your variables and then perform checks on them to see if they are valid. A classic example is the following:
int* ptr;
if (!ptr) { initializePointer(ptr); }
When compiled in Debug on some compilers this will execute as you would expect. The problems come when the code is compiled in Release mode. The ptr variable will NOT be set to NULL but instead some random memory address, which can cause problems because the initialization code that should have been running will be skipped due to the conditional if (!ptr) will be true. Remembering to always initialize variables avoids these issues.
Array Initialization¶
define MY_ARRAY_SIZE 10;
int array[10]; // Good - Will compile on all platforms
int array[MY_ARRAY_SIZE]; // Good - Will compile on all platforms
const int size = 10;
int array[size]; // Bad - Will NOT compile on all platforms
An array is declared as datatype name[constant-size] and groups one or more instances of a data type into one addressable place. Constant-size may be an expression, but the expression must evaluate to a constant.
The Dereference operator *
and the Address-Of Operator &
Should Be Directly Connected with the Type-Specifier¶
int32* p; // Correct
int32 *p; // Incorrect
int32* p, q; // Probably error
The int32 p;_ form emphasizes type over syntax while the _int32 p; form emphasizes syntax over type. Although both forms are equally valid C++, the heavy emphasis on types in C++ suggests that int32* p; is the preferable form.
Never Assume CPU Capabilities¶
When writing code, never assume that a CPU has a specific capability such as SSE. Use a ##define block to either test for the capability at runtime or write a generic version of your code will work on any machine and then write the specific implementation of the code that is bracketed by a ##ifdef block.
##ifdef HAS_SSE_3
SSE3 specific code
##else
Generic Code or error message stating the cpu is missing the functionality
##endif
Copyright & License Information¶
Always include a copyright and/or license block at the start of every source file. It is good practice to document in the source code any copyright or licensing restrictions in every file that you write. An example license block is shown below.
///////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2007, 2013 Michael A. Jackson for BlueQuartz Software
// All rights reserved.
// BSD License: http://www.opensource.org/licenses/bsd-license.html
//
///////////////////////////////////////////////////////////////////////////////
More stringent notices can also be written into the source code.
/***************************************************************************
**
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT\_BEGIN\_LICENSE:LGPL$
** Commercial Usage
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL\_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
** $QT\_END\_LICENSE$
**
***************************************************************************/
Always Implement the "Big Three" in C++ Classes¶
When writing C++ classes the programmer will always define the "Big Three" which are defined as:
- copy constructor
- copy assignment operator
- destructor
The programmer should never allow the compiler to implement these methods. This will happen if they are not explicitly defined in the class declaration. Further more if the destructor is declared public then it will have the virtual modifier applied to the declaration.
class A
{
public:
A();
virtual ~A();
private:
A(const A&); //Copy Constructor Not Implemented
void operator=(const A&); //Copy Assignment Not Implemented
};
Note that with C++11, programmers now have the ability to inform the compiler which of these operations can be constructed by default and which can be ignored using the default
and delete
keywords.
String Constants¶
String constants in C++ should be declared as:
const std::string MyFile("SomeFile.dat");
Like constants should be grouped into a namespace.
namespace MyConstants {
const std::string MyFile("SoimeFile");
}
When using ANSI C one should use a char* for constant strings:
const char* MyFile "SomeFile.dat";
const char MyFile[5] = { 'a', '.', 'd', 'a', 't'};
Using this type of approach allows for quicker code updates when constant values need to be changed.