Coding Style C++ Coding Style C++


Overview


For a great overview of good CPP guidelines check out the recent Modern C++ Code Core Guidelines!

This page is for the Coding Style of the C++ version of the engine. For .NET and C# see CodingStyle C#.

The DeltaEngineCpp repo is not simply a .NET C++/CLI version of the DeltaEngine, but instead a complete port to native C++11 code. It contains the code that is required to make the DeltaEngine work on platforms that do not support .NET code directly. Unlike the .NET public repository the native source code is only partially available (as header files, all test projects with full source code plus compiled libraries). This is mainly because we do generate compiled code directly while keeping the header files for each platform in sync. Currently do not plan to support native code back into the .NET public repository where we all work in. If you have a full company Delta Engine Services license and a signed NDA, please contact us so we can provide you with as much information, debugging files and full library source code we can provide. You are free to change any open part of the source code or extend it, which is easy to do. Changing low level components is dangeous and can cause code to become platform dependent quickly, but changing anything at a higher layer (above low level graphics, platform, multimedia, etc. modules) is highly encouraged.

We think DeltaEngineCpp is mostly useful for C++ programmers, for people that do not want to learn C# or for anyone still believing it makes a difference to write in native code only (we don't think it matters nowadays, profiling and optimizing whatever is slow is a much more useful approach than getting into language wars). There is obviously a lot of advantages with C++ like more control over the source code, memory management and tons of libraries. We do support C++ as a first class citizen of the Delta Engine, but we are not C++ gurus and we much rather program in C# and let C++ code be generated from it. Feel free to modify and if you have tips or want to join the team, let us know :)

Please note that we also plan to support more programming languages in the future (like Java, Lua, Python, maybe HTML5/Javascript?). We don't know yet, but if you have ideas or feedback or even want to help out, use the Forum. Thank you.

Used Tools

All the tools used for programming in C#, with the following additions:

All of these dependencies are included in DeltaEngine (Hypodermic, boost), but you can also install and use other versions (just copy into the appropriate directories). See Getting Started with Cpp for more information. For easier testing and debugging the engine is developed mostly in debug mode. Released libraries are in Release mode for better performance!

Following environment paths are recommended, but not required (you can still use the default paths and everything works, or fix up the paths yourself in your project):
  • BOOST_HOME: default path for boost, usually sits at c:\program files\boost\boost_1_51 If this is used the boost directory in your DeltaEngineCpp repositories will NOT be used, BOOST_HOME is always prefered
  • DeltaEngineCppPath: default path for your DeltaEngineCpp repository, usually sits atc:\code\DeltaEngineCpp

Keybindings

Use the same keys as for the C# coding style (F4 to start without debugging, F5 to start with debugging, etc.). This is important for pair programming as well as everyone should feel at home. As a reminder, these are the most important extra VS hotkeys from our CodingStyle: On top of those and VS and Visual Assist X hotkeys, these additional keys should be used for testing (Shift is used to avoid conflicts with existing C# hotkeys):

New Projects

Before any code can be added to a new project, the following steps must be made:

  1. Open the project Properties (always use All Configurations to edit).
  2. Csxproj files are very fragile, in case anything is not working anymore, compare with other working csxproj files and make sure the structure is the same (e.g. ImportGroups order is important for Intellisense and some tools).
  3. Set both the configuration type and target extension to .exe for tests and apps, lib for internal library development or .dll for external or engine libraries.
  4. In case DeltaEngineCpp is not at your $(SolutionDir) please include it accordingly to your include and library directories. We recommend setting up an environment path and using $(DeltaEngineCppPath):
    • Add $(DeltaEngineCppPath) and $(BOOST_HOME) (optional, everthing from boost that is needed internally for Hypodermic is already included in DeltaEngineCppPath) as additional include directories. Alternatives: $(SolutionDir) (if DeltaEngineCpp is part of your solution).
    • Add $(DeltaEngineCppPath)/lib as your additional library directories. Alternatives:$(SolutionDir)/lib.
    • Either include just the header files and libraries you need (recommended) you can also include everything with DeltaEngine.h and DeltaEngine.props.
Example header file for DeltaEngine.Graphics.OpenGL.h:
1
2
#include "Graphics/OpenGL/OpenGLImage.h"
#include "Graphics/OpenGL/OpenGLDevice.h"
And the 
DeltaEngine.Graphics.OpenGL.props property sheet to simplify including dependencies:
1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0"xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <ImportGroup Label="PropertySheets" />
    <PropertyGroup Label="UserMacros" />
    <PropertyGroup />
    <ItemDefinitionGroup>
        <Link>
            <AdditionalDependencies>DeltaEngine.Graphics.OpenGL.lib;%(AdditionalDependencies)</AdditionalDependencies>
        </Link>
    </ItemDefinitionGroup>
    <ItemGroup />
