changed a year ago
Published Linked with GitHub

Modern C++?

by Mes

Why you write program?

"Just for fun"


「我不是有願景的人。 我沒有五年計畫。 我是工程師。
而且我覺得這真的──我是說,我對這些人一點意見也沒有,他們可以四海飄遊,看看雲,看看星星,然後說:『我想去那裡。』
但是我是看著地面的人,而且我想把那個就在我正前方的坑洞補好,免得我跌倒。我就是這種人。」── Linus Torvalds

很明顯我不是這種人

我開了很多坑,但常常不補坑


「如果你有『我要快點進步』、『我要快點找回畫畫的感覺』這樣的心態,
不用啦,只要學著怎麼再次享受就好,
因為我想,大家剛開始畫畫時都不是想說『我要成為專業畫家』,
而是像『我想畫這個』、『我很享受畫畫』、『我畫畫時得到很多樂趣』,
像是這些,我也覺得有這些心態去繼續你的畫畫歷程是很重要的。
你會開始畫畫是因為你喜歡,你也很享受,我也知道這趟旅程很長,
你不知到何處是終點,如果會結束啦,途中有很多技術是你要學會的,
不過你要記住最重要的是,要去享受他」── Ina

Why I love programming?

I love Magic


工程師或許就是魔法師

我們甚至有本課本叫魔法書


還有黑魂

哪天多一本 Elden ring 都不奇怪


魔法是什麼?

魔法是種能讓人實現願望的「語言」,會有其特殊的咒文、圖騰與規則等

加 速 符 文

ios_base::sync_with_stdio(0), cin.tie(0);


有人說 CS、CE 是種工程,也有人說是藝術

但我認為它就是地球上的魔法

能改變世界,同時具有美感


Learn Modern Cpp?


Spec?

The standard of C++


Java:SE Specifiactions (852 pages)
Python:The Python Language Reference (186 pages)
C#:C# Language Specification (521 pages)
C++:Working Draft, Standard for Programming Language C++ (1834 pages)

Why spec?

(不正經的魔術講師與禁忌教典第二集)

How many S instance in each part will be constructed?
S fn() { return S(); }

int main() {
  // S is a struct; Cpp version: C++17
  S s1 = fn();
  S s2 = S( S( fn() ) );
  S s3{ S( fn() ) };
    
  S s4;
  s4 = S( fn() );
}

Compiler Explorer


How many S instance in each part will be constructed?
S fn1() {
  S obj;
  return obj;
}
S fn2(bool flag) {
  if (flag) {
    S obj1;
    return obj1;
  }
  else {
    S obj2;
    return obj2;
  }
}
int main(int argc, char *argv[]) {
  S s1 = fn1();
  S s2 = fn2(argc == 1);
}

Compiler Explorer


Black Magic

SSO / SOO

(not SAO)


2 times Heap allocations?
int main()
{
  std::string str = "Mes";
  std::string str2 = "A very very long string";
}

Compiler Explorer


Why?
_CONSTEXPR20 basic_string& assign(
    _In_reads_(_Count) const _Elem* const _Ptr, _CRT_GUARDOVERFLOW const size_type _Count) {
        // assign [_Ptr, _Ptr + _Count)
    if (_Count <= _Mypair._Myval2._Myres) {
        _Elem* const _Old_ptr   = _Mypair._Myval2._Myptr();
        _Mypair._Myval2._Mysize = _Count;
        _Traits::move(_Old_ptr, _Ptr, _Count);
        _Traits::assign(_Old_ptr[_Count], _Elem());
        return *this;
    }
    return _Reallocate_for(
        _Count,
        [](_Elem* const _New_ptr, const size_type _Count, const _Elem* const _Ptr) {
            _Traits::copy(_New_ptr, _Ptr, _Count);
            _Traits::assign(_New_ptr[_Count], _Elem());
        },
        _Ptr);
}
(mingw-gcc 11.2.0)

How I learn Modern C++?

  1. Watched talk & Articles
  2. Asked & Be asked
  3. spend time to read cppreference and spec
  4. Do Project

Talk & Articles

  1. Cppcon (link)
  2. C++ Weekly With Jason Turner (link)
  3. TJSW (link)
  4. 重新理解C++ (link)

Asked & Be asked

  1. 好的老師能幫你省去大量時間,文章也是
  2. 別人的問題能幫你檢查你是否真的懂了
  3. 有時你根本沒想過會有這問題
  4. 有時可以看到真的魔法

怕英文不好?

真的不用怕,去問就對了


(但即使被改過這麼多次我的英文仍然沒有變好)

