Migrating to C++ From Other Languages
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:
- Microsoft’s Standard Library for MSVC
- Electronic Arts (EA)’s EASTL
- Boost’s family of libraries
- Facebook’s Folly
#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 submodule
s. 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 submodule
s, 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 . --verboseafter_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:
- x64configuration:
- Releasebranches:
only:
- masterbuild:
parallel: true
verbosity: minimalbuild_script:
- git checkout tests
- git submodule update --init --recursive
- mkdir visualstudio && cd visualstudio
- cmake .. -DMYAPP_TESTS=ON
- cmake --build .
- ctest . -C Debug --verbosetest_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_NDKgeneral: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:
- Blender 3D repo’s shows their build philosophy and linting tools such as
clang-tidy
,clang-format
. - ImGUI is arguably the best UI library for C++.
- NVIDIA Falcor powers a ton of examples related to computer graphics in C++, including the NVIDIA Introductory Course on Ray Tracing.
- Google Filament by (@omainguy) is a full rendering engine and amazing reference.
- Epic Games’s Unreal Engine, though this requires that you register on Epic Games and tie your GitHub account to it.
- Boost’s Beast is an HTTP server library written in C++.
- Json by Niels Lohmann (@nlohmann) is effective JSON that can handle even massive files.
- Cereal by Shane Grant (@WShaneGrant) is one of my favorite serialization libraries.
- ChaiScript by Jason Turner (@lefticus) of Cppcast an embeded scripting language that’s easy to include in your projects.
- Sol2 by JeanHeyd Meneide (@thephantomderp) is a Lua library for parsing and executing Lua code bound to C++.
- RenderDoc by Baldur Karlsson (@baldurk) is one of the best debugging tools for computer graphics applications, and features a great Travis configuration that checks for formatting with pull requests.
There’s a few other resources on C++ best practices here:
- CppCon, CppNow and Cpp On Sea are research conferences focused on C++.
- CppCast is a frequently updated podcast discussing C++ standards and uses.
- Include C++ is the most popular C++ discord server out there, but plenty of domain specific servers like the Vulkan, DirectX, ShaderToy discord have C++ discussions.
And for resources specific to CMake:
- Matt Keeter (@impraxical) wrote a post on compile time strings in CMake, useful for build related messages.
- Pablo Arias (@pabloariasal) wrote It’s Time To Do CMake Right, which offers more opinonated CMake design.
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!