</Project>

DEPENDENCIES

If you are creating a dynamic linked (.DLL) library that uses other DeltaEngine Libraries, you may have to solve some dependency issues that may occur,  In order to avoid to add the projects on the Frameworks and References section of the Properties of the project, so the projects can be compiled for the calling project in order to solve the dependencies.

Add the projects

This makes our project to compile the needed project, generate the object files for the code, and link from them the library.
In order to take the object files directly into our DLL, we need to set the Linker parameter to "Use Library Dependency Inputs".

UseLibraryDependenyInputs


This allows to create libraries with the functions of its depending projects embedded on the dinamic library.
 
As in C# you have to be careful to avoid circular references, like: Library 1 depending on Library 2, and Library 2 depending of Library 1.

Test Projects

If you want us to support other native test frameworks (UnitTest++Google TestCppUnitWinUnit, etc.), let us know in the Forum!

Tests are written using Visual Studio Native Unit Test Support because it is the easiest way to test native code in VS. Hence, new test projects must be created using the Native Test Project template. Afterwards, the default solution folders and precompiled headers should be removed.

Note: When using Run All or the automatic test runner after each build of the Test Explorer window in Visual Studio, it is highly recommended to limit the tests with a filter to -Trait:"Slow" to exclude all slow and integration tests. Remove the filter to run integration tests from time to time.

There should not be any header file except for helper classes (e.g. Mocks). There must be only one .cppfile per class to test, which should have the following format:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <CppUnitTest.h>
#include "MyProject/MyClass.h"
 
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace DeltaEngine { namespace MyNamespace { namespace Tests
{
    TEST_CLASS(MyClassTests)
    {
    public:
        TEST_METHOD(MyClass_TestMethodName)
        {
            // Some test here.
            Assert::AreEqual(expectedValue, actualValue);
        }
 
        TEST_METHOD(MyClass_OtherTest)
        {
            // Other test here.
            Assert::IsTrue(trueValue);
        }
        // ...
    };
}}}
For more information, read this guide.

Mocks