Cppreference & Spec

  1. cppreference (link)
  2. Spec (link)

spec 讀起來會像在讀法律一樣
考不上法律系可以讀一下 spec 假裝自己是律師


Project

「如果你把游泳池當作浴缸泡著,再泡幾年還是不會游泳」── Jserv
搞不好還感冒

Modern C++ 在追求什麼?

  1. Safer code
  2. compile time error detect
  3. metaprogramming

C++ 重點介紹


C++98 & C++03


建構子與解構子
class T {
public:
  T() { puts("T()"); }
  ~T() { puts("~T()"); }
};
這裡 T() 就是一種建構子,而 ~T() 就是一種解構子。
這個型態的物件被建構出來時會去呼叫建構子,被解構時會去呼叫解構子

RAII
#include <iostream>
struct T {
  T() { puts("T()"); }
  ~T() { puts("~T()"); }
};

int main()
{
  {
    T t;
  }    // use RAII to destruct t
}
全名為 Resource Acquisition Is Initialization,意思為資源取得即初始化
物件離開對應的 scope 時會自動解構

模板 (template)
template<typename First, typename Second>
struct T{
  First i;
  Second j;
};
當我們像這樣 T<int, bool> t; 建構物件時,
第一個型態 First 就會被替換為 int,第二個型態就會變成 bool
metaprogramming 的基礎

algorithm
#include <algorithm>
#include <vector>

int main()
{
  std::vector<int> vec = { 3, 1, 2 };
  std::sort(vec.begin(), vec.end());    // The elements of vec will be 1 2 3.
}
相較於一堆 for loop 能夠大幅增加可讀性
大部分情況比自己實作更為安全 (但可能會比較慢, ex. std::swap)

C++11 & C++14

C++14 大多為 C++11 的延伸,這邊視情況介紹,有興趣的可以看礦坑本文

auto

#include <algorithm>
#include <vector>

void count_things(const std::vector<int> &vec, int value)
{
  const auto count = 
      std::count(std::begin(vec), std::end(vec), value);
}
型態不重要時使用 auto 可以增加可讀性
可以避免意外的物件切片(slicing)

ranged-for loop

#include <vector>

void travel_thing(const std::vector<int> &vec)
{
  for (const auto &elem : vec) {
    // do thing with elem
  }
}
增加可讀性與便利性
常搭配 auto 與 structure binding 使用

lambda

#include <algorithm>
#include <vector>

template <typename T>
void count_things_less_than_3(const T &vec)
{
  const auto count = std::count_if(std::begin(vec), std::end(vec),
    [](int i) { return i < 3; }
  );
}
增加可讀性與便利性
避免名稱汙染

variadic templates

template <typename Func, typename... T>
void caller(const Func &func, const T &...param)
{
  func(param...);
}
更方便的 template
使 metaprogramming 好用的大功臣之一

智慧指標 (smart pointer)

#include <memory>

void allocate_memory()
{
  std::unique_ptr<int> ptr(new int(5));
}    // ptr destory, memory free
Safer code
增加可讀性

C++14 後可利用 make_unique 等來分配智慧指標的空間
#include <memory>
void allocate_memory()
{
  auto ptr{std::make_unique<int>(5)};  
}
Safer code
增加可讀性

constexpr

constexpr int get_value()
{
  return 5 * 3;
}

constexpr auto value = get_value();
runtime -> compile time
C++11 時有許多限制,C++14 時放寬

C++17

語法開始變神奇了

Copy / Move Elision 的保證

#include <iostream>
class T {
public:
  T() { puts("T()"); }
  ~T() { puts("~T()"); }
};

T fn()
{
  return T();
}

int main()
{
  T t = fn();
}
上面這個例子中只會有一個物件被建構

constexpr 於 Stdlib 中開始被普遍支援

增加方便性與速度

string_view

#include <string_view>
constexpr std::string_view name = "Hello";
string_view 是 string 的一個 「view」,也就是說唯讀,不可修改
取代部分 const std::string& 的使用時機,增加可讀性

Class Template Argument Deduction

#include <array>
std::array data{1, 2, 3, 4, 5};
C++17 後可以自動推斷 template argument
這裡 Compiler 會自動幫你推斷出 data 的 template argument 為 <int, 5>

fold expression

template<typename ...T>
auto add(const T & param...)
{
  return (param + ...);
}
幫助我們更有彈性的處理多個參數的傳遞

Structured Bindings

std::pair<int, int> values{1, 2};
auto [first, second] = values;
幫助我們使用 tuple-like 的容器,如 std::pair、std::tuple 等

if-init expressions

