Migrating to C++ From Other Languages

Alain Galvan
10 min readAug 29, 2020

--

How do I get started writing C++ projects? I have experience with JavaScript, Go, Rust, Ruby, etc. so what are the package managers, build tools, and libraries?

C++ is a systems level programming language designed as an extension to C, introducing classes, generics, object initializers, destructors and by extension Resource Allocation is Initialization (RAII), and much more. The language was designed by Dr. Bjorne Strustrup (@stroustrup) in 1985 at Bell Labs in Holmdel, New Jersey.

Source: A New Urbanist Developer Gives Saarinen a Reboot by Bloomberg

Since then it’s been the de-facto language for high performance computing, games, film rendering, simulations, scientific computing, the underlying language for machine learning libraries, native applications for Windows, MacOS/iOS (with Objective C++), Linux, game consoles, web browsers, databases, and so much more.

With so much support across the industry, it’s a shame the C++ ecosystem is much more difficult to work with than other languages such as Rust, JavaScript, Python, Go, etc.

There’s a variety of different IDEs, each with their own project files such as:

  • .sln
  • .xcworkspace
  • makefile, though this is more of a build script.

There’s a variety of 🔨 build systems that can abstract IDEs for multi-platform development such as:

There’s no official 📦 package manager, rather there are a variety of different package managers and intermediary solutions. These include:

It’s also common for projects to use precompiled binaries for libraries (.lib, .so, or .a) as well, since that saves them the trouble of recompiling, increasing build speeds at the cost of tying your project to the compiler version your library was built for.

There’s no official 📝 compiler, there’s a variety of compilers that operating system developers maintain, such as:

While the 📚 standard library is maintained heavily, it’s not uncommon for companies to maintain their own standard library alternatives to improve their application performance, or for unique features:

#include <vector>class MyActor : Actor
{
virtual void update() override
{
std::vector<std::string> strings;
strings.emplace_back("🐪 CamelCase");
strings.push_back("🐍 snake_case")
}
};

There is no standard for code, with some projects adopting 🐪 camelCase for functions and variables, some adopting 🐍 snake_case like the standard library, and some adopting prefixes for classes like Actors (♟️ AMyPawn) such as Unreal Engine. That being said there are linters and tools to enforce standards in formatting in your code such as:

While these might appear as weaknesses at first glance, the fact that there’s so many alternatives to all of these different classes of problems attests to how large and capable the C++ community is. That being said the fact that there’s no one opinion to each of these problems means that it’s hard for folks starting to learn C++ to know what to do. This post will serve as a guide and introduction to greenfield C++ for those migrating from other languages that feature more opinionated workflows such as JavaScript, Rust, etc.

CMake

CMake is the most common C++ Library/Application build tool, and makes writing applications and using dependencies much easier than managing them via IDE specific methods.

The following is a basic CMake CMakeLists.txt file you would put at the top directory of your library/application:

# Project Infocmake_minimum_required(VERSION 3.6 FATAL_ERROR)
cmake_policy(VERSION 3.6)
project(MyApplication
VERSION 1.0.0.0
LANGUAGES C CXX
)
# =============================================================# Common CMake Settings# Use Folders in Project Files
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
# Don't generate useless CMake Build Scripts
set(CMAKE_SUPPRESS_REGENERATION true)
# Use C++ 14
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# Write all libraries/executables to `/bin/`
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/bin)
SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/bin)
SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/bin)
SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/bin)
SET(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/bin)
SET(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/bin)
# Add a `d` postfix to denote debug libraries/executables
if(NOT CMAKE_DEBUG_POSTFIX)
set(CMAKE_DEBUG_POSTFIX d)
endif()
# =============================================================# Sources
file(GLOB_RECURSE FILE_SOURCES RELATIVE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/*.h
)
# Solution Filters
foreach(source IN LISTS FILE_SOURCES)
get_filename_component(source_path "${source}" PATH)
string(REPLACE "/" "\\" source_path_msvc "${source_path}")
string(REPLACE "src" "" source_path_final "${source_path_msvc}")
source_group("${source_path_final}" FILES "${source}")
endforeach()
# =============================================================# Finalize Appadd_executable(
${PROJECT_NAME}
"${FILE_SOURCES}"
)
# =============================================================# Dependencies# For example, you could import a git submodule like so:
# add_subdirectory(external/yourdependency)
# set_property(TARGET LibraryName PROPERTY FOLDER "Dependencies")
# target_link_libraries(${PROJECT_NAME} LibraryName)
# =============================================================# Finish Settings# Write this project's libraries/executables to `/bin/`
set_target_properties(${PROJECT_NAME} PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
)
# Change working directory to top dir to access `assets` folder
if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
set_property(TARGET ${PROJECT_NAME} PROPERTY VS_DEBUGGER_WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/..)
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ${PROJECT_NAME})
endif()

Installing Dependencies

There’s multiple ways to add dependencies, the one I would recommend the most is using git submodules. Since dependencies are built from source, there's less headaches involved when linking and building your final application.

# 📂 At your project root folder
mkdir external
cd external
git submodule add https://github.com/alaingalvan/crosswindow.git

And inside your CMakeLists.txt file:

# ✖ CrossWindow
add_subdirectory(external/crosswindow)
set_property(TARGET CrossWindow PROPERTY FOLDER "Dependencies")

Some projects distribute CMake build scripts that you can use to link dependencies to your project:

# 📦 Packages (installed by frameworks like Qt)
find_package(Qt5 COMPONENTS Core Gui REQUIRED)

And libraries accessible in system folders can also be found by CMake, such as Apple Frameworks:

# 📚 Libraries (such as Apple Frameworks like Cocoa, Metal, UIKit, etc.)
find_library(METAL_LIBRARY Metal)

You can also add libraries manually by just including the source files like so:

# 👩‍💻 Cpp and Headers
add_library(Glad external/glad/src/glad.c)
target_include_directories(Glad PRIVATE external/glad/include)
include_directories(external/glad/include)

Or if a project is header only, you could just include that directory, just be sure to call this after you’ve created your binary project with cmake function add_executable, refer to the example script above if anything.

# 👒 Header Only
target_include_directories(${PROJECT_NAME} external/json/include)

From there you can link that dependency to your project like so:

target_link_libraries(${PROJECT_NAME} LibraryName)
add_dependencies(${PROJECT_NAME} LibraryName)

Building

To build a project you’ve downloaded with the terminal, similar to npm start for Node.js, cargo run for Rust, etc. you can do the following:

# 👷 Make a build folder
mkdir build
cd build
# 🖼️ To build your Visual Studio solution on Windows x64
cmake .. -A x64
# 🍎 To build your XCode project On Mac OS for Mac OS
cmake .. -G Xcode
# 📱 To build your XCode project on Mac OS for iOS / iPad OS / tvOS / watchOS
cmake .. -G Xcode -DCMAKE_SYSTEM_NAME=iOS
# 🐧 To build your .make file on Linux
cmake ..
# 🌐 For WebAssembly Projects
cmake .. -DCMAKE_TOOLCHAIN_FILE="$EMSDK/emscripten/<YOUR_EMSCRIPTEN_VERSION>/cmake/Modules/Platform/Emscripten.cmake"
# 🤖 For Android write a build.gradle file# 🔨 Build on any platform:
cmake --build .

With 🌐 WebAssembly Projects, you’ll need to use emmake, Emscripten's alternative to the GNU Make.

# ⚙️ Run emconfigure with the normal configure command as an argument.
$EMSDK/emscripten/emconfigure ./configure
# Run emmake with the normal make to generate linked LLVM bitcode.
$EMSDK/emscripten/emmake make
# Compile the linked bitcode generated by make (project.bc) to JavaScript.
# 'project.bc' should be replaced with the make output for your project (e.g. 'yourproject.so')
$EMSDK/emscripten/emcc project.bc -o project.js

For more information visit the Emscripten Docs on CMake.

For 🤖 Android Studio you’ll need to make a project, then edit your build.gradle file.

// 🤖 To build your Android Studio project
android {
...
externalNativeBuild {
cmake {
...
// Use the following syntax when passing arguments to variables:
// arguments "-DVAR_NAME=ARGUMENT".
arguments "",
// The following line passes 'rtti' and 'exceptions' to 'ANDROID_CPP_FEATURES'.
"-DANDROID_CPP_FEATURES=rtti exceptions"
}
}
buildTypes {...}
// Use this block to link Gradle to your CMake build script.
externalNativeBuild {
cmake {...}
}
}

for more information visit Android Studio’s docs on CMake.

Code Cleanup

Having a .clang-format file in your top level directory allows for text editors and IDEs to use that file to 🧹 format your code.

# Run manually to reformat a file:
# clang-format -i --style=file <file>
BasedOnStyle: llvm
DerivePointerAlignment: false
UseTab: Never
AllowShortIfStatementsOnASingleLine: true
IndentWidth: 4
TabWidth: 4
BreakBeforeBraces: Allman
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: true
Cpp11BracedListStyle: true
IndentWrappedFunctionNames: true
PointerAlignment: Left
FixNamespaceComments: false

Also, having a .clang-tidy file can help with maintaining your project's code styles and find odd errors:

Checks:  >
-*,
readability-*,
-readability-uppercase-literal-suffix,
-readability-magic-numbers,
-readability-isolate-declaration,
-readability-convert-member-functions-to-static,
-readability-implicit-bool-conversion,
-readability-avoid-const-params-in-decls,
-readability-simplify-boolean-expr,
-readability-make-member-function-const,
-readability-misleading-indentation, -readability-inconsistent-declaration-parameter-name,
-readability-redundant-preprocessor,
-readability-redundant-member-init,
-readability-const-return-type,
-readability-static-accessed-through-instance,
-readability-redundant-declaration,
-readability-qualified-auto,
-readability-use-anyofallof,
bugprone-*,
-bugprone-narrowing-conversions,
-bugprone-unhandled-self-assignment,
-bugprone-branch-clone,
-bugprone-macro-parentheses,
-bugprone-reserved-identifier,
-bugprone-sizeof-expression,
-bugprone-integer-division,
-bugprone-incorrect-roundings,
-bugprone-copy-constructor-init,
WarningsAsErrors: '*'

Testing

For the sake of making it easier for individuals to consume my libraries via git submodules, I keep all tests on a separate branch, and require that you specify if you want tests enabled. This means build specific dependencies like Google Test only need to exist as submodules if the user switches to that branch, if they're consuming the library they won't ever switch to that branch saving them installation and build time.

option(MYLIB_TESTS "If enabled, compile the tests." OFF)if (MYLIB_TESTS)
MESSAGE( STATUS "Building My Library Tests..." )
set_property(GLOBAL PROPERTY USE_FOLDERS ON) # we use this to get code coverage
if(CMAKE_CXX_COMPILER_ID MATCHES GNU)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage")
endif()
# Unit Tests
set(GOOGLETEST_ROOT external/googletest/googletest CACHE STRING "Google Test source root")
include_directories(
${PROJECT_SOURCE_DIR}/${GOOGLETEST_ROOT}
${PROJECT_SOURCE_DIR}/${GOOGLETEST_ROOT}/include
${PROJECT_SOURCE_DIR}/src
)
set(GOOGLETEST_SOURCES
${PROJECT_SOURCE_DIR}/${GOOGLETEST_ROOT}/src/gtest-all.cc
)
foreach(_source ${GOOGLETEST_SOURCES})
set_source_files_properties(${_source} PROPERTIES GENERATED 1)
endforeach()
add_library(GoogleTest ${GOOGLETEST_SOURCES}) file(GLOB_RECURSE tests_list RELATIVE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/tests/*.cpp
${CMAKE_CURRENT_SOURCE_DIR}/tests/*.mm
${CMAKE_CURRENT_SOURCE_DIR}/tests/*.h
${CMAKE_CURRENT_SOURCE_DIR}/tests/*.hpp
)
# Solution Filters
foreach(test IN LISTS tests_list)
get_filename_component(test_path "${test}" PATH)
string(REPLACE "/" "\\" test_path_msvc "${test_path}")
string(REPLACE "tests" "" test_path_final "${test_path_msvc}")
source_group("${test_path_final}" FILES "${test}")
endforeach()
add_executable(
Tests
"${tests_list}"
)
if(NOT MSVC)
find_package(Threads REQUIRED)
set(PThreadLib -pthread)
endif()
target_link_libraries(
Tests
${PROJECT_NAME}
${PThreadLib}
GoogleTest
)
add_dependencies(
Tests
${PROJECT_NAME}
GoogleTest
)
include(CTest)
enable_testing()
add_test(
NAME UnitTests
COMMAND Tests
)
set_property(TARGET GoogleTest PROPERTY FOLDER "Dependencies")endif()

Here’s an example of writing a test in Google Test, though for your applications you’ll want to test out interfaces and tasks the library needs to do, such as parsing and compiling code, creating data structures, etc.

#include "gtest/gtest.h"TEST(TestCategory, TestName)
{
int x = 0;
EXPECT_TRUE(x == x);
}

Travis CI is a great option for testing changes in your code at build time when pushed to your Git server:

sudo: false
env:
global:
- CXX_FLAGS="-Wall -pedantic -Werror -Wno-variadic-macros -Wno-long-long -Wno-shadow"
language:
- cpp
compiler:
- g++
addons:
apt:
update: true
sources:
- ubuntu-toolchain-r-test
packages:
- lcov
- cmake
- cmake-data
before_install:
- export CXX_FLAGS=${CXX_FLAGS}" "${ENV_CXX_FLAGS}
script:
- git submodule update --init --recursive
- mkdir make
- cd make
- cmake .. -DMYAPP_TESTS=ON
- cmake --build .
- ctest . --verbose
after_success:
- lcov --directory . --capture --output-file coverage.info
- lcov --remove coverage.info '/usr/*' --output-file coverage.info
- lcov --list coverage.info
- bash <(curl -s https://codecov.io/bash) || echo "Codecov did not collect coverage reports"
notifications:
email: false

If you’re writing an application for Windows, you’ll need to use a continuous intergration tool like AppVeyor:

# Windows Build Configuration for AppVeyor
# http://www.appveyor.com/docs/appveyor-yml
# build version format
version: "{build}"
os: Visual Studio 2017platform:
- x64
configuration:
- Release
branches:
only:
- master
build:
parallel: true
verbosity: minimal
build_script:
- git checkout tests
- git submodule update --init --recursive
- mkdir visualstudio && cd visualstudio
- cmake .. -DMYAPP_TESTS=ON
- cmake --build .
- ctest . -C Debug --verbose
test_script:notifications:
- provider: Email
to:
- '{{commitAuthorEmail}}'
on_build_success: false
on_build_failure: false
on_build_status_changed: false

With Circle CI being useful for MacOS and Android Testing, though there is an cost to it:

machine:
environment:
ANDROID_NDK: $HOME/android-ndk-r12b
ANDROID_NDK_HOME: $ANDROID_NDK
PATH: $PATH:$ANDROID_NDK
general:dependencies:
cache_directories:
- ~/android-ndk-r12b
pre:
- git checkout tests
- git submodule update --init --recursive
#-- Install NDK r12b --
- if [[ ! -e ~/android-ndk-r12b ]]; then wget http://dl.google.com/android/repository/android-ndk-r12b-linux-x86_64.zip && unzip -d ~ android-ndk-r12b-linux-x86_64.zip; fi
#-- Intall Android SDK Build-tools, revision 24.0.0 --
- echo y | android update sdk --no-ui --all --filter "build-tools-24.0.0"
#---Install CMake--
- wget https://github.com/Commit451/android-cmake-installer/releases/download/1.1.0/install-cmake.sh
- chmod +x install-cmake.sh
- ./install-cmake.sh
#------------------
- echo "ndk.dir=/home/ubuntu/android-ndk-r12b" > local.properties
- chmod +x gradlew
-
test:
override:
- ./gradlew assembleDebug

Conclusion

C++ is an incredibly useful language for highly performant applications such as games with Unreal Engine 4 or your own custom engine, geometric processing, physical simulations, and native application development across operating systems. These are the most common use cases, but it’s also possible to develop web servers as most backend databases use C++, just about anything can be written in it.

More Resources

It can be helpful to look at existing projects as a guide to designing your own greenfield applications. I also have a large list of amazing C++ projects on my 🌟 GitHub stars, so take a look, here are some ✨ highlights:

There’s a few other resources on C++ best practices here:

And for resources specific to CMake:

Also, the journey to learning any programming language can be daunting to do alone, so I would encourage you to reach out to mentors on Twitter, send DMs and emails, talk on Discord/Slack/Twitter/Meetups, and don’t be afraid to communicate your goals and ideas!

--

--

Alain Galvan
Alain Galvan

Written by Alain Galvan

https://Alain.xyz | Graphics Software Engineer @ AMD, Previously @ Marmoset.co. Guest lecturer talking about 🛆 Computer Graphics, ✍ tech author.

No responses yet