- Formatting
- Left-leaning (C++ style) asterisks for pointer declarations
- C++ style comments
- 2 spaces of indentation for blocks or bodies of conditionals
- 4 spaces of indentation for statement continuations
- Align function arguments vertically
- Initialization lists
- CamelCase for methods, functions, and classes
- snake_case for local variables and parameters
- snake_case_ for private class fields
- snake_case_ for C-like structs
- Space after
template
- Memory Management
- Others
Unfortunately, the C++ linter (based on
Google’s cpplint
), which can be run
explicitly via make lint-cpp
, does not currently catch a lot of rules that are
specific to the Node.js C++ code base. This document explains the most common of
these rules:
char* buffer;
instead of char *buffer;
Use C++ style comments (//
) for both single-line and multi-line comments.
Comments should also start with uppercase and finish with a dot.
Examples:
// A single-line comment.
// Multi-line comments
// should also use C++
// style comments.
The codebase may contain old C style comments (/* */
) from before this was the
preferred style. Feel free to update old comments to the preferred style when
working on code in the immediate vicinity or when changing/improving those
comments.
if (foo)
bar();
or
if (foo) {
bar();
baz();
}
Braces are optional if the statement body only has one line.
namespace
s receive no indentation on their own.
VeryLongTypeName very_long_result = SomeValueWithAVeryLongName +
SomeOtherValueWithAVeryLongName;
Operators are before the line break in these cases.
void FunctionWithAVeryLongName(int parameter_with_a_very_long_name,
double other_parameter_with_a_very_long_name,
...);
If that doesn’t work, break after the (
and use 4 spaces of indentation:
void FunctionWithAReallyReallyReallyLongNameSeriouslyStopIt(
int okay_there_is_no_space_left_in_the_previous_line,
...);
Long initialization lists are formatted like this:
HandleWrap::HandleWrap(Environment* env,
Local<Object> object,
uv_handle_t* handle,
AsyncWrap::ProviderType provider)
: AsyncWrap(env, object, provider),
state_(kInitialized),
handle_(handle) {
Exceptions are simple getters/setters, which are named property_name()
and
set_property_name()
, respectively.
class FooBar {
public:
void DoSomething();
static void DoSomethingButItsStaticInstead();
void set_foo_flag(int flag_value);
int foo_flag() const; // Use const-correctness whenever possible.
};
int FunctionThatDoesSomething(const char* important_string) {
const char* pointer_into_string = important_string;
}
class Foo {
private:
int counter_ = 0;
};
For plain C-like structs snake_case can be used.
struct foo_bar {
int name;
}
template <typename T>
class FancyContainer {
...
}
Malloc()
,Calloc()
, etc. fromutil.h
abort in Out-of-Memory situationsUncheckedMalloc()
, etc. returnnullptr
in OOM situations
What it says in the title.
"Smart" pointers are classes that act like pointers, e.g.
by overloading the *
and ->
operators. Some smart pointer types can be
used to automate ownership bookkeeping, to ensure these responsibilities are
met. std::unique_ptr
is a smart pointer type introduced in C++11, which
expresses exclusive ownership of a dynamically allocated object; the object
is deleted when the std::unique_ptr
goes out of scope. It cannot be
copied, but can be moved to represent ownership transfer.
std::shared_ptr
is a smart pointer type that expresses shared ownership of a
dynamically allocated object. std::shared_ptr
s can be copied; ownership
of the object is shared among all copies, and the object
is deleted when the last std::shared_ptr
is destroyed.
Prefer to use std::unique_ptr
to make ownership
transfer explicit. For example:
std::unique_ptr<Foo> FooFactory();
void FooConsumer(std::unique_ptr<Foo> ptr);
Never use std::auto_ptr
. Instead, use std::unique_ptr
.
- Always avoid C-style casts (
(type)value
) dynamic_cast
does not work because RTTI is not enabled- Use
static_cast
for casting whenever it works reinterpret_cast
is okay ifstatic_cast
is not appropriate
Do
#include "util-inl.h" // already includes util.h
instead of
#include "util.h"
#include "util-inl.h"
When there is a need to throw errors from a C++ binding method, try to
return the data necessary for constructing the errors to JavaScript,
then construct and throw the errors using lib/internal/errors.js
.
Note that in general, type-checks on arguments should be done in JavaScript
before the arguments are passed into C++. Then in the C++ binding, simply using
CHECK
assertions to guard against invalid arguments should be enough.
If the return value of the binding cannot be used to signal failures or return the necessary data for constructing errors in JavaScript, pass a context object to the binding and put the necessary data inside in C++. For example:
void Foo(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
// Let the JavaScript handle the actual type-checking,
// only assertions are placed in C++
CHECK_EQ(args.Length(), 2);
CHECK(args[0]->IsString());
CHECK(args[1]->IsObject());
int err = DoSomethingWith(args[0].As<String>());
if (err) {
// Put the data inside the error context
Local<Object> ctx = args[1].As<Object>();
Local<String> key = FIXED_ONE_BYTE_STRING(env->isolate(), "code");
ctx->Set(env->context(), key, err).FromJust();
} else {
args.GetReturnValue().Set(something_to_return);
}
}
// In the initialize function
env->SetMethod(target, "foo", Foo);
exports.foo = function(str) {
// Prefer doing the type-checks in JavaScript
if (typeof str !== 'string') {
throw new errors.codes.ERR_INVALID_ARG_TYPE('str', 'string');
}
const ctx = {};
const result = binding.foo(str, ctx);
if (ctx.code !== undefined) {
throw new errors.codes.ERR_ERROR_NAME(ctx.code);
}
return result;
};
When you have to throw the errors from C++, try to do it at the top level and not inside of nested calls.
Using C++ throw
is not allowed.