Skip to content

Instantly share code, notes, and snippets.

@rtimmons
Created June 21, 2018 18:11
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save rtimmons/784b45d7bdcfce7d8e975c7417c66182 to your computer and use it in GitHub Desktop.
Save rtimmons/784b45d7bdcfce7d8e975c7417c66182 to your computer and use it in GitHub Desktop.
Learning C++ The Hard Way

Welcome

Who is this guy?

Goal(s)

  1. Don't WTF quite so much when looking at C++ PRs
  2. Know what you don't know (so you know what to look up to learn more)

Agenda

  1. Some new stuff
  2. Some language oddities
  3. Some OO specifics
  4. Templating
  5. Compilers, CMake, and you
  6. Where to learn more?
  7. Random topics as time allows

Hello, World!

#include <iostream>
int main() {
  std::cout << "Hello, World!" << std::endl;
  return 0;
}

There can only be one main.

Hello, World!

$ ls
main.cc

$ cat main.cc 
#include <iostream>
int main() {
  std::cout << "Hello, World!" << std::endl;
  return 0;
}

$ clang++ ./main.cc

$ ./a.out 
Hello, World!

Agenda

  1. Some new stuff
  2. Some language oddities
  3. Some OO specifics
  4. Templating
  5. Compilers, CMake, and you
  6. Where to learn more?
  7. Random topics as time allows

What's New

C++14 and C++17: Not your father's C++

  • More auto
  • using (templates for typedefs)
  • Lambdas
  • Range-based for
  • Smart(er) pointers
  • std::move and rvalue-refs
  • threading API

What's New: More auto

For types:

auto numbers = std::vector<int> {1,2,3};

For return values:

auto next(int i) {
    return i + 1;
}

Later: Force the compiler to tell us what the auto is deduced to.

What's New: Lambdas

auto printer = [](auto c) {
    std::cout << c << std::endl;
};
printer("Foo");

The [] lets you capture outside variables.

What's New: Range-based for

auto numbers = std::vector<int> {1,2,3};

for(auto number : numbers) {
    std::cout << number << std::endl;
}

What's New: Smart(er) Pointers

No more new:

auto client =
    std::make_shared<Client>("mongo://...");

What's New: rvals (&&) and std::thread

  • Too complex to talk about here.
  • Read more if you're going to work on framework or library code. (Meyers book is good for this.)

Agenda

  1. Some new stuff
  2. Some language oddities
  3. Some OO specifics
  4. Templating
  5. Compilers, CMake, and you
  6. Where to learn more?
  7. Random topics as time allows

Language: Primitive Types

#include <iostream>
int main() {
  std::cout << "Hello, World!" << std::endl;
  return 0;
}

long, short, char, bool, int, double, float, long long, unsigned ..., a few more but not that many.

Language: Assignment Syntax Sucks ๐ŸŒŸ

Constructors:

struct Point {int x; int y;};

Point p = Point {1, 2}; // if ctor is `explicit`
Point p = {1, 2};
Point p   {1, 2};
Point p   (1, 2); // needs ctor!

Primitives:

int x = {7};
int x   {7};    
int x   (7);
int x =  7;

Preferred:

auto p = Point {1,2};
auto x = 7;

Language: Stack All The Things ๐ŸŒŸ

int main() {
    Person x = Person("Ryan", "T");
    Person y = x;
    print(x);
    print(y);
    x = y;
}
void print(Person x) { ... }

Language: Assignment isn't Real

int main() {
    Person x = Person("Ryan", "T");
    Person y = Person("Ryan", "S");
    x = y;  // ????
}

Language: Assignment isn't Real ๐ŸŒŸ

Person x = Person("Ryan", "T");
Person y = Person("Ryan", "S");
// x = y is "really":
x.operator=(y);

Language: Assignment isn't Real

Foo x = ...;
x = 6;          // x.operator=(int 6)
x = false;      // x.operator=(bool false)

