ns-3 Coding Style
This page documents how contributors to ns-3 should format their source code. It contains a mix of syntactical and design guidelines.
ns-3 is an open source project with many contributors. Once code is checked into the simulator, the bulk of hours spent working on it is in some form of maintenance (often many years in the future and not performed by the initial author). A common coding and commenting scheme can make for easier to read, easier to understand, easier to maintain, and more bug-free code.
Since there are many disparate programming and commenting styles in use today, we have to form some guidelines that may seem arbitrary to the contributor but are selected from possible options for the sake of uniformity. Beyond syntactical guidelines, there are also often several robust ways to implement certain programming constructs (e.g. noncopyable vs. explicit hiding of a constructor), but we may recommend one way below for the sake of uniformity, again with maintenance and overall code readability in mind.
When writing code for ns-3, we therefore ask that you follow the below coding standards. Ideally, all of the below will be followed by each contribution, but to avoid being too overbearing and ambiguous, we will try to differentiate between hard requirements and guidelines or possible options in the below.
Note: We have provided a check-style.py program that will automatically indent code for you.
General guidelines
In general, the C++ code layout follows the GNU coding standard layout for C and extends it to C++. If a style question is not addressed by the below set of rules, please consult the GNU coding standard.
In general, the Python code layout follows the PEP 8. The most noticeable difference with respect to GNU is that python uses func(...) while ns-3 uses func (...) (i.e. with a space before parentheses). Since PEP 8 is a widely used standard, we do not try to align the Python code with the GNU standard in this regard.
The basic principles of the ns-3 coding style are:
- Keep the appearance of ns-3 code to be mostly uniform. When someone opens up a source file, it should "look like an ns-3 file." When a maintainer with ns-3 emacs shortcuts opens up a file, it should not mean that the shortcuts do not work anymore. Maintainers should not have to guess which styles are prevailing in a source file. In short, we try to make indentation, naming conventions, comment formats, use of namespaces, filename conventions, whitespace rules, and doxygen formats consistent, even if some arbitrary rules are chosen for the sake of uniformity.
- Prevailing coding style of an existing file/class should take precedence. This may mean that the coding styles and maintainer feedback may be more rigorously enforced for modifications to existing code.
- Keep the overall system design consistent. A key concern is that the new contribution work in a coherent way with the rest of the simulator. Another key concern is that the implementation is generally consistent (use of ns-3 logging, use of ns-3 attribute system and TypeId system, where appropriate, etc.) across modules. Please keep in mind that we want to avoid discontinuities in user experiences, or surprises, when users try to combine the new code with other parts of the simulator in novel ways, and that while there may be multiple robust ways to implement something, a maintainer may recommend a particular way of doing it for consistency with our codebase.
Code layout
Indentation
The following indentation rules are mandatory. Note that we provide a check-style.py program to assist contributors with indentation.
Do not use tabs for whitespace (indentation or otherwise). Indentation spacing is 2 spaces (the default emacs C++ mode) as outlined below. In particular,
- the method name is in column one, and the return type immediately precedes it
- the open brace that starts a function is in column one
- open braces for conditionals are placed on the following line, and indented two spaces; the next statement is indented two spaces further
void
Foo ()
{
if (test)
{
// do stuff here
}
else
{
// do other stuff here
}
for (int i = 0; i < 100; i++)
{
// do loop
}
while (test)
{
// do while
}
do
{
// do stuff
} while ();
switch (i)
{
case 0:
break;
case 1:
{
++i;
}
default:
break;
}
}
For a class declaration, indent all members by two spaces, and keep the return type on the same line as the function name. Example:
class Node : public Object
{
public:
static TypeId GetTypeId ();
Node ();
/**
* \returns the unique id of this node.
*
* This unique id happens to be also the index of the Node into
* the NodeList.
*/
uint32_t GetId () const;
Braces are mandatory for conditional and looping statements (if, for, do, while), except optionally for simple single statement conditionals. Examples:
if (condition)
{
statement // recommended
}
if (condition) a_single_statement // permitted if simple and fits on one line
if (condition) { a_single_statement } // permitted if simple and fits on one line
if (condition)
statement // not permitted due to potential for future maintenance errors
if (condition)
{
statement // braces not indented correctly; not permitted
}
For switch statements, the case statements do not require braces but can
use them such as follows:
switch (i)
{
case 0:
break;
case 1:
{
++i;
}
default:
break;
Each statement should be put on a separate line to increase readability. Each variable declaration is on a separate line. Variables should be declared at the point in the code where they are needed, and should be assigned an initial value at the time of declaration. Except when used in a switch statement, the open and close braces "{" and "}" are always on a separate line. Do not use the C++ "goto" statement.
The layout of variables declared in a class may either be aligned with the variable names or unaligned, as long as the file is internally consistent and no tab characters are included. Examples:
int varOne; double varTwo; // OK (unaligned)
int varOne; double varTwo; // also OK (aligned)
int varOne; double varTwo; // not OK (type of variable should be on same line)
Spaces
We use the GNU style of adding a space before the open parentheses and after commas; example:
if (x < foo (y, z))
haha = bar[4] + 5;
Avoid leaving trailing whitespace (unnecessary spaces) at the end of the line.
Naming
Name encoding
Function, method, and type names follow the CamelCase convention: words are joined without spaces and are capitalized. For example, "my computer" is transformed into MyComputer. Do not use all capital letters such as MAC or, PHY, but choose instead Mac or Phy. Do not use all capital letters, even for acronyms such as EDCA: use Edca instead. This applies also to two-letter acronyms, such as IP (which becomes Ip). The goal of the CamelCase convention is to ensure that the words which make up a name can be separated by the eye: the initial Caps fills that role. Use PascalCasing (CamelCase with first letter capitalized) for function, property, event, and class names.
Variable names follow a slight variation on the base CamelCase convention: camelBack. For example, the variable "user name" would be named "userName". This variation on the basic naming pattern is used to allow a reader to distinguish a variable name from its type. For example, "UserName userName;" would be used to declare a variable named userName of type UserName.
Global variables should be prefixed with a "g_" and member variables (including static member variables) should be prefixed with a "m_". The goal of that prefix is to give a reader a sense of where a variable of a given name is declared to allow the reader to locate the variable declaration and infer the variable type from that declaration. Defined types will start with an upper case letter, consist of upper and lower case letters, and may optionally end with a "_t". Enum type names are recommended to avoid the suffix "_e" although there are some instances of such suffixes in our existing codebase. Defined constants (such as static const class members, or enum constants) will be all uppercase letters or numeric digits, with an underscore character separating words. Otherwise, the underscore character should not be used in a variable name. For example, you could declare in your class header my-class.h:
typedef struct {
uint32_t m_magicNumber;
uint32_t m_versionMajor;
} PcapFileHeader_t; // the trailing _t is optional
const uint8_t PORT_NUMBER = 17;
class MyClass
{
void MyMethod (int aVar);
int m_aVar;
static int m_anotherVar;
};
and implement in your class file my-class.cc:
int MyClass::m_anotherVar = 10;
static int g_aStaticVar = 100;
int g_aGlobalVar = 1000;
void
MyClass::MyMethod (int aVar)
{
m_aVar = aVar;
}
As an exception to the above, the members of structures do not need to be prefixed with an "m_".
Avoid retyping the built-in types, such as:
typedef int NewTypeOfInt_t;and use portable integer types that convey the size of storage explicitly, such as uint8_t, int32_t, etc., instead of e.g. "unsigned long".
Finally, do not use Hungarian notation , and do not prefix enums, classes, or delegates with any letter.
Choosing names
Variable, function, method, and type names should be based on the English language, American spelling. Furthermore, always try to choose descriptive names for them. Types are often english names such as: Packet, Buffer, Mac, or Phy. Functions and methods are often named based on verbs and adjectives: GetX, DoDispose, ClearArray, etc.
A long descriptive name which requires a lot of typing is always better than a short name which is hard to decipher. Do not use abbreviations in names unless the abbreviation is really unambiguous and obvious to everyone (e.g., use "size" over "sz"). Do not use short meaningless names such as foo, bar, or baz. The name of an item should always match its purpose. As such, names such as "tmp" to identify a temporary variable or such as "i" to identify a loop index are ok.
If you use predicates (that is, functions, variables or methods which return a single boolean value), prefix the name with "is" or "has", such as "bool IsBroadcast () const;".
File layout and code organization
The maximum line length is 128 columns. For lines longer than 128 columns, split them in the GNU style, before an operator, and with four spaces of indentation:
if (foo_this_is_long && bar > win (x, y, z)
&& remaining_condition)
Contributors may have previously contributed files with comments or lines formatted for 80 columns. If you are editing such a file for which 80 columns is already the prevailing style, do not reformat or do not suddenly change to 128 column lines, such that the file has a visual appearance of two different line lengths in use.
A class named MyClass should be declared in a header named my-class.h and implemented in a source file named my-class.cc. The goal of this naming pattern is to allow a reader to quickly navigate through the ns-3 codebase to locate the source file relevant to a specific type.
Each my-class.h header should start with the following comments: the first line ensures that developers who use the emacs editor will be able to indent your code correctly. The following lines ensure that your code is licensed under the GPL, that the copyright holders are properly identified (typically, you or your employer), and that the actual author of the code is identified. The latter is purely informational to try to track the most appropriate person to review a patch or fix a bug. If you edit an existing file, please do not add or change the Author line if it is present. Do not add the "All Rights Reserved" phrase after the copyright statement.
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* * Copyright (c) YEAR COPYRIGHTHOLDER * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation; * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Author: MyName*/
Below these C-style comments, always include the following which defines a set of header guards (MY_CLASS_H) used to avoid multiple header includes, which ensures that your code is included in the "ns3" namespace and which provides a set of doxygen comments for the public part of your class API. Detailed information on the set of tags available for doxygen documentation is described in the doxygen website.
#ifndef MY_CLASS_H
#define MY_CLASS_H
namespace n3 {
/**
* \brief short one-line description of the purpose of your class
*
* A longer description of the purpose of your class after a blank
* empty line.
*/
class MyClass
{
public:
MyClass ();
/**
* \param firstParam a short description of the purpose of this parameter
* \returns a short description of what is returned from this function.
*
* A detailed description of the purpose of the method.
*/
int DoSomething (int firstParam);
private:
void MyPrivateMethod ();
int m_myPrivateMemberVariable;
};
} // namespace ns3
#endif /* MY_CLASS_H */
The my-class.cc file is structured similarly:
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /* * Copyright (c) YEAR COPYRIGHTHOLDER * * 3-paragraph GPL blurb * * Author: MyName*/ #include "my-class.h" namespace ns3 { MyClass::MyClass () {} ... } // namespace ns3
Use of namespaces
As shown in the preceding example, ns-3 uses the "ns3" namespace for code implemented in the src/ directory. Additional namespaces may be nested inside of ns3, such as:
namespace ns3 {
namespace olsr {
...
} // namespace olsr
} // namespace ns3
Do not use the "using namespace ns3" or "using namespace std" within ns-3 source code in the src/ directory. However, ns-3 programs outside of the src/ directory (including examples/, samples/, and test/) may use the "using namespace ns3" or "using namespace std".
Doxygen
The project uses Doxygen to document the interfaces, and uses comments for improving the clarity of the code internally. All public methods should have Doxygen comments. Doxygen comments should use the C comment style. For non-public methods, the \internal command should be used. Please use the \see command for cross-referencing. All parameters and return values should be documented.
Every public function should be preceded by a detailed (Doxygen) comment block describing what the function does, what the formal parameters are, and what the return value is (if any). Every public class declaration should be preceded by a (Doxygen) comment block describing what the class is to be used for. We also recommend to add doxygen to private classes as an aid to future users and maintainers but this is not required.
Doxygen permits a number of equivalent styles. Doxygen commands start with a backslash (\) or an at-sign (@). ns-3 prefers the backslash in general, and typically uses the JavaDoc style or the C++ style of markup, but not the Qt style that uses exclamation marks:
- Modules (groups) are documented such as follows from aodv.h:
/** * \ingroup routing * \defgroup aodv AODV * * This model implements the base specification of the Ad hoc on demand * distance vector (AODV) protocol. Implementation is based on RFC3561. * * Class aodv::RoutingProtocol implements all functionality of service * packet exchange and inherits Ipv4RoutingProtocol. Base class defines * two virtual functions for packet routing and forwarding. The first one, * RouteOutput(), is used for locally originated packets, and the second * one, RouteInput(), is used for forwarding and/or delivering * received packets. ... */
- Classes are documented such as follows from ipv4-routing-protocol.h:
/** * \ingroup ipv4ListRouting * * This class is a specialization of Ipv4RoutingProtocol that allows * other instances of Ipv4RoutingProtocol to be inserted in a * prioritized list. Routing protocols in the list are consulted one * by one, from highest to lowest priority, until a routing protocol * is found that will take the packet (this corresponds to a non-zero * return value to RouteOutput, or a return value of true to RouteInput). * The order by which routing protocols with the same priority value * are consulted is undefined. * */ class Ipv4ListRouting : public Ipv4RoutingProtocol
- Member functions are typically documented such as follows:
/** * \brief Register a new routing protocol to be used in this IPv4 stack * * \param routingProtocol new routing protocol implementation object * \param priority priority to give to this routing protocol. * Values may range between -32768 and +32767. */ virtual void AddRoutingProtocol (Ptr
routingProtocol, int16_t priority); - Doxygen grouping may be used; an example of this is the AODV module
such as this statement that groups all of the inherited virtual methods:
///\name From Ipv4RoutingProtocol //\{ PtrRouteOutput (Ptr p, const Ipv4Header &header ...); bool RouteInput (Ptr p, const Ipv4Header &header, Ptr idev, UnicastForwardCallback ucb, MulticastForwardCallback mcb, LocalDeliverCallback lcb, ErrorCallback ecb); virtual void NotifyInterfaceUp (uint32_t interface); virtual void NotifyInterfaceDown (uint32_t interface); virtual void NotifyAddAddress (uint32_t interface, Ipv4InterfaceAddress address); virtual void NotifyRemoveAddress (uint32_t interface, Ipv4InterfaceAddress address); virtual void SetIpv4 (Ptr ipv4); //\} - In-body documentation of member variables may be used such as follows:
uint32_t RreqRetries; ///< Maximum number of retransmissions of RREQ
Comments
As for comments within the code, comments should be used to describe intention or algorithmic overview where is it not immediately obvious from reading the code alone. There are no minimum comment requirements and small routines probably need no commenting at all, but it is hoped that many larger routines will have commenting to aid future maintainers. Please write complete English sentences and capitalize the first word unless a lower-case identifier begins the sentence. Two spaces after each sentence helps to make emacs sentence commands work.
Short one-line comments and long comments can use the C++ comment style; that is, '//', but longer comments may use C-style comments:
/* * A longer comment, * with multiple lines. */
Variable declaration should have a short, one or two line comment describing the purpose of the variable, unless it is a local variable whose use is obvious from the context. The short comment should be on the same line as the variable declaration, unless it is too long, in which case it should be on the preceding lines. See above for an example of how to use Doxygen in-body documentation style for this.
Miscellaneous items
- The following emacs mode line should be the first line in a file:
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
- Const reference syntax:
void MySub (const T&); // Method 1 (prefer this syntax) void MySub (T const&); // Method 2 (avoid this syntax)
- Use a space between the function name and the parentheses, e.g.:
void MySub(const T&); // avoid this void MySub (const T&); // use this instead
This spacing rule applies both to function declarations and invocations. - Do not include inline implementations in header files; put all implementation in a .cc file (unless implementation in the header file brings demonstrable and significant performance improvement).
- Do not use "nil" or "NULL" constants; use "0" (improves portability).
When checking pointers against null values, use
if (ptr == 0) {instead of "if (ptr == NULL)" or "if (!ptr)" - Consider whether you want the default copy constructor and assignment
operator in your class, and if not, make them private such as follows:
private: ClassName (const Classname&); ClassName& operator= (const ClassName&)
- Avoid returning a reference to an internal or local member of an object:
a_type& foo (); // should be avoided, return a pointer or an object. const a_type& foo (); // same as above
This guidance does not apply to the use of references to implement operators. - Expose class members through access functions, rather than direct access to a public object. The access functions are typically named "Get" and "Set". For example, a member m_delayTime might have accessor functions GetDelayTime () and SetDelayTime ().
- For testing whether a boolean variable is true, do not write
if (ret == true)
but insteadif (ret)
- Provide default constructors for publicly visible classes; the python bindings code requires this when those classes have overloaded arithmetic operators.
- Do not declare methods that are not implemented.
- If functions are declared only as friend functions but not declared also as plain functions, gccxml or pybindgen do not like this, so declare the friend functions outside the class that they're friend of
- Do not use variable-length arrays. Variable-length arrays are not standards-compliant C++; use instead std::vector, std::string, or operator new[] when your code might call for a variable-length array.
Design guidelines
Aside from layout requirements and guidelines, we recommend a few conventions for C++ code. There may be more than one way to correctly implement some things, but for the sake of uniformity, we prefer to consistently do it one way or another. We also want to make consistent use of the features available to ns-3 classes, such as logging and attributes.
- If a class deriving from ns3::Object has parameters that users may want the ability to change (such as default values), consider to code them as attributes rather than explicit setters and getters in the traditional C++ way. Please consult the manual if you are unsure about attributes.
- Consider the use of NS_LOG statements in the code, in particular,
NS_LOG_FUNCTION () for function call tracing, and NS_LOG_DEBUG or NS_LOG_LOGIC
for program flow logging. For example:
bool CsmaNetDevice::SetMtu (uint16_t mtu) { NS_LOG_FUNCTION (this << mtu); uint32_t newFrameSize = FrameSizeFromMtu (mtu); if (newFrameSize > std::numeric_limitsAvoid using the NS_LOG_FUNCTION_NO_ARGS variant unless for static functions.::max ()) { NS_LOG_WARN ("CsmaNetDevice::SetMtu(): Frame size overflow, MTU not set."); return false; } - Avoid making raw data members of a base class as protected members (for easy manipulation by subclasses). Instead, provide access to them through protected member functions.
- If you want to force the program to exit abnormally due to an error
condition, prefer
NS_FATAL_ERROR ("Some text to explain what went wrong and what you can do to fix it");toNS_ASSERT (0); or NS_ASSERT_MSG (0, "Message explaining what went wrong");
- In many places in the code, pointers are used without being checked
whether they are valid. Sometimes these lead to segmentation faults
reported by users. This project made a design decision to not
always explicitly check pointer validity throughout the codebase.
If segmentation faults are reported for
null pointer values, the preferred way to address each one is to
consider whether the model makes a poor assumption about whether a
pointer should be valid or not at all points in time in the object
lifecycle, or whether the error might represent a problem in the user
code. If the former, change the model to account properly for this
possibility. If instead the maintainer believes that the pointer
should always be valid, he or she may consider to add in this case the
following macro (defined in src/core/abort.h) to alert future users
more explicitly that an invalid pointer may be symptomatic of a problem
in the user's code.
#define NS_ABORT_MSG_UNLESS(cond, msg)
But in general, if a pointer is assumed by a model to always be valid, one does not need to explicitly check it before each usage.
Generated on 31/Aug/2011 at 11:00:03. Thanks to the XORP (http://www.xorp.org) project for this stylesheet.