LOADING

加载过慢请开启缓存 浏览器默认开启

Okabe's LAB

SFINAE —— Substitution Failure Is Not An Error

2025/3/24

IsConvertible

#include <iostream>
#include <string>

template <typename FROM, typename TO> struct IsConvetible {
  // FROM should be converted to TO implicitly
  static void aux(TO);

  // Should use another typename F, otherwise it will cause an compilation error
  // immediately
  template <typename F, typename = decltype(aux(std::declval<F>()))>
  static std::true_type test(void *);

  // Will failed with: No viable conversion from 'double' to 'std::string'
  //   template <typename = decltype(aux(std::declval<FROM>()))>
  //   static std::true_type test(void *);

  // If the above function failed, it will fallback here
  template <typename> static std::false_type test(...);

  static constexpr bool value = decltype(test<FROM>(nullptr))::value;
};

int main() {
  std::cout << std::boolalpha;
  std::cout << IsConvetible<double, std::string>::value << std::endl;
  std::cout << IsConvetible<double, int>::value << std::endl;
  std::cout << IsConvetible<const char *, std::string>::value << std::endl;
  std::cout << IsConvetible<const char[10], std::string>::value << std::endl;
  std::cout << IsConvetible<char[10], std::string>::value << std::endl;
}

输出:

false
true
true
true
true

IsDefaultConstructible

#include <iostream>
#include <string>

template <typename T> struct IsDefaultConstructibleHelper {

  // Should use another typename U, otherwise it will cause an compilation error
  // immediately
  template <typename U, typename = decltype(U())>
  static std::true_type test(void *);

  // If the above function failed, it will fallback here.
  template <typename> static std::false_type test(...);

  static constexpr bool value = decltype(test<T>(nullptr))::value;
};

template <typename T>
constexpr bool IsDefaultConstructible = IsDefaultConstructibleHelper<T>::value;

class WithDefaultConstructor {};

class WithoutDefaultConstructor {
  WithoutDefaultConstructor() = delete;
};

int main() {
  std::cout << std::boolalpha;
  std::cout << IsDefaultConstructible<int> << std::endl;
  std::cout << IsDefaultConstructible<WithDefaultConstructor> << std::endl;
  std::cout << IsDefaultConstructible<WithoutDefaultConstructor> << std::endl;
}

输出:

true
true
false
阅读全文

C++ Template 阅读笔记(2)

2025/3/21

接上文,继续记录学习 C++ Template 这本书的心得。

变参模板

以下是一个例子:

void print() {}

template <typename T, typename... Types>
void print(T firstArg, Types... args) {
    std::cout << firstArg << std::endl;
    print(args...);
}

上述的代码必须要定义 print() 这个重载函数,否则编译会不通过。因为 Types... args 的参数数量最后会变成 0,这样就找不到对应的函数了,也可以采用如下的版本。

template <typename T>
void print(T arg) {
    std::cout << arg << std::endl;
}

template <typename T, typename... Types>
void print(T firstArg, Types... args) {
    print(firstArg);
    print(args...);
}

当传入的参数为 1 个的时候,会优先匹配第一个函数。

折叠表达式

template <typename... T> 
auto foldSum(T... args) { return (... + args); }

变参下标(Variadic Indices)

template <typename T, typename... Idx>
void printElements(const T &v, Idx... idx) {
  print(v[idx]...);
}
const char *names[]{"Alice", "Bob", "Peter"};
// print "Alice" and "Bob"
printElements(names, 0, 1);

template <size_t... Idx, typename C> void printElements(const C &v) {
  print(v[Idx]...);
}
const char *names[]{"Alice", "Bob", "Peter"};
// print "Bob" and "Peter"
printElements<1, 2>(names);

模板成员变量

template<typename T>
class MyClass {
public:
  static constexpr int size = sizeof(T);
};

template<typename T>
constexpr int Size = MyClass<T>::size;

std::cout << Size<char> << std::endl; // 1
std::cout << Size<double> << std::endl; // 8

各种 type trait,如 std::is_const_v 就是基于此。

从 static_assert 到 std::enable_if_t 再到 concept

static_assert

