Hello C++ my old friend.
This article is part of a series.
It’s been a minute since I spent real quality time with C++, so it’s time to do a refresher. Even then I was writing C++ for embedded systems, an entirely different beast than working with it for something like a desktop or iOS app. I plan to follow the same pattern I did with my recent C brush up.
For that I wrote:
- A small pure C project to get familiar with the modern tools on a Mac
- A Swift project that has C files mixed in
- A library where Swift Target Wraps C Target & a test project
- A Swift library that wraps a system installed C library & test project
My original intention, to use OpenUSD as the libpng analog, has been blown out of the water by how crazy extensive and way more than a file format OpenUSD is. I will likely pick something simpler for my Swift/C++ Interop Hello World, especially since that’s so new too. TBD what it will be.
In the mean time let’s get started with a little C++ scratch-pad project.
REPO: https://github.com/carlynorama/Hello_Cpp_Using_VSCode
C++ Project Start Up
Check the Environment
C++ needs a compiler. Confirm what tools are available. My result on checking:
clang --version # Apple clang version 14.0.3 (clang-1403.0.22.14.1)
gcc --version # aliases clang
g++ --version # also aliases clang
cmake --version # cmake version 3.27.1
make --version # GNU Make 3.81
Set Up Project
mkdir $PROJECT_NAME
cd $PROJECT_NAME
git init
touch .gitignore
touch HelloWorld.cpp
gitignore
GitHub provides more, but bare minium:
.DS_Store
HelloWorld # Leave out build of default script
# Will eventually put all binaries in a bin folder
bin/
*.out #in case forget to add a -o flag to compiler call
## Apple for iOS
## can turn them off by not using -g in compiler call
## or using -g0 compiler flag
*.dSYM
HelloWorld.cpp
#include <iostream>
int main()
{
std::cout << "Hello World" << std::endl;
}
# -o for output file name,
# if none provided will be a.out https://en.wikipedia.org/wiki/A.out
g++ HelloWorld.cpp -o HelloWorld
./HelloWorld
Set Up VSCode
Compile Options
With a .cpp file open, hit the run button on the top right to link clang up to VSCode. A tasks.json
file will be generated. The below is an example of tha file after choosing clang++ build active file
, with some noted changes.
For more on available arguments:
- https://clang.llvm.org/docs/ClangCommandLineReference.html
- https://clang.llvm.org/get_started.html
- https://gcc.gnu.org/onlinedocs/gcc/
- C++ status: https://clang.llvm.org/cxx_status.html
- on dSYM files: https://developer.apple.com/videos/play/wwdc2021/10211/
tasks.json
{
"tasks": [
{
"type": "cppbuild",
"label": "C/C++: clang++ build active file",
"command": "/usr/bin/clang++",
"args": [
"-fcolor-diagnostics",
"-fansi-escape-codes",
//ADDED!
"-std=c++17",
//-g is default, -g0 turns off the creation of .dSYM files
"-g0",
"${file}",
"-o",
//default: ${fileDirname}/${fileBasenameNoExtension}
"${workspaceFolder}/bin/${fileBasenameNoExtension}"
],
"options": {
"cwd": "${fileDirname}"
},
"problemMatcher": [
"$gcc"
],
"group": {
"kind": "build",
"isDefault": true
},
"detail": "Task generated by Debugger."
}
],
"version": "2.0.0"
}
It’s possible to use a later library via a command line compile, too:
g++ HelloWorld.cpp -std=c++17 -o HelloWorld
./HelloWorld
Formatting Options
VSCode’s default style is to put the opening curly brace below the starting line, but this is easily changed by adding the following line to the workspace’s .vscode/settings.json
{
//other settings...,
"C_Cpp.clang_format_fallbackStyle": "LLVM"
}
To be fancier provide a .clang-format
file in the project’s root directory. An added benefit, a .clang-format
file will be usable by others not using VSCode.
---
Language: Cpp
# BasedOnStyle: LLVM
AccessModifierOffset: -4
IndentWidth: 4
TabWidth: 4
AlignAfterOpenBracket: Align
AlignConsecutiveMacros: false
AlignConsecutiveAssignments: false
- https://code.visualstudio.com/docs/cpp/cpp-ide#_code-formatting
- https://clang.llvm.org/docs/ClangFormat.html
- https://www.clangpowertools.com/blog/getting-started-with-clang-format-style-options.html
C++ Review
Resources
- Standard Library headers: https://en.cppreference.com/w/cpp/header
- keywords: https://en.cppreference.com/w/cpp/keyword
- Precedence rules: https://en.cppreference.com/w/cpp/language/operator_precedence
- https://cplusplus.com/reference/
- Easy clear course to blow through, down side, requires (free) account: https://www.codecademy.com/courses/c-plus-plus-for-programmers/
Also when back and looked at a 2021 project to remind myself about Templates. Originally cribbed from FastLED a really well written hardware library.
Practice Code
Each file can be compiled and run independently.
HelloWorld.cpp
Miscellaneous things to remember about C++
// Preprocessor Directive
// Standard library names in < >,
// User-defined library names in " ".
#include <iostream>
using namespace std; //To drop the std:: everywhere.
// No header, no hoisting. Functions/vars used in main() need to be declared
// above main in a single-file program.
// declare a constant
const int number = 3;
// function with no return
void whisperToProgrammer() { cout << "Shhh, I'm telling you secretly" << endl; }
// function with string return and int parameter with a default value
string shareTheMessageWithTheClass(int count = 10) {
string message = "LA";
for (int i = 1; i < count; i++) {
message.append(" LA");
}
return message;
}
// pass in a reference
void setMeTo5(int &numToChange) { numToChange = 5; }
// Note on function declarations:
// Return types not used to tell overloads apart.
//--------------------------------------------------------------------- main()
// friendly neighborhood main function
int main() {
int changeable = 3;
int &alias = changeable;
changeable += 3;
// if had not included namespace line.
// std::cout << "Hello, World!" << std::endl;
cout << "Hello, World! "; //<-- no end of line, it's not automatic.
// has returns. alias should == 6
cout << "I'm so happy to see you.\nHere are " << alias << " fish." << endl;
// should be the memory pointers in hex, and they should be the same.
cout << &alias << ' ' << &changeable << endl;
// Don't forget in C++ case statements fall through by default.
switch (changeable) {
case 6:
cout << "Everything as expected.\n";
break;
// And switches do NOT have to be exhaustive.
// default:
// cout << "Something feels hinky.\n";
// break;
}
// Home sweet for loop home.
for (int i = 1; i <= 5; i++) {
cout << i;
}
cout << endl;
// foreach
// warning: range-based for loop is a C++11 extension
// warning: 'auto' type specifier is a C++11 extension
// to compile: g++ HelloWorld.cpp -std=c++17 -o HelloWorld
int pi_times_100k[6] = {3, 1, 4, 1, 5, 9};
// for (int number : pi_times_100k) {
for (auto number : pi_times_100k) {
cout << number;
}
cout << endl;
whisperToProgrammer();
setMeTo5(changeable);
// Should print out 5 LA's
cout << shareTheMessageWithTheClass(alias) << endl;
// sometimes left out, but is the everything is okay line
return 0;
}
Classes.cpp
Example classes, with inheritance
#include <iostream>
class Banner {
// variables are by default private.
// Can set them pubic or make public mutator functions.
public:
std::string message;
void wave() {
std::cout
<< "********************************************************\n";
std::cout << message << "\n";
std::cout
<< "********************************************************\n";
}
// example of declaring method without implementing it, i.e. for header
// files.
void outside();
};
void Banner::outside() {
std::cout << "++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n";
};
// https://en.cppreference.com/w/cpp/language/derived_class
// https://learn.microsoft.com/en-us/cpp/cpp/member-access-control-cpp
class HasDefaults : protected Banner {
public:
HasDefaults(std::string say_this = "This is my standard message!") {
message = say_this;
}
// base class is protected, but can be accessed via mutator functions.
void update(std::string new_message) { message = new_message; }
// Allows access to protected base class function, but is not an override
void passThrough() { wave(); }
// Destructor
~HasDefaults() { std::cout << "Nothing more to do\n"; }
};
// Third time a charm
class WrapAgain : public HasDefaults {
public:
// Treats parent initializer as member initializer list.
WrapAgain(std::string real_message) : HasDefaults(real_message) {}
// no override keyword (keyword: polymorphism)
void passThrough() { std::cout << "+++++" << message << "+++++\n"; }
};
// A class with constants needs to have "member initializer list"
// https://en.cppreference.com/w/cpp/language/constructor
class HousePlant {
private:
const std::string genus;
const int year_acquired;
public:
HousePlant() : genus("Crassula"), year_acquired(2004) {}
};
//----------------------------------------------------------------------- main()
int main() {
Banner end_of_message;
end_of_message.message = "Thats all they wrote!";
end_of_message.wave();
end_of_message.outside();
HasDefaults example;
example.passThrough();
example.update("But I can say something new!");
example.passThrough();
WrapAgain slimmessage("keep it clean");
slimmessage.passThrough();
return 0;
}
Containers.cpp
Various data types like, arrays, vectors, stacks, queues, sets and maps.
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <unordered_map>
#include <unordered_set>
#include <vector>
// https://cplusplus.com/reference/stl/
// -------------------------------------------------------------- Arrays
// https://cplusplus.com/reference/array/array/
// fixed sized
// int count_down[] = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };
const int row_count = 2;
const int col_count = 3;
int grid[row_count][col_count] = {
{0, 1, 2},
{3, 4, 5},
};
const int depth_count = 4;
int cube[row_count][col_count][depth_count] = {
{{0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11}},
{{12, 13, 14, 15}, {16, 17, 18, 19}, {20, 21, 22, 23}},
};
// -------------------------------------------------------------- Vectors
// https://cplusplus.com/reference/vector/vector/
// Variable sized array
std::vector<char> alphabet = {'a', 'b', 'c'};
// -------------------------------------------------------------- Stacks
// https://cplusplus.com/reference/stack/stack/
// LIFO
std::stack<int> plates;
// -------------------------------------------------------------- Queue
// https://cplusplus.com/reference/queue/queue/
// FIFO
std::queue<std::string> line;
// -------------------------------------------------------------- Set
// https://cplusplus.com/reference/set/set/
// https://cplusplus.com/reference/unordered_set/unordered_set/
std::unordered_set<int> primes({2, 3, 5, 7});
void removeAndPrint(int to_remove, std::unordered_set<int> &from_this) {
int result = from_this.erase(to_remove);
if (result == 1) {
std::cout << "removed a " << to_remove << "\n";
} else {
std::cout << "there wasn't a " << to_remove << "\n";
}
}
bool contains(int to_find, const std::unordered_set<int> &in_this) {
int result = in_this.count(to_find);
if (result != 0) {
std::cout << "found a " << to_find << "\n";
return true;
} else {
std::cout << "there wasn't a " << to_find << "\n";
return false;
}
}
// -------------------------------------------------------------- Map
// https://cplusplus.com/reference/unordered_map/
// https://cplusplus.com/reference/map/
// keys must be unique, but is hashed on BOTH key and value.
std::unordered_map<std::string, int> shopping_list({{"apples", 5},
{"eggs", 12}});
//---------------------------------------------------------------------- main()
int main(int argc, char *argv[]) {
// Danger, this does not error out if put in too many args
// -------------------------------------------------------------- Arrays
int test_count[5];
for (int i = 0; i < argc; i++) {
std::cout << "arg " << i << ": " << argv[i] << "\n";
test_count[i] = i; // should have a max of 5
std::cout << "overflow test " << i << ": " << test_count[i] << "\n";
}
std::cout << grid[1][2] << "\n";
for (int i = 0; i < row_count; i++) {
for (int j = 0; j < col_count; j++) {
for (int k = 0; k < depth_count; k++) {
std::cout << cube[i][j][k] << ", ";
}
std::cout << "\n";
}
std::cout << "\n";
}
// -------------------------------------------------------------- Vector
alphabet.push_back('d');
alphabet.push_back('e');
for (auto letter : alphabet) {
std::cout << letter;
}
std::cout << "\n";
char first = alphabet.front();
std::cout << first << "\n";
//actually has vs .capacity() which is allocated
int size = alphabet.size();
std::cout << size << "\n";
for (auto letter : alphabet) {
std::cout << letter;
}
std::cout << "\n";
// -------------------------------------------------------------- Stack
plates.push(3);
plates.push(8);
plates.push(5);
const int current_size = plates.size();
for (int i = 0; i < current_size; i++) {
int next = plates.top();
plates.pop();
std::cout << next << ' ';
}
std::cout << "\n";
// -------------------------------------------------------------- Queue
line.push("George");
line.push("Bonny");
line.push("Linda");
line.push("Dianne");
const int line_size = line.size();
for (int i = 0; i < line_size; i++) {
std::string next = line.front();
line.pop();
std::cout << next << ' ';
}
std::cout << "\n";
// -------------------------------------------------------------- Set
primes.insert(2);
primes.insert(7);
primes.insert(43);
for (auto num : primes) {
std::cout << num << " ";
}
std::cout << "\n";
removeAndPrint(2, primes);
for (auto num : primes) {
std::cout << num << " ";
}
removeAndPrint(2, primes);
bool result = contains(5, primes);
std::cout << result << "\n";
std::cout << "\n";
std::set<int> ordered({4, 2, 7, 1, 3});
std::cout << "set: ";
for (int n : ordered) {
std::cout << n << " ";
}
// consider also my_set.rbegin() and my_set.rend()
std::set<int, std::greater<int>> descending{4, 2, 7, 1, 3};
std::cout << "descending set: ";
for (int n : descending) {
std::cout << n << " ";
}
// -------------------------------------------------------------- Map
std::cout << "\n";
shopping_list.insert({"acorn squash", 1}); // does insert
shopping_list.insert({"cashews, bag of", 1}); // does insert
shopping_list.insert({"eggs", 24}); // does not update.
shopping_list["eggs"] =
2; // does update, but will also create if does not exits.
// shopping_list.at("frozen peas"); //will be error, out of range.
for (auto item : shopping_list) {
std::cout << item.first << " " << item.second << "\n";
}
shopping_list.insert_or_assign("apples", 3);
// shopping_list.
for (auto item : shopping_list) {
std::cout << item.first << " " << item.second << "\n";
}
// erase and count work similarly to set, i.e. they return 1 if successful.
}