Language: Assignment isn't Real

++x;            // x.operator++()
x++;            // x.operator++(int)
x << "Hello";   // x.operator<<(string "Hello")
x[7] = 8;       // x.operator[](int 7)

Language: Assignment isn't Real

x %= 7;         // x.operator%=(int 7)
x == 7;         // x.operator==(int 7)
x == x;         // x.operator==(Foo x)
*x;             // x.operator*()
x->foo();       // x.operator->().foo()
if(x)           // x.operator bool()
x,6;            // ๐Ÿ˜”

Language: Assignment isn't Real ๐ŸŒŸ

Too much syntax sugar causes cancer of the semicolon

struct Foo {
    int i;
    int operator ,(int other) {
        return i + 10;
    }
};
auto y = Foo{6};
std::cout << (y,7) << std::endl; // ๐Ÿ˜”

'nuff said?

Language: Pass By Value

int main() {
    auto d = Database();
}
void print(Database d) { ... }

๐Ÿ˜ฑ Deep Copy! ๐Ÿ˜ฑ

Language: Pointers and Refs

  • C only has pointers (but is always pass-by-value)
  • Java and Python (implicitly) also only have pointers. (Except primitives which are passed by value. Usually.)

Language: Pointers and Refs ๐ŸŒŸ

C++ plays by its own rules ๐Ÿค 

  • Pass by value (like C; pointers are values)

  • Unless signature takes a ref (then use that ref)

      void process(Database d);
      void process(Database& d);
      void process(const Database& d);
    

Language: Pointers and Refs

int x = 7;

Pointers:

int* pointerToX = &x; // std::addressof(x);
*pointerToX = 10;

Refs:

int& refToX = x;
refToX = 10;

Results: x == 10.

Language: Pointers and Refs

How did this work??

x[7] = 8;

It's just syntax sugar!

struct Foo {
    int i;
    int& operator[](int) {
        return i;
    }
}

Cool tidbit: Refs can never be nullptr. Enforced at the language-level.

Language: Pointers and Refs

Refs can be evil ๐Ÿ˜ˆ

void evil(int& x) { ++x; }
void doSomething() {
    int x = 7;
    evil(x);
}

Language: Pointers and Refs ๐ŸŒŸ

Usually use const X& to prevent calling non-consty things.

int main() {
    auto d = Database();
}
void print(const Database& d) { ... }

๐Ÿ‘ฏ No Deep-Copy ๐Ÿ‘ฏ

Language: Pointers and Refs

void foo(Database* d) { ... }
void foo(Database  d) { ... }
void foo(Database& d) { ... }

๐Ÿ’…๐Ÿฝ Can't have both ref and value overloads ๐Ÿ’…๐Ÿฝ

Language: Pointers and Refs

Q: When do I use pointers?
A: When you know that you don't own the thing

Q: What do I use instead?
A: Use std::shared_ptr (ref-counted, some overhead)
A: or std::unique_ptr (faster but awkwarder)

Language: Pointers and Refs

Don't use new
(or at least never "naked new" ๐Ÿ™ˆ)

Instead of this:

 Database* d = new Database(1,2,3);
 ... ๐Ÿ’ฅ?
 delete d;

Do this:

auto d = std::make_shared<Database>(1,2,3);

Moving On: Code Organization!

Linkers and Compilers: Scheme together to share object code across multiple executables

Language: Code Organization

No de-facto standards on

  • directory-layout
  • class/method/file naming
  • documentation and where it goes
  • what should be "header-only" and what should be a compiled library

๐Ÿ˜ฉ

Unlike Java. Kinda like Python.

Language: Code Organization ๐ŸŒŸ

include/lib.hpp:

#ifndef _HAVE_LIB_HPP
#define _HAVE_LIB_HPP
void add(int, int);
void sub(int, int);
#endif

src/lib.cpp:

#include <lib.hpp>
void add(int a, int b) { return a + b; }
....