template <typename T> void foo(T t) {
  static_assert(sizeof(T) > 4,
                "size of given type should be greater than 4 bytes");
  std::cout << t << std::endl;
}
foo(1);

上述代码会报错:

Static assertion failed due to requirement ‘sizeof(int) > 4’: size of given type should be greater than 4 bytesclang(static_assert_requirement_failed)

std::enable_if_t

template <typename T> 
std::enable_if_t<(sizeof(T) > 4)> foo(T t) {
  std::cout << t << std::endl;
}

std::enable_if_t 等价于 std::enable_if::type。其中第一个模板类型是 bool 编译期常量,若为 true,则函数返回第二个模板类型(若没有第二个模板类型,则为 void),否则函数未定义。

上述代码会报未定义错误:

No matching function for call to ‘foo’

concept

template <typename T>
  requires(sizeof(T) > 4)
void foo(T t) {
  std::cout << t << std::endl;
}

类似的报错:

No matching function for call to 'foo'clang(ovl_no_viable_function_in_call)
main.cpp(18, 6): Candidate template ignored: constraints not satisfied [with T = int]
main.cpp(17, 12): Because 'sizeof(int) > 4' (4 > 4) evaluated to false

模板中按值传递 & 按引用传递

引用传递会保留类型的修饰,而值传递则会对类型进行退化。

template<typename T>
void foo(T &t) {
  if constexpr(std::is_array_v<T>) {
    std::cout << "is array" << std::endl;
  }
  if constexpr(std::is_const_v<T>) {
    std::cout << "is const" << std::endl;
  }
}

const char a[] = "Hello, World";
foo(a);
// 输出
// is array
// is const

而将上述代码修改为值传递:

template <typename T> void foo(T t) {
  if constexpr (std::is_array_v<T>) {
    std::cout << "is array" << std::endl;
  }
  if constexpr (std::is_const_v<T>) {
    std::cout << "is const" << std::endl;
  }
}

则不会产生任何输出,因为 const char[13] 被退化为了 char *
既不是 array 也不是 const

下面的函数有时候会产生问题:

template<typename T>
T foo(T t) {
  return t;
}

虽然会对类型进行退化,但是仍然可以通过显式的方式将 T 声明为引用。

int i;
foo<int &>(i);

这样可能会导致垂悬引用(dangling reference),所以此处可以通过三种方式保证返回值是退化的。

// 使用 auto 自动退化
template<typename T>
auto foo(T t) {
  return t;
}

// 使用 std::decay 显式退化
template<typename T>
std::decay_t<T> foo(T t) {
  return t;
}

// 去除引用
template<typename T>
std::remove_reference_t<T> foo(T t) {
  return t;
}

std::is_convertible

std::is_convertible 可以用于限制一个使用万能引用的构造函数的模板类型,例如:

class MyClass {
public:
  template <typename T>
  MyClass(T &&data)
    requires(std::is_convertible_v<T, std::string>)
      : data(data) {}

private:
  std::string data;
};

MyClass a(1); // error

报错信息:

No matching constructor for initialization of 'MyClass'clang(ovl_no_viable_function_in_init)
main.cpp(19, 7): Candidate constructor (the implicit copy constructor) not viable: no known conversion from 'int' to 'const MyClass' for 1st argument
main.cpp(19, 7): Candidate constructor (the implicit move constructor) not viable: no known conversion from 'int' to 'MyClass' for 1st argument
main.cpp(22, 3): Candidate template ignored: constraints not satisfied [with T = int]
main.cpp(23, 14): Because 'std::is_convertible_v<int, std::string>' evaluated to false

std::reference_wrapper

std::string str = "123";
auto r1 = std::ref(str); // std::reference_wrapper<string>
auto r2 = std::cref(str); // std::reference_wrapper<const string>

std::reference_wrapper 相当于在引用外包了一层,这样就可以在保证性能的前提下进行值传递(有些模板采用值传递)。std::reference_wrapper 可以隐式地转换成包裹的引用本身。

std::string &s2 = r1;
const std::string &s3 = r2;
阅读全文

面试笔记

2025/3/20

最近在面试阿里云文件存储 C++ 岗位,问了很多底层的问题,以前没有了解过或者印象不清,现在在此记录一番。

