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:
  • F4: Debug.StartWithoutDebugging
  • F5: Debug.Start
  • F6: ReSharper.ReSharper_ReSharper_UnitTest_RunContext
  • Shift+F6: ReSharper.ReSharper_UnitTestSession_RepeatPreviousRun
  • F7: Build.BuildSelection
  • Shift+F8: ReSharper_UnitTestSession_RunAllTestsInSession
  • F9: ReSharper.ReSharper_ReSharper_UnitTest_DebugContext
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):
  • Ctrl+F6: TestExplorer.RunAllTestsInContext (like F6 in C#)
  • Ctrl+F7: TestExplorer.RepeatLastRun (like Shift+F6 in C#)
  • Ctrl+F9: TestExplorer.DebugAllTestsInContext (like F9 in C#)

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, 
  • STL and template functions errors.
  • Linking missings symbols, as they are not added on the generation of the library
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:
  • "using namespace xy" should NOT be used in headers at all. Feel free to use it in cpp files.
  • Classes should always have a comment. It describes shortly what each class does, 1 sentence, if possible in one line.
  • Rarely methods will have one too. Like in C# comments should be avoided at all times, even inside methods, but in C++ sometimes an explanation can help complicated code.
  • Header files must always provide the declaration of methods, but never their implementation (except template classes).
  • No header file should be included unless required by the declaration.
  • All public elements should appear before protected ones, and all protected before privates. In each of these sections, class members should be organized following C# coding style.
  • Methods and fields are separated by a blank line, same goes for public, protected and private sections. If a method is implemented in the header it can be surrounded by blank lines too. Otherwise there are no blank lines between methods in the header.
  • If the class is suppossed to be created automatically via constructor injection use the AutowiredSignature like in the example above. Put it directly in front of the constructor.
  • Keep all typedefs, the constructor, destructor and Dispose in one block, then add an empty line before starting with the public methods.

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

  • Each class should start by a short summary as in the example above.
  • Core classes should explain their functionality and which .NET classes they mimic.
  • For the others, the summary must match the one found in the C# version of the engine.

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

  • .NET Interfaces converted to C++ are usually marked with an I (to have the same name in native code as well). They are however rarely used, but sometimes necessary. There are also quite a lot of interfaces from C# in the Delta Engine, which is usually something you can see when looking at the code (all virtual abstract methods) and from the class comment. To avoid the multiple inheritance diamond problem virtual should always be added to the base class definition:
    1
    2
    3
    4
    class Window : public virtual Core::Object, public virtualCore::IDisposable
    {
        //...
    }
    For more details you can also checkout this article (from Chapter 6, Thinking in C++ by Bruce Eckel), which basically tells you for normal C++ code you should avoid multiple inheritance. For us we want to keep the source code as similar and compatible as possible to the C# counterparts. Our build system will optimize away unneeded base classes anyway.

  • When creating a new project, e.g. a native unit test project, remove all files and folders to keep things simple (most test projects just have a couple of simple cpp files).

  • The External Dependencies folder in VS2010+ is annoying and confusing once you open up some external dependency, which also exists in another project. Opening that file directly will result in an annoying dialog box "The file is already open in a different context." and it will expanding the External Dependencies folder. This is really annoying too because you have to scroll up many screens and close it all over and over again and the folder is not useful anyway. So just turn it off at Tools -> Options -> Text Editor -> C++ -> Advanced -> Disable External Dependencies folder.

  • When getting "warning LNK4042: object specified more than once; extras ignored" make sure you have no conflicting obj files generated with the same name in your project. This can happen for several reasons, but since we rarely use folders the most common cause for us is .h files wrongly be compiled as C++ source code (instead of C++ headers). Right click on the file and set the correct compile type in that case.

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.