Writing mocks is not as easy or automated like in .NET, but still possible via Google Mock. Usually (like in many cases with simple C# mocks) it is more than enough to quickly create your own derived class without any mocking framework in the test project where the mock is needed.

Simple example, for a more complex one check out the MockWindow in DeltaEngine.Platforms.Tests:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class MockWindow : public Window, public IDisposable
{
public:
    MockWindow(List<string>* output)
    {
        this->output = output;
    }
  
    void Run()
    {
        output->Add("Window.Run");
    }
  
    void Dispose() {}
  
private:
    List<string>* output;
};

Visual Tests

You should test as much as possible with automated unit tests and run all tests after each build. However tests that take longer than 10ms should be integration tests and tests that need user input should be visual tests. An example is closing a window after confirming visually it has been created and looks correct. While compiling and starting automatic unit tests takes much longer in C++ than in C#, the actual execution speed of unit tests is much faster as there is no additional startup overhead, assembly loading costs or reflection. This means many tests that would be too slow in C# might fit into an automatic unit test in C++, but you should still make sure tests are as fast as possible and no actual windows, devices, etc. are created in automatic unit tests, which would annoy or confuse the programmer.

When derving from TestStarter class you can easily define integration and visual tests and when testing, they actually runs the same test method multiple times for each registered resolver. This is very useful for namespaces like graphics, multimedia, input, etc. tests, which need to be run with each framework separately.

For example take a look at WindowTests of DeltaEngine.Platforms.Tests, which runs either Integration tests or a Visual test:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
TEST_CLASS(WindowTests), TestStarter
{
public:
    VISUAL_TEST(Window_CreateWindow, std::shared_ptr<HypodermicResolver> resolver)
    {
        Start<Window>(resolver, [](std::shared_ptr<Window> window) {
            Assert::IsTrue(window->GetIsVisible());
        });
    }
      
    INTEGRATION_TEST(Window_ChangeTotalSize, std::shared_ptr<HypodermicResolver> resolver)
    {
        Start<Window>(resolver, [](std::shared_ptr<Window> window) {
            window->SetTotalPixelSize(Size(200.0f, 200.0f));
            Assert::AreEqual(Size(200.0f, 200.0f), window->GetTotalPixelSize());
        });
    }
};

Header Files

The following structure is used in all files:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#pragma once
#include "ElapsedTime.h"
#include "Hypodermic/AutowiredConstructor.h"
#include <memory>
  
namespace DeltaEngine { namespace Core
{
    // Provides the current app run time and delta time for the frame. All times are in seconds.
    class Time
    {
    public:
        typedef Hypodermic::AutowiredConstructor<Time(ElapsedTime*)> AutowiredSignature;
        Time(std::shared_ptr<ElapsedTime> elapsed);
  
        int GetFps();
        float GetCurrentDelta();
        long long GetMilliseconds();
        void Run();
        bool CheckEvery(float timeStepInSeconds);
        // Returns an accurate seconds float value for today, would get inaccurate with more days.
        float GetSecondsSinceStartToday();
  
    private:
        void SetFpsTo60InitiallyAndSetUsefulInitialValues();
        void UpdateFpsEverySecond();
      
#pragma warning(disable: 4251)
        const std::shared_ptr<ElapsedTime> elapsed;
        int fps;
        int framesCounter;
        long long thisFrameTicks;
        long long lastFrameTicks;
        float currentDelta;
    };
}}
Header Rules:

Namespace Headers

Each non-test namespace should have a header file and props file, located in the solution directory. These file are designed to help users including a whole namespace and all needed libraries easily, but it does not have to be used. In fact in the engine these files are never used and it is a better practice to only include headers and libraries you really need. However it is much simpler to just include a header and props file to get started with your project.

1
2
3
4
#include "SomeRequiredLibraryHeader.h"
#include "MyNamespace\HatFactory.h"
#include "MyNamespace\BigHat.h"
#include "MyNamespace\SmallHat.h"

Source Files

Every source file should start with an include statement for the corresponding namespace's header file. The namespace should also be used.

The implementation of class members should follow all the same rules as for C# files, except that static proprieties assignments must appear last.

1
2
3
4
5
6
7
#include "DeltaEngine.MyNamespace.h"
using namespace DeltaEngine::MyNamespace;
  
void HatFactory::MakeCap()
{
    // ...
}

Comments

Except for these summaries, no comments are usually not allowed in the code. Like in C# code TODO, HACK, commented out code and other author comments are forbidden, only when something really needs explanation (which is not that rare in C++) a comment should be used to help explain the issue to the reader.

Consts

Consts should only be used whenever some code requires const parameters (e.g. Assert::AreEqual for unit testing), usually this means operators are const. When the C++ code is generated const will be inserted if the generator knows that the body won't modify the parameters or fields. However sometimes const will be obmitted when not necessary for performance reasons (non-const code can sometimes be optimized better in C++11).
Const parameters and methods can be called with or without const parameters, but not the other way around. Most code affected by const is low level code for datatypes and basic classes. Also const is designed to be used as a protection for a programmer not to modify stuff at the wrong places, so you will also find it in high layers or in user written code when the programmer wants to avoid mistakes. For the mostly generated engine code it does not matter, but it might still be helpful to know if a method does not modify pointers passed into it (usually only happens when we would use ref or out in C#, which is very rare).

In tests, your own projects, game code, etc. you should of course use const where possible.

Tips

Build Times

To enable build times go to Visual Studio -> Options -> Projects and Solutions -> VC++ Project Settings and enable Build Timing:

Pointers

Use smart pointers if possible and especially at high level. Pointers should only be used when strictly necessary (when C++ does not provide any other reasonable solution). For small classes using them as value types usually makes more sense and is faster (datatypes, simple and short classes, etc.). In the case of doubt on whether or not to use pointers (or if std::shared_ptr might slow down some performance critical code), simply make a speed test for both solutions (in unit tests and integration tests) and optimize then.

Detecting Memory Leaks

Memory leak detection is build into App.h in debug mode, which is the entry point for any application or integration or visual test. When debugging and your application ends (either normally, with an exception or any other way) and it contains memory leaks a dump will be printed into the output window like:
1
2
3
4
Detected memory leaks! Dumping objects ->
{2123} normal block at 0x036073A8, 32 bytes long. Data: 69 6D 61 67 69 6E 61 74 69 6F 6E 20 74 65 63 68
{355} normal block at 0x008CB2B8, 140 bytes long. Data: < a > 94 0B 61 01 00 00 00 00 CD CD CD CD 00 00 00 00
...
Next you want to figure out where those leaks are coming from, which can be done by using_CtrSetBreakAlloc(dataBreakpointAddress) (e.g. in the SetupMemoryLeakDetectionInDebugModemethod of App.h) or via the Debug menu. Now restart with a debugger attached and you will see the line of code where new or malloc is used, but not properly released. When fixed remove _CtrSetBreakAlloc or the data breakpoints again.
We also recommend the Visual Leak Detector for Visual C++ 2008/2010/2012. Here are some additional tips and tools to hunt memory leaks:

Warnings

C++ compilers commonly generate several warnings, even for perfectly working code. Yet, warnings should be treated as errors, and solved before being pushed to the repository. Hence, the \wxcompiler flag should be enabled. The same goes for linker warnings (use the same /wx linker flag). Only as a last resort linker warnings should be disabled (e.g. when you understand the warning and just disagree with the linker).

When warnings are not solvable, or simply do not make sense, they should be ignored using the "disable" prepocessor directive.