std::shared_ptr 和 std::weak_ptr

std::shared_ptr 由指向的数据和控制块组成。控制块维护的是一些元数据,包括强引用计数和弱引用计数。

  • 当强引用计数变为 0 的时候,会释放指向的数据,但是保留控制块,因为可能存在若干 std::weak_ptr 指向该数据。可以通过 std::weak_ptr::expired 判断是否当前数据已过期(即强引用为 0,数据已经被释放)。
  • 当弱引用计数变为 0 的时候,控制块也被释放,此时 std::shared_ptr 走向生命周期的终点。

如何遍历 std::tuple

std::get

auto t = std::make_tuple(1, '123', 2.0);
std::cout << std::get<0>(t) << std::endl;

std::apply

std::apply([](auto&&... args) {
    ((std::cout << args << " "), ...);
}, t);

std::index_sequence

template <typename T, size_t... Is>
void printTuple(const T &tuple, std::index_sequence<Is...>) {
    ((std::cout << std::get<Is>(tuple) << " "), ...);
    std::cout << std::endl;
}

template <typename ...Args>
void printTuple(const std::tuple<Args...> &t) {
    printTuple(t, std::make_index_sequence<sizeof...(Args)>{});
}
printTuple(t);

顺便复习了一下可变参数模板:

template <typename ...Args>
void print(Args... args) {
    // 注意下面这个有无空格的两种写法
    // (std::cout << ... << args);
    ((std::cout << args << " "), ...);
    std::cout << std::endl;
}

template <typename ...Args>
auto sum(Args... args) {
    return (... + args);
}

template <typename T, size_t... Is>
void printElements(const T &v, std::index_sequence<Is...>) {
  print(v[Is]...);
}

template <typename T, typename... Is>
void printElements(const T &v, Is... idx) {
  print(v[idx]...);
}

template <typename T, size_t... Is>
void printTuple(const T &tuple, std::index_sequence<Is...>) {
    ((std::cout << std::get<Is>(tuple) << " "), ...);
    std::cout << std::endl;
}

其他 points

  • 使用数据分区、分片来提升性能;
  • top 指令的 nice 值应该就对应 MLPQ 里的优先级;
  • std::string_view;
  • 为什么 MCS lock 使用 spin lock?
    • 基于 CAS,不涉及上下文切换
    • std::mutex 基于睡眠和唤醒
  • WAL 和 多机分布式备份的优劣对比
  • std::shared_from_this
  • 内外 const
阅读全文

面试常用算法复习

2025/3/11

快速排序

void quicksort(vector<int> &v, int l, int r) {
  if (l >= r) {
    return;
  }
  int target = v[l];
  int start = l;
  int end = r;
  while (true) {
    while (l < r && v[r] >= target) {
      r--;
    }
    while (l < r && v[l] <= target) {
      l++;
    }
    if (l == r) {
      break;
    }
    swap(v[l], v[r]);
  }
  swap(v[start], v[l]);
  quicksort(v, start, l - 1);
  quicksort(v, l + 1, end);
}

快速幂

int fastPower(int x, int n) {
  int base = x;
  int ans = 1;
  while (n) {
    if (n & 1) {
      ans *= base;
    }
    base *= base;
    n >>= 1;
  }
  return ans;
}

int fastPowerWithMod(int x, int n, int mod) {
  int base = x;
  int ans = 1;
  while (n) {
    if (n & 1) {
      ans = ((ans % mod) * (base % mod)) % mod;
    }
    base = ((base % mod) * (base % mod)) % mod;
    n >>= 1;
  }
  return ans;
}

并查集

int find(vector<int> &fa, int x) {
  return (fa[x] == x) ? x : (fa[x] = find(fa, fa[x]));
}

void merge(vector<int> &fa, int x, int y) { 
    fa[find(fa, x)] = find(fa, y); 
}

#define left_child(x) ((x + 1) * 2 - 1)
#define right_child(x) ((x + 1) * 2)
#define parent(x) ((x + 1) / 2 - 1)

void makeHeap(vector<int> &v) {
  int n = v.size();
  for (int i = 0; i < n; i++) {
    int leftIdx = left_child(i);
    int rightIdx = right_child(i);
    if (leftIdx >= n) {
      break;
    }
    if (leftIdx < n && v[leftIdx] > v[i]) {
      swap(v[leftIdx], v[i]);
    }
    if (rightIdx < n && v[rightIdx] > v[i]) {
      swap(v[rightIdx], v[i]);
    }
  }
}

void push(vector<int> &v, int ele) {
  v.push_back(ele);
  int idx = v.size() - 1;
  while (idx != 0) {
    int parentIdx = parent(idx);
    if (v[parentIdx] < v[idx]) {
      swap(v[parentIdx], v[idx]);
      idx = parentIdx;
    } else {
      break;
    }
  }
}

void siftDown(vector<int> &v) {
  int n = v.size();
  if (n == 0) {
    return;
  }
  int idx = 0;
  while (idx < n) {
    int leftIdx = left_child(idx);
    int rightIdx = right_child(idx);
    int maxIdx = -1;
    int maxV = v[idx];
    if (leftIdx < n && v[leftIdx] > maxV) {
      maxV = v[leftIdx];
      maxIdx = leftIdx;
    }
    if (rightIdx < n && v[rightIdx] > maxV) {
      maxV = v[rightIdx];
      maxIdx = rightIdx;
    }

    if (maxIdx == -1) {
      break;
    }
    swap(v[idx], v[maxIdx]);
    idx = maxIdx;
  }
}

int pop(vector<int> &v) {
  int n = v.size();
  if (n == 0) {
    return 0;
  }

  int ret = v[0];
  swap(v[n - 1], v[0]);
  v.pop_back();
  siftDown(v);
  return ret;
}

void heapSort(vector<int> &v) {
  vector<int> h(v);
  makeHeap(h);
  int n = v.size();
  for (int i = 0; i < n; i++) {
    v[i] = pop(h);
  }
}
阅读全文

C++ Template 阅读笔记(1)

Language 2025/3/2

最近在学习 C++ Template 这本书,补齐了一些模板元编程相关的信息,后续会持续更新。

函数模板

推断函数模板的返回值

假设我们希望计算两个两个类型不相同的变量中的最大值(如 1u 和 2.0f),我们要如何定义函数模板?

// since c++ 14
template <typename T1, typename T2> auto max(T1 x, T2 y) {
  return x > y ? x : y;
}

// here x's type is float
auto x = ::max(1u, 2.0f);

从 c++ 14 开始,我们可以使用 auto 让编译器帮我们自动计算返回值的类型,然而在此之前,我们必须使用 decltypecommon_type 这样的工具来告诉编译器,我们需要返回什么类型:

// c++ 11
template <typename T1, typename T2>
auto max(T1 x, T2 y) -> decltype(x > y ? x : y) {
  return x > y ? x : y;
}

// equal to
template <typename T1, typename T2>
auto max(T1 x, T2 y) -> decltype(true ? x : y) {
  return x > y ? x : y;
}

然而,上述的代码可能面临一些严重的问题。在一些特殊场景下,返回值可能为引用,这时候需要使用类型萃取(type trait)std::decay来将引用“退化”。

// c++ 11
#include <type_traits>
template <typename T1, typename T2>
auto max(T1 a, T2 b) -> typename std::decay<decltype(true ? a : b)>::type {
  return b < a ? a : b;
}

// since c++ 14, simplified
template <typename T1, typename T2>
auto max(T1 a, T2 b) -> std::decay_t<decltype(true ? a : b)> {
  return b < a ? a : b;
}

注意到 c++ 14 对 typename std::decay<decltype(true ? a : b)>::type 这样的写法进行了简化。

这边顺便回顾一下 autodecltype 的区别:

auto 在大部分场景下和模板类型 T 的行为是一致的,会对推导的类型进行退化(decay)。此处的退化是指:

  • 去除 constvolatile 等关键字;
  • 去除引用
  • 将数组退化成指针

decltype 则是老老实实地获取类型,不会进行退化操作。有一个比较有意思的特殊场景:

// will return int &
auto f(int a) -> decltype((a)) { return a; }

虽然上述的用法是不正确的(返回了垂悬引用,dangling reference),但是能表明 decltype 会把 () 括起来的变量视作引用。

言归正传,之前的写法用 decltype 虽然能解决问题,但是未免太麻烦了点,我们可以用 std::common_type_t<T1, T2, ...> 来解决这个问题:

// since c++ 14
template <typename T1, typename T2>
auto max(T1 a, T2 b) -> std::common_type_t<T1, T2> {
  return b < a ? a : b;
}

还有一种写法,实际上 c++ 在实例化模板时,只要能保证能推断出所有的类型即可,我们可以定义一个返回类型 RT:

template <typename RT, typename T1, typename T2> 
RT max(T1 a, T2 b) {
  return b < a ? a : b;
}
auto x = ::max<float>(1u, 2.0f);

这样只需要指定 RT 即可,因为 T1T2 的类型对于编译器而言是已知的。但是下面的写法就会出错:

template <typename T1, typename T2, typename RT>
RT max(T1 a, T2 b) {
  return b < a ? a : b;
}
auto x = ::max<unsigned int, float>(1u, 2.0f);

报错信息为:

Candidate template ignored: couldn’t infer template argument ‘RT’

编译器无法自动推断 RT 的类型,除非使用上述的 auto

c++ 还支持模板类型的默认值,比如常用的 std::priority_queue 实际上有 3 个模板类型。

类模板

c++ 支持使用基础的数值类型(double 除外)作为模板参数:

template <typename T, size_t MaxSize> class Stack {
  array<T, MaxSize> elements;
  Stack(T first) : elements({first}) {}
};

虽然不支持直接使用字符串常量,但是可以通过定义一个变量来解决

template <char const *name> class MyClass {
  ...
};
MyClass<"hello"> x; // ERROR: string literal "hello" not allowed
extern char const s03[] = "hi"; // external linkage 
char const s11[] = "hi"; // internal linkage
int main() {
  MyClass<s03> m03;               // OK (all versions)
  MyClass<s11> m11;               // OK since C++11
  static char const s17[] = "hi"; // no linkage
  MyClass<s17> m17;               // OK since C++17
}

推断指引(Deduction Guides)

template <typename T> class Stack {
  vector<T> elements;

public:
  Stack() = default;
  Stack(T first) : elements({first}) {}
};

Stack(char const *)->Stack<std::string>;

Stack s("123");

如果没有 Stack(char const *)->Stack<std::string>;,s 将被推导为 Stack<const char *>

Concept

template <typename T> struct AccumulationTraits;
template <> struct AccumulationTraits<int> {
  using AccT = int;
  static AccT constexpr zero = 0;
};

template <> struct AccumulationTraits<string> {
  using AccT = string;
  // support since c++ 17
  inline static AccT const zero = "";
};

template <typename T> auto accum(T begin, T end) {
  using AccT = AccumulationTraits<T>::AccT;
  AccT total = AccumulationTraits<T>::zero;

  for (T i = begin; i <= end; i++) {
    total += i;
  }
  return total;
}

int main(int, char **) {
  auto total = accum(1, 100);
  cout << total << endl;
}
template <typename T>
concept Addable = requires(T a, T b) {
                    { a + b } -> std::same_as<T>;
                  };

template <typename T>
  requires Addable<T> && std::is_arithmetic_v<T>
T accum(T begin, T end) {
  T total{};
  for (T i = begin; i <= end; i++) {
    total += i;
  }
  return total;
}

此处也可以把 typename 替换成 Addable 之类的 concept。和 rusttrait 语法非常类似。

阅读全文

Kmesh 环境配置

2024/7/9
  • 下载 openEuler 23.03

  • 安装 kernel headers:

    yum install kernel-headers
    
  • 安装 docker:

    参考:https://cloud.tencent.com/developer/article/2383890

    curl -fsSL https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/docker-ce.repo -o /etc/yum.repos.d/docker-ce.repo
    sed -i 's#https://download.docker.com#https://mirrors.tuna.tsinghua.edu.cn/docker-ce#' /etc/yum.repos.d/docker-ce.repo
    sed -i 's#$releasever#7#g' /etc/yum.repos.d/docker-ce.repo
    yum install docker-ce-24.0.7
    
  • 修改 docker proxy:

    修改 /etc/docker/daemon.json 为如下格式:

    {
        "proxies": {
            "http-proxy": "http://ip:7890",
            "https-proxy": "http://ip:7890",
            "no-proxy": "127.0.0.1,localhost"
        }
    }
    

    重启 docker:

    systemctl restart docker
    systemctl status docker
    
  • 安装 kind

    wget https://github.com/kubernetes-sigs/kind/releases/download/v0.23.0/kind-linux-amd64
    chmod +x kind-linux-amd64
    mv kind-linux-amd64 /usr/bin/kind
    
  • 安装 istioctl

    curl -L https://istio.io/downloadIstio | sh -
    cd istio-1.22.2/bin
    chmod +x istioctl
    mv istioctl /usr/bin/
    
  • 创建 ambient 模式集群

    kind create cluster --image=kindest/node:v1.23.17 --config=- <<EOF
    kind: Cluster
    apiVersion: kind.x-k8s.io/v1alpha4
    name: ambient
    nodes:
    - role: control-plane
    - role: worker
    - role: worker
    EOF
    
    istioctl install --set profile=ambient --skip-confirmation
    
  • 安装 go

    https://golang.google.cn/doc/install

  • 安装 kubectl

    https://kubernetes.io/zh-cn/docs/tasks/tools/install-kubectl-linux/

  • 安装 kmesh

    参考 https://kmesh.net/en/docs/setup/quickstart/ 即可。

参考:

阅读全文

Istio Hello World

2024/6/4

部署应用

注意,如果用 kind 需要将 HTTP_PROXY 设置为本机的 ip(而非 127.0.0.1),否则访问不到。

按照 istio book info 教程一步步来:https://istio.io/latest/docs/tasks/traffic-management/ingress/ingress-control/

在安装 gateway 之后,由于我们用的是 kind 而不是在云服务商提供的环境,我们需要使用 metallb 来提供外部 ip:https://metallb.universe.tf/installation/

可视化

ssh -NL 20000:localhost:20001 root@10.119.14.80 -p 22
阅读全文

记一次使用 Docker 遇到的奇怪问题

2024/6/3

最近在 jcloud 的服务器上起了一些 docker 容器,发现在容器内部怎么都无法访问公网。最后发现问题是网卡的 MTU 和 docker0 的 MTU 对不上(Docker 默认是 1500,而网卡是 1450)。

类似的问题:https://www.zeng.dev/post/2022-the-docker-mtu-problem/

可以通过 ifconfig -s 查看 MTU 信息。

解决方案:

修改 /etc/docker/damon.json:

{
    "mtu": 1450
}

重启 docker

systemctl restart docker
阅读全文

C++ 笔记(3)—— Effective Modern C++ (3-6 章)

Language 2024/5/29

Item 18 —— std::unique_ptr

std::unique_ptr 支持使用自定义的析构函数,可以传入一个 lambda 函数:

auto delFunc = [](auto* v) {
    std::cout << "Deconstruct for value: " << *v;
};
std::unique_ptr<int, decltype(delFunc)> ptr(new int{123}, delFunc);

这里 decltype 将再次发挥作用。注意,与 std::shared_ptr 不同,这个自定义析构函数的类型是会绑定到 std::unique_ptr 上的(参见模板第二个参数)。

Item 19 —— std::shared_ptr

std::shared_ptr 会额外维护一个控制块:

这个控制块位于堆上。在执行 move 操作的时候,控制块会沿用。当一个 std::shared_ptr 移动构造一个新的 std::shared_ptr 的时候,ref_cnt 不会增加(会将原来的设置为 nullptr)。

不能用一个裸指针初始化多个 std::shared_ptr,这会导致多个控制块,执行多次析构,造成未定义结果

std::shared_ptr 还支持对于每个实例都使用不同的自定义析构函数函数。如果使用了自定义析构函数,就无法用 std::make_shared 生成对应 std::shared_ptr 对象。与 std::unique_ptr 不同,自定义函数不会体现到类型上。
例如:

std::shared_ptr<int> ptr(new int{123}, delFunc);

有时候我们希望,对于一个对象 std::shared_ptr<Object>,我们希望在 Object 的成员函数中创建一个新的,指向自己(this)的 std::shared_ptr<Object>

class Object : public std::enable_shared_from_this<Object> {
   public:
    static std::shared_ptr<Object> Create(int val) {
        return std::shared_ptr<Object>(new Object(val));
    }
    std::shared_ptr<Object> CloneInner() { return shared_from_this(); }
    inline int Val() const { return val; }

    ~Object() { std::cout << "Deconstruct called." << std::endl; }

   private:
    int val;

    // shared_from_this 必须基于已有的 std::shared_ptr 对象。
    // 设置为 private 以防止用户构造一个裸露对象并调用 shared_from_this。
    explicit Object(int val) : val(val) {}
};

int main() {
    auto p = Object::Create(233);
    std::cout << "Value 1: " << p->Val() << std::endl;
    std::cout << "Reference Count: " << p.use_count() << std::endl;

    auto p2 = p->CloneInner();
    std::cout << "Value 2: " << p2->Val() << std::endl;

    std::cout << "Reference Count: " << p2.use_count() << std::endl;
}
阅读全文

C++ 笔记(2)—— Effective Modern C++ (1-3 章)

Language 2024/5/25

以下笔记囊括了前三章我学习到的新知识。

Item 1 —— 模板参数

推导按值传递的模板形式参数类型 T 时,若传入的实参带有 constvolatile 时,将会去掉。
因为按值传递就相当于是对数据进行了拷贝,不应该有上述限制修饰。

若形式参数为 T,传入的数据为函数或者数组,将会退化成对应的指针。除非使用 T &

Item 2 —— auto

一般而言,使用 auto 和模板 T 是一样的。除了以下的情况:

// x 的类别为 std::initializer_list<int>
auto x = {1, 2, 3};

template <typename T>
void func(T x);
// 错误,无法推断 T 的类型
func({1, 2, 3})

// 错误,无法推断类型
// auto 用在函数类型推导时会被当作模板类型推导
auto func() {
    return {1, 2, 3}
}

auto 可以用来推断一些只有编译器清楚的类型,比如 lambda 表达式。

auto x = []() { return "test"; };
auto y = std::function{[]() { return "test"; }};

比起 ystd::function 包裹,使用 auto 的效率更高,内存占用更少。

Item 3 —— decltype

下面的代码将报错:

template <typename Container, typename Index>
auto func(Container& c, Index i) {
    return c[i];
}
std::vector<int> vec{1, 2};
func(vec, 1) = 10;

此处 auto 相当于 T,按值传递。这将会把 vector::operator[] 返回的 T& 的引用给去掉。我们无法给一个右值赋值。

而如下的代码将通过编译:

template <typename Container, typename Index>
decltype(auto) func(Container& c, Index i) {
    return c[i];
}
std::vector<int> vec{1, 2};
func(vec, 1) = 10;

一般而言,不同于 auto 和模板类型推导,decltype 只会鹦鹉学舌。
对于类型为 T 的表达式,除非该表达式仅有一个名字,否则 decltype 总是得出类型 T &

// 此处类型为 int &
decltype(auto) f() {
    int x = 0;
    return (x);
}

Item 5 —— 优先选用 auto 而非显式类别声明

  • 例子 1:vector<int>::size()

    vector<int>::size() 返回的类型是 vector<int>::size_type,用很多常见的显示类别去声明会产生隐式的类型转换,在不同平台可能会有一定问题。

  • 例子 2:遍历 map

    std::map<std::string, int> m;
    for (const std::pair<std::string, int>& p : m) {
    }
    

    这么写看似没有问题,但是实际上真正遍历的时候 p 的类型应该是 std::pair<const std::string, int> &。上述的遍历方式会新建临时变量。这是 map 中的源码:

    typedef pair<const key_type, mapped_type>        value_type;
    ...
    typedef value_type&                              reference;
    

    转换成 auto 之后:

    std::map<std::string, int> m;
    for (const auto& p : m) {
    }
    

    此时 p 的类型为 const std::map::value_type &,符合预期。

  • 例子 3:std::vector<bool>

    std::vector<bool>::operator[] 的返回值不是 bool &,而是 std::vector<bool>::reference。这是因为 bool 在 c++ 里面是用一个 bit 表示的,vector<bool> 不允许直接返回一个 bit 的引用。

    这里如果用 auto 就可能会产生问题,而用 bool 则可以实现隐式类型转换。

Item 7 —— () 与 {}

std::vector<int>{1, 2};
std::vector<int>(1, 2);

两者完全不一样。

只要有机会,总是倾向于使用具有 std::initializer_list 类型的构造函数。

Item 9 —— using 与 typedef

using 支持模板,而 typedef 只能新建一个模板类:

template <typename T>
class Container {
    T x;
};

// 支持
template <typename T>
using ContainerList = std::vector<Container<T>>;
ContainerList<int> list;
// 不支持
template <typename T>
typedef std::vector<Container<T>> ContainerList;

// 支持
template <typename T>
class ContainerList {
   public:
    typedef std::vector<Container<T>> type;
};
typename ContainerList<int>::type list;

这里必须要有 typename,因为有些特例化实现可能会定义一个叫做 type 的成员对象,需要明确告诉编译器这里用的是 type 这个类型。

Item 10 —— 优先使用 enum class

enum class 会限制作用域。普通的 enum 中的枚举值可能会与其他的同名变量产生歧义。而使用 enum class 声明的枚举值只能通过 enum_class_name::value 的方式来访问。

同时,没有 class 限制的枚举值可以发生隐式类型转换,而加了限制的不行:

enum Color1 { red, white };
enum class Color2 { red, white };
// 可以
if (Color1::red > 1.5) {
}
// 不可以
if (Color2::red > 1.5) {
}

enum class 底层使用 int,也可以使用其他类型:

enum class Status: std::uint32_t;

Item 11 —— 使用 delete 而非未定义的私有函数

应该将声明为 delete 的函数声明为 public,这样便于生成更好的报错信息。
假如设置为 private,用户使用了这个函数,一些编译器会先检查函数的可访问性,说该函数是 private,但是更重要的信息显然是该函数被标记为了 delete

Item 12 —— 使用 override 关键字

使用 override 可以让编译器更好地识别函数是否实现了重写。例如 C++ 11 中有一个鲜为人知的特性:

class A {
    void doSomething() &;
    void doSomething() &&;
}

是两个函数,&& 对应右值调用该函数的情况,例如下面的例子:

class A {
   public:
    void doSomething() & { std::cout << "Called first." << std::endl; }
    void doSomething() && { std::cout << "Called second." << std::endl; }
};

A getRVal() {
    return A{};
}

int main() {
    A a{};
    a.doSomething();
    getRVal().doSomething();
}

将输出:

Called first.
Called second.

Item 14 —— noexcept

在保证一定不会产生异常的前提下,能用 noexcept 就用,因为编译器可以对具有该标识的函数进行更好的优化。同时,编译器不会强求 noexcept 一定要调用具有 noexcept 标识的函数。

Item 15 —— constexpr

  • constexpr 对象(包括函数)都具有 const 属性,且由编译期已知的值完成初始化。
  • constexpr 函数在调用的时候,如果传入的实参值是编译器已知的,则会产出编译期结果。否则退化成普通的函数,在运行时计算结果。

好处是可以在编译期就确定一些变量的值,坏处是可能增加编译时间。

Item 16 —— 保证 const 函数的线程安全性

对于被 const 修饰的函数,我们应该保证其线程安全性。例如:

class A {
   private:
    bool is_valid{};
    int val{};
    mutable std::mutex mtx;

   public:
    int getVal() const {
        std::lock_guard<std::mutex> lock_guard(mtx);
        if (is_valid) {
            return val;
        }
        return -1;
    }
};

为了这一点,我们通常会配合 std::mutex 或者 std::atomic 等同步原语。由于 mutex 在上锁的时候会修改其内部状态(该函数未被标记为 const),所以我们需要通过 mutable 告诉 const 函数该变量是会变化的,不应该在函数内限制其不可变。

阅读全文
1 2 3
avatar
Zihong Lin

What I can’t create, I don’t understand.