void fn(int i, int t){
  if(int j = i * t;
     j < 5)
  {
    // do something
  }
}
增加可讀性、便利性

「若說 C 語言給了你足夠的繩子吊死自己,那麼 C++ 給的繩子除了夠你上吊之外,還夠綁住你身邊的朋友
相較之下,Java 讓你在吊死自己之際仍有親友監視著,雖然死不了,但事後會更想死」── Jserv

走入 Modern Cpp


試著像牛仔一樣用那條繩子,畢竟那條繩子比較長

打到朋友就算了,至少沒綁住


值類別

(這裡以 C++17 後的定義為主,它改過很多次,很亂)


Value Categories is a Property of

Expression

很重要所以講三次,
Expression、Expression、Expression


Example of Expression (by kris)

42  // Expression evaluating to value 42
    
17 + 42 // Expression evaluating to value 59
int a;

a = 23  // Expression evaluating to value 23
a + 17 // Expression evaluation to value 40
static_cast<float>(a) // Expression evaluating to 
                      // floating-point value 23.0f

Primary value categories

  • prvalue ── Pure rvalue
  • lvalue ── Locator value
  • xvalue ── eXpiring value

根據 Expression 的回傳來分類

  • prvalue ── no id, non movable
  • lvalue ── have id, non movable
  • xvalue ── have id, movable

id?

全名為 identity,你可以簡單理解為有記憶體位址


Does it evaluate to an identity?

(by Kris)
int a;

a    // 擁有 identity
a + 2    // 沒有identity
a || true    // 沒有identity
a++    // 沒有identity
++a    // 擁有identity
42    // 沒有 identity
nullptr    // 沒有 identity
false    // 沒有 identity
[]{return 42;}    // 沒有 identity
"Hello world"    // 擁有 identity!!
std::cout    // 擁有 identity,std::cout 是 std::ostream 的 instance
static_cast<int>(a)    // 沒有 identity
std::move(a)    // 擁有 identity

movable?

代表其資源可以被安全的轉移給其他人

資源通常指的是記憶體、socket 等等


move?

原先 obj 由 ptrA 控管,經移動後由 ptrB 控管


Can its resources be safely transformed?

(by Kris)
#include <iostream>
#include <vector>

std::string func() {
    return "Steal me!";
}
int main() {
    std::vector<std::string> vec;
    vec.push_back( func() );

    std::string x{ "Steal me!" };
    vec.push_back( std::move( x ) );
    return 0;
}

Example of Prvalue

(no id, non movable)
42 // prvalue
nullptr // prvalue
int foo() {
    return 0;
}

foo();    // foo() is prvalue

int x = 42;
x++ // prvalue

Example of Lvalue

(have id, non movable)
"Hello world" // lvalue
    
int x = 42;
++x // lvalue
x // lvalue

Example of Xvalue

(have id, movable)
struct S {
  int i;
};

S().i // xvalue

int i;
std::move(i);  // xvalue

How to confirm quickly?

  1. 查表 (link)
  2. By Overload Resolution
    (2022 年口誤講成 ADL 了)
  3. address of operator 「&」


Check by Overload Resolution Example

namespace detail {
  template <class T> struct value_category      {
    static constexpr char const * value = "prvalue"; };

  template <class T> struct value_category<T&>  {
    static constexpr char const * value = "lvalue";  };

  template <class T> struct value_category<T&&> {
    static constexpr char const * value = "xvalue";  };
}
Compiler Explorer

address-of operator &

(7.6.2):The operand of the unary & operator shall be an lvalue of some type T. The result is a prvalue.

能用 & 的 Expression 為 lvalue Expression


Black Magic?

Copy Elision


不,這不是黑魔法,它一直存在於你的 code 裡面

它很重要

因為它會影響程式行為


What is Copy Elision?

  • 省略複製或移動
  • 直到物件必須建構前,不要建構物件

回到一開始的例子
S fn() { return S(); }

int main() {
  // S is a struct; Cpp version: C++17
  S s1 = fn();
  S s2 = S( S( fn() ) );
  S s3{ S( fn() ) };
    
  S s4;
  s4 = S( fn() );
}

Compiler Explorer


何時必須建構?

查表吧🈹(link)


一開始的第二個例子呢?

那叫 NRVO,有興趣的可以去看看


都看不懂?


那也沒差,用到再說

「你不知到何處是終點,如果會結束啦,途中有很多技術是你要學會的,
不過你要記住最重要的是,要去享受他」── Ina

踩到坑不會解?

問人、翻 spec、google,或找 workaround,逃避可恥但有用


Thanks


離開小心不要滑倒

Select a repo