Comprehending any large code base is never a simple factor. This is especially true of C++ with hits heavily used (and abused) typedef system. The Unreal Engine has a very well defined and consistent naming convention but trying to crack it can take a bit of effort, as it’s explained piecemeal throughout the documentation. That is, until I found the Coding Standard document, which frankly is the very first page a C++ programmer working with UE4 should start with. This document is the titular Rosetta Stone, the method that makes reading UE code possible.
Understanding their naming conventions will help you a great deal when trying to work through code samples. A few of their choices strike me as a bit unusual, especially if you were raised on Hungarian like I was. Here are the naming convention:
The first letter of each word in a name (e.g. type or variable) is capitalized, and there is usually no underscore between words. For example, Health and UPrimitiveComponent, but not lastMouseCoordinates or delta_coordinates.
Type names are prefixed with an additional upper-case letter to distinguish them from variable names. For example, FSkin is a type name, and Skin is an instance of a FSkin.
Template classes are prefixed by T.
Classes that inherit from UObject are prefixed by U.
Classes that inherit from AActor are prefixed by A.
Classes that inherit from SWidget are prefixed by S.
Classes that are abstract interfaces are prefixed by I.
Most other classes are prefixed by F, though some subsystems use other letters.
Type and variable names are nouns.
Method names are verbs that describe the method's effect, or describe the return value of a method that has no effect.
Variable, method, and class names should be clear, unambiguous, and descriptive. The greater the scope of the name, the greater the importance of a good, descriptive name. Avoid over-abbreviation.
All variables should be declared one at time so that a comment on the meaning of the variable can be provided. Also, the JavaDocs style requires it. You can use multi-line or single line comments before a variable, and the blank line is optional for grouping variables.
All functions that return a bool should ask a true/false question, such as "IsVisible()", or "ShouldClearBuffer()". All boolean variables must be prefixed with a "b" character (e.g. "bPendingDestruction", or "bHasFadedIn").
A procedure (a function with no return value) should use a strong verb followed by an object. An exception is if the object of the method is the object it is in; then the object is understood from context. Names to avoid include those beginning with "Handle" and "Process"; the verbs are ambiguous.
Though not required, we encourage you to prefix function parameter names with "Out" if they are passed by reference and the function is expected to write to that value. This makes it obvious that the value passed in this argument will be replaced by the function.
This explains all the AClass and UProperty you will encounter through out the code, and explains the far to common FSomethings you see in the code..
There are a few things I find unwieldy about these choices. First, I am so used to instances starting with lower case that this seems like Anathema to me. Second, if you are going to prefix with all capital letters, why not BBoolValue instead of bBoolValue, this seems inconsitent to me. Next, why F for types? Finally why SWidget and S instead of Widget and W? WButton makes a ton more sense than SButton for example.
At the end of the day it doesn’t really matter, the most important thing is to be consistent, which they have. Now with a thorough understanding of their naming convention, groking the code is a heck of a lot easier.
Also of great important are the C++ type usage guidelines. This is both a power and flaw of working with C++, there are more types than grains of sand in the desert, so knowing which to use is critical.
Portable Aliases for Basic C++ Types
bool for boolean values (NEVER assume the size of bool). BOOL will not compile.
TCHAR for a character (NEVER assume the size of TCHAR)
uint8 for unsigned bytes (1 byte)
int8 for signed bytes (1 byte)
uint16 for unsigned "shorts" (2 bytes)
int16 for signed "shorts" (2 bytes)
uint32 for unsigned ints (4 bytes)
int32 for signed ints (4 bytes)
uint64 for unsigned "quad words" (8 bytes)
int64 for signed "quad words" (8 bytes)
float for single precision floating point (4 bytes)
double for double precision floating point (8 bytes)
PTRINT for an integer that may hold a pointer (NEVER assume the size of PTRINT)
Don't use the C++ int type in portable code, since it's dependent on the compiler how large it is.
So if you are just starting out with C++ programming in Unreal, start here, it will make the journey a lot easier.
Finally C++ 11 guidelines are also important. C++11 support across compilers is by no means universal but many of the features of C++11 are pretty damned useful. Keep in mind, these guidelines are for Unreal staff working on the engine, not the end user working with the engine, so if you are say… only using Visual C++ 13, then feel free to use whatever features you want. If you want to target as many platforms as possible though, you should be mindful of these instructions:
C++ 11 and Modern Language Syntax
Unreal Engine is built to be massively portable to many C++ compilers, so we're careful to use features that are compatible with the compilers we can imagine supporting. Sometimes features are so useful that we'll wrap them up in macros and use them pervasively (such as the 'override' keyword), but usually we'll wait until all of the compilers we could imagine supporting are up to the latest standard.
We are utilizing certain C++ 11 language features that appear to be well-supported across modern compilers, such as the "auto" keyword, range-based-for and lambdas. In some cases we're able to wrap up usage of these features in preprocessor conditionals (such as rvalue references in containers.) However, certain language features we may opt to avoid entirely until we're confident we won't be surprised by a new platform appearing that cannot digest the syntax.
Unless specified below as a modern C++ compiler feature we are supporting, you should refrain from using compiler-specific language features unless they are wrapped in preprocessor macros or conditionals and used sparingly.
The 'auto' Keyword
The 'auto' keyword is supported by all compilers UE4 targets and you are encouraged to use it in your code where it makes sense to do so.
Remember you can and should use const, & or * with auto just like you would with the type name. With auto, this will coerce the inferred type to be what you want.
We encourage use of the auto keyword for iterator loops (eliminates boilerplate), and also when you are initializing a variable to a new instance (eliminates redundant type name.) Some of the other uses are more contentious, but feel free to use it however you want for now and we can learn and improve best practices as we go.
Tip: If you hover over a variable in Visual Studio, it will usually tell you the inferred type.
Range Based For
This is allowed in all engine and editor code and encouraged where it can help to keep the code easier to understand and more maintainable.
Lambdas and Anonymous Functions
Lambdas are now allowed, however we are cautious about use of stateful lambdas that capture stack variables -- we're still learning about where those are appropriate. Also, stateful lambdas cannot be assigned to function pointers which we tend to use a lot. We expect to update this documentation in the future as we establish best practices.
So, basically ranged based for loops, stateless lambdas good, stateful lambda bad and auto is a maybe. For the remaining features that aren’t cross platform like override, they’ve provided preprocessor conditionals to work with them. I really wish they listed them all here though.