This chapter describes code conventions that should be followed when writing C++ code.

Code Style

Memgraph uses the Google Style Guide for C++ in most of its code. You should follow them whenever writing new code. Besides following the style guide, take a look at C++ Code Review Guidelines for common design issues and pitfalls with C++.

Naming

For interface types we use the I prefix to distinguish from implementation types.

Often Overlooked Style Conventions

Pointers & References

References provide a shorter syntax for accessing members and better declare the intent that a pointer should not be nullptr. They do not prevent accessing a nullptr and obfuscate the client/calling code because the reference argument is passed just like a value. Errors with such code have been very difficult to debug. Therefore, pointers are always used. They will not prevent bugs but will make some of them more obvious when reading code.

The only time a reference can be used is if it is const. Note that this kind of reference is not allowed if it is stored somewhere, i.e. in a class. You should use a pointer to const then. The primary reason being is that references obscure the semantics of moving an object, thus making bugs with references pointing to invalid memory harder to track down.

This can be seen while capturing member variables by reference inside a lambda. Let’s define a class that has two members, where one of those members is a lambda that captures the this pointer.

<aside> ‼️ Only whole objects are captured; when bar means this->bar, it's this that is captured, not a copy of (or reference to) the bar alone. C++17 structured bindings, on the other hand, are indeed captured piece-by-piece.

</aside>

struct S {  
	std::function<void()> foo;  
	int bar;  
	S() : foo([&]() { std::cout << bar; })  {}
};

What would happen if we move an instance of this object? Our lambda reference capture will point to the same location as before, i.e. it will point to the old memory location of this/bar. This means we have a dangling reference in our code! There are multiple ways to avoid this. The simple solutions would be capturing by value or disabling move constructors/assignments. Still, if we capture by reference an object that is not a member of the struct containing the lambda, we can still have a dangling reference if we move that object somewhere in our code and there is nothing we can do to prevent that. So, be careful with lambda captures, and remember that references are still a pointer under the hood!

Style guide reference

Proposed Pointers & References

References provide a shorter syntax for accessing members and better declare the intent that a pointer should not be nullptr. They do not prevent accessing a nullptr but eliminate a big chunk of nullptr checks. They also do not prevent accessing deallocated/destructed objects.

There is no big difference between pointers and references, but when an object is received by reference, then it is safe to assume it is a valid (not dangling) reference because the nullptr check should be done every time before a pointer is dereferenced. This can spare a lot of nullptr checks. Even Google has allowed the usage of mutable references, therefore it makes sense to do the same. Because of all of these reasons the mutable references are also allowed in Memgraph.

On the other hand, from a caller's point of view, it is good to be aware if the address of an object is stored in the called function. Therefore, if the address of an object is stored inside a function (in a way that it outlives the invocation of the function), then the object must be passed by a pointer to express this. In order to distinguish such parameters from "optional references", the gsl::not_null helper class should be used to indicate that pointer cannot be nullptr.

There is a conflict between optional references and optional objects whose addresses are stored: both should be passed by raw pointer. We assume currently there is no occurrence of an optional object whose address is stored, therefore there is no hard decision on that yet. The most compelling solution is an explicitly constructible simple type that expresses that the address is going to be stored. The summary is: