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:

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:

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

C++ Review

Resources

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.
}

This article is part of a series.