src/main.cpp:

#include <lib.hpp>
int main() { return add(1,2); }

Language: Code Organization

Header files:

  • Class declarations
  • Any templates and their definitions
  • Rarely: inline function definitions

Impl files:

  • Include needed header files
  • Implementations

Language: (Named) Namespaces

std::cout
mongo::client()

Language: (Named) Namespaces

namespace mongo {
    class client;
}
namespace std {
    ostream& cout;
}

Language: (Named) Namespaces ๐ŸŒŸ

  // new in c++17
namespace foo::bar {
  ...
}

// instead of:
namespace foo {
namespace bar {
  ...
}
}

Language: Anonymous Namespaces ๐ŸŒŸ

#include <lib/Person.hpp>

// best-practice!
namespace {
  void printPerson(const Person& p) { ... }
}

void Person::inspect() {
  something();
}

(Don't pollute the global namespace.)

Language: Argument-Dependent Lookup (ADL) ๐Ÿ‘น

std::cout << 7;

Note that operator<<(std::ostream&,int) isn't in the global namespace...

Agenda

  1. Some new stuff
  2. Some language oddities
  3. Some OO specifics
  4. Templating
  5. Compilers, CMake, and you
  6. Where to learn more?
  7. Random topics as time allows

OO: ๐Ÿ‘ธ๐Ÿฝ Types ๐Ÿคด๐Ÿฟ

  • Your types == built-in types
  • Do as the ints do

OO: Initializer Syntax

struct Point {
    int x;
    int y;
}
auto origin = Point {0,0};

OO: Intializer Syntax (Reminder From Earlier) ๐ŸŒŸ

struct Point {int x; int y;};

Point p = Point {1, 2}; // if ctor is `explicit`
Point p = {1, 2};
Point p   {1, 2};
Point p   (1, 2); // needs ctor!

OO: Initializer Syntax

What you "want" to do from Java/Python:

struct Point {
    int x;
    int y;
    
    Point(int x, int y) {
        this->x = x;
        this->y = y;
    }
};

But: Then can't make x and y const!

OO: Initializer Syntax

Solution:

struct Point {
    const int x; 
    const int y;
    
    Point(int x, int y)
    : x{x}, y{y} {}
};

NB: Conventions usually have class members with leading underscore:

struct Point {
    const int _x; 
    const int _y;
}

OO: Default Constructors

class Banner {
public:
    Banner() = default;
    // equiv:
    //   Banner() {}
private:
    std::string msg = "Foo";
};

OO: Constness (1/2)

struct Point {
    const int x;
    const int y;
    
    Point(int x, int y)
    : x{x}, y{y} {}
    
    const Point up() const {
        return Point {this->x, this->y + 1}
    }
}

OO: Constness (2/2) ๐ŸŒŸ

const is like a separate interface for your class.

void draw         (      Point  p) { }
void drawRef      (      Point &p) { }
void drawConstRef (const Point &p) { }

const Point origin = {0,0};
      Point up     = origin.up();
const Point cup    = origin.up();

// OK(ish)
draw(origin);    draw(up);    draw(cup);

drawRef(origin); // Error!
drawRef(up);
drawRef(cup);    // Error!

// OK
drawConstRef(origin);    drawConstRef(up);    drawConstRef(cup);

OO: Lvalue-ness & RValue-ness

Ugh:

struct Foo {
    void enact() &;  // 1
    void enact() &&; // 2
}

void makeFoo() { auto out = Foo{}; return out; }

Foo f;
f.enact(); // 1
makeFoo().enact(); // 2

OO: Assignment and Copy (1/2)

struct Point {
    int x;
    int y

    Point(int x, int y, int z)
    : x{x}, y{y}, z{z} {}
};

Point a = {0,0};
Point b = {1,1};

Point c = b; // copy
a = c;       // assign

OO: Assignment and Copy (2/2) ๐ŸŒŸ

(Only occasionally necessary)

struct Point {
    ...
    // Copy constructor
    // Point a = b
    Point(const Point& other)
    : x{other.x}, y{other.y} {}

    // Copy assignment
    // c = b
    Point& operator=(const Point& other) {
        this->x = other.x; 
        this->y = other.y;
        return *this;
    }
};

Point a = {0,0};
Point b = {1,1};

Point c = b;
      a = c;

OO: Destructors and RAII ๐ŸŒŸ

struct Point {
    ...
    ~Point() {
        std::cout << "Destructing" << std::endl;
    }
}

This is how smart-pointers work.

OO: Visibility ๐ŸŒŸ

  • Classes: Default private
  • Structs: Default public
  • Create public:, private:, and protected: sections for class-functions and members

OO: Friendship

class Printer { };
class SomeObject {
    friend class Printer;
};

OO Principles:

  • "Long-distance" friendships are dangerous.
  • Consider Passkey idiom instead

OO: Private/Public Inheritance

class Figure { };
class BetterFigure : public Figure { };
class SomethingElse : private Figure { };

OO Principles:

  • Hide unless designing to be visible
  • Composition >> inheritance

OO: Virtual Methods ("Interfaces") ๐ŸŒŸ

struct Session {
    virtual void rollback() { std::cout << "Session"; };
};
struct Causal : public Session {
    void rollback() override { std::cout << "Causal"; }
};
struct Normal : public Session {
    void rollback() override { std::cout << "Normal"; }
};

void enact(bool causal) {
    std::unique_ptr<Session> session;
    if (causal) {
        session = std::make_unique<Causal>();
    } else {
        session = std::make_unique<Normal>();
    }
    session->rollback();
}

OO: Containers of Polymorphic Types

class QueuedJob {
public:
    virtual void run() = 0;
};
class PrinterJob : public QueuedJob {
    void run() override { }
};

void runJobs() {
    std::vector<std::unique_ptr<QueuedJob>> jobs;
    jobs.push_back(std::make_unique<PrinterJob>());
    for(auto& job : jobs) {
        job->run();
    }
}

OO: Exceptions (1/2)

  • They're not evil.
  • Most compilers: no overhead except when thrown
  • Uncaught exceptions cause the program to exit

OO: Exceptions (2/2)

Throw by value and catch by reference

void doSomething() {
    throw std::logic_error("unknown value");
}

try {
    doSomething();
} catch(const std::exception& ex) {
    std::cerr << ex.what() << std::endl;
} catch(...) { // avoid except in framework code ...
}

Agenda

  1. Some new stuff
  2. Some language oddities
  3. Some OO specifics
  4. Templating
  5. Compilers, CMake, and you
  6. Where to learn more?
  7. Random topics as time allows

Templating (1/4) ๐ŸŒŸ

  • A bit fancier than text-substitution (but not by much!)
  • Completely separate types ("duplicated" object-code)
  • Templates need to be defined in headers

No "real" connection between these types/classes:

std::vector<int>
std::vector<std::string>
std::vector<MyClass>

Aside: Templates Versus Java Generics

Java version:

void blatz(List<? extends Foo> foos) {
    for(Foo foo : foos) { explode(foo); }
}

"Direct translation" C++:

template<class T>
void blatz(const std::vector<Foo>& foos) {
    for(auto& foo : foos) { explode(foo); }
}

More idiomatic:

template<class Foos>
void blatz(const Foos& foos) {
    for(auto foo : foos) { explode(foo); }
}

Aside: Templates Versus Java Generics

No real way to give an "interface" to template parameters. Standards committee is working on it. Calling it concepts.

Templating (2/4)

๐Ÿฆ† Hey C++ Does Have Duck-Typing! ๐Ÿฆ†

template<typename T>
struct MyStruct {
    T myvar;
    void incr() { myvar++; }
};

MyStruct<int> m1 {7};
std::cout << m1.myvar << std::endl;
m1.incr();

Templating (2/4)

๐Ÿฆ† Hey C++ Does Have Duck-Typing! ๐Ÿฆ†

template<typename T>
struct MyStruct {
    T myvar;
    void incr() { myvar++; }
};

MyStruct<int> m1 {7};
std::cout << m1.myvar << std::endl;
m1.incr();

MyStruct<std::string> m2 {"Foo"};
std::cout << m1.myvar << std::endl;

Does this work?

Templating (2/4)

๐Ÿฆ† Hey C++ Does Have Duck-Typing! ๐Ÿฆ†

template<typename T>
struct MyStruct {
    T myvar;
    void incr() { myvar++; }
};

MyStruct<int> m1 {7};
std::cout << m1.myvar << std::endl;
m1.incr();

MyStruct<std::string> m2 {"Foo"};
std::cout << m1.myvar << std::endl;
m2.incr();

Does this work?

Templating (3/4)

error: cannot increment value of type 
       'std::__1::basic_string<char>'
            myvar++;
            ~~~~~^

Templating (4/4) SFINAE ๐ŸŒŸ

template<typename T>
struct MyClass {
    T myvar;
    void incr() { myvar++; }
};

// ๐ŸŽ‰
template<>
struct MyClass<std::string> {
    std::string myvar;
    void incr() { }
};

MyClass<int> m1 {7};
std::cout << m1.myvar << std::endl;
m1.incr();

MyClass<std::string> m2 {"Foo"};
std::cout << m1.myvar << std::endl;
m1.incr();

๐Ÿšจ Templating Bonus: Determining Type of auto ๐Ÿšจ

template<class T>
class TD;

void foo() {
    auto x = "123";
    TD<decltype(x)> xType;
}

Fancy:

error: implicit instantiation of 
  undefined template 'TD<const char *>'

Agenda

  1. Some new stuff
  2. Some language oddities
  3. Some OO specifics
  4. Templating
  5. Compilers, CMake, and you
  6. Where to learn more?
  7. Random topics as time allows

Build Tooling (1/5)

๐Ÿ˜ฑ๐Ÿคฎ๐Ÿคฏ๐Ÿคž๐Ÿ”

Build Tooling (2/5)

A Haiku about cmake:

CMake is garbage
CMake is garbage
CMake is garbage

Build Tooling (3/5) ๐ŸŒŸ

  • Use an IDE at first (CLion is nice!)

  • CMake GUI

  • make VERBOSE=1

  • Use latest version of CLang you can (xcode 10 beta)

      sudo xcode-select -s \
          /Applications/Xcode-beta.app/Contents/Developer
    

Build Tooling (4/5)

  • Learn to read the matrix
  • SFINAE
  • Grok how header files, object files, and linkers play together
  • Use a STL Pretty-Printer (one header file gives you << for many built-in types)

Build Tooling (5/5)

CMake is garbage

Agenda

  1. Some new stuff
  2. Some language oddities
  3. Some OO specifics
  4. Templating
  5. Compilers, CMake, and you
  6. Where to learn more?
  7. Random topics as time allows

Learning More ๐ŸŒŸ

Agenda

  1. Some new stuff
  2. Some language oddities
  3. Some OO specifics
  4. Templating
  5. Compilers, CMake, and you
  6. Where to learn more?
  7. Random topics as time allows

Closing Thoughts ๐ŸŒŸ

This is C++. There are no simple questions, and even fewer simple answers.

  • Ask on Slack #cxx-discuss! Everybody is really friendly and helpful. No n00b-shaming. Much shaming of the language (and of CMake)
  • Thanks to Henrik's Open-Source Project for impress.js for powering this presentation!

Random Topics

  • ABIs
  • using X, using Y = Z, don't say using X in headers
  • Implicit conversions
  • explicit constructors
  • constexpr
  • assert
  • passkey idiom
  • pimpl idiom

My Reference: 8745WUT2P8NHYK1JGP5F4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment