master 发布的文章

iPadPro_0.jpg

生日的时候老婆大人送了一台iPad,相比之前使用的iPad Pro 2017 (10.5 inch)感觉提升还是挺大的。

首先的提升是屏幕,大了0.5英寸, 屏占比提升了不少。其实2017 iPad Pro还有个奇怪的CPU过热问题:CPU发热量比较大,然后屏幕在CPU附近的区域会被“烤”黄。这个现象在白色显示的时候比较明显。不知道为什么,觉得屏幕素质还是比先辈提升了一点点。

iPadPro_2.jpg

CPU和内存方面有了显著提升,但是对我而言上手实际感觉出来不了多少。A10X变成了A12Z,内存从4GB提升到了6GB。但是库克还是比较省材料啊,想来安卓的手机内存都有8GB了。

ipad_pro_4.png

重量上面,这两台机器其实是很相似的。10.5寸款是477 g,新款轻了6g。如果不戴套的话,手持感觉相当棒~一般来说如果我看Kindle的话就会把套子去掉。推荐买那种背面磁吸的手机壳,拆装很方便!

iPadPro_case.jpg

本次更新最大的提升点是摄像头,但是鄙人用iPad摄像头的机会是少之又少。稍许试了一下,相比17款,摄像头的拍摄效果有巨大提升,特别是景深传感器感觉是下一代iPhone的提前布局。

总而言之,如果手持是10.5寸Pad设备的话,强烈建议换机。但如果用的2018款的iPad Pro,除非要用到摄像头,否则不建议再花钱啦~

C++11有了一些关于线程的模型,在此之前C++里可是各自为政的,各种线程库各种神奇用法。其中有两个好玩的东西就是std::promise<T>std::future<T>,下文书中形容它们像是个“虫洞”。

  • std::future是虫洞的出口:一个未来对象的“获取器”,在未来的某一刻它能返回一个有用的值,但现在还没有...
  • std::promise是虫洞的入口:我们要保证在未来的某一刻给一个值进去,但不是现在...

Basic usage

一般来说这俩是成对使用的,看个代码:

#include <cassert>
#include <chrono>
#include <future>
#include <iostream>
#include <string>
#include <thread>

using namespace std::chrono_literals;

int main()
{
  std::promise<int> p1, p2;
  std::future<int> f1 = p1.get_future();
  std::future<int> f2 = p2.get_future();

  p1.set_value(42);
  assert(f1.get() == 42);

  std::thread t([&]() {
    std::this_thread::sleep_for(100ms);
    p2.set_value(43);
  });

  auto start_time = std::chrono::system_clock::now();
  assert(f2.get() == 43);
  std::chrono::duration<double, std::milli> elapsed = std::chrono::system_clock::now() - start_time;
  std::cout << "Waited " << elapsed.count() << " ms\n";
  t.join();
  return 0;
}

// output: Waited 100.253 ms                                                                                                                                                       

一个future对象可以通过promise.get_future()方法创建出来。当我们有真正的值来填进promose对象的时候,就用promise.set_value(v)方法。同时,(一般是在另一个线程里)当准备要获取值的时候,就调用future.get()方法。get方法会保持阻塞状态直到一个有效值被填进去。

值得一提的是,有一个特化的T = void。这货有啥用呢?可以用来阻塞等待某个并行任务完成:

std::promise<void> ready_p;
std::future<void> read_f = ready_p.get_future();

std::thread thread_b([&]() {
    prep_work();
    ready_p.set_value(); // no arg
    main_work();
});
ready_f.wait();
// now that thread B has completed

A bit of details

需要留意的是,promise也好future也罢,都是有动态内存分配(dynamic memory allocation)的开销的。 std_promise_future_schema.PNG(图源为参考文献)

留意图中的那个State对象,它基本上是一个shared_ptr——因为虫洞的两端(很可能是不同线程)都要用到这个共享对象(shared ownership)。所以创建std::promose/std::future的时候都是要申请新的堆空间。

Reference

本文全文参考自本书: <Mastering the C++17 STL: Make Full Use of the Standard Library Components in C++17>

std::variant应当是用来替代union来使用的,后者从C时代就有了,但是它缺乏类型安全的保障。boost库很早之前就有了实现(其实从C++11开始,感觉boost不知道多少东西被借鉴走了)。 std::variant的类长这个样子:

template <class... Types>
class variant;

Usage

使用起来也是比较方便的

std::variant<int, double> v1;
v1 = 1; // activate the "int" member
assert(v1.index() == 0);
assert(std::get<0>(v1) == 1);

v1 = 3.14; // activate the "double" member
assert(v1.index() == 1);
assert(std::get<1>(v1) == 3.14);
assert(std::get<double>(v1) == 3.14);

assert(std::holds_alternative<int>(v1) == false);
assert(std::holds_alternative<double>(v1) == true);

assert(std::get_if<int>(&v1) == nullptr);
assert(*std::get_if<double>(&v1) == 3.14);

上面代码里的几个好用的constexpr函数:

  • std::get<>:可以传入index或是type。如果index或者类型不对的话,会跑出std::bad_variant_access异常;
  • std::get_if<>:这个是不抛异常的版本。注意参数和返回值类型都是指针——如果没有有效元素,那就会返回nullptr;
  • std::holds_alternative<Type>:判断一个Type在不在定义的variant参数类里面;

Visiting variants

如果要根据当前存储的类型来对variant对象做一些操作,第一会想到写一些if语句

if (std::holds_alternative<int>(v1) == true) {
  // handle int value
}
else {
  // handle double value
}

但是如果variant的可选类型比较多的话,定义一个Visitor类会有助于按类型来执行操作,逻辑会显得清晰

struct Visitor {
  double operator() (double d) { return d; }
  double operator() (int i) { return double(i); }
};

void show(std::variant<double, int> v) {
  std::cout >> std::visit(Visitor{}, v) << std::endl;
}

void test() {
  show(3.14);
  show(1);
}

std::generate是“遍历——执行”工具函数的一种

#include <algorithm>
#include <iostream>
#include <string>
#include <vector>

int main()
{
  std::vector<std::string> v(4);
  std::generate(v.begin(), v.end(), [i=0]() mutable {
      return ++i % 2 ? "hello" : "world";
  });

  for (auto&& s : v) {
    std::cout << s << " ";
  }
  return 0;
}

上面的函数输出是hello world hello world.

std::generate(beginIt, endIt, generator)函数接受起止的迭代器以及一个生成方法。

看代码说话

template <typename Iterator>
auto distance(Iterator begin, Iterator end) {
  using Traits = std::iterator_traits<Iterator>;
  if constexpr (std::is_base_of_v<std::random_access_iterator_tag, typename Traits::iterator_category>) {
    return end - begin;
  } else {
    auto result = typename Traits::difference_type();
    for (auto it = begin; it != end; ++it) {
      ++result;
    }
    return result;
  }
}

上面的代码用来判断两个iterator之间的距离。主要逻辑是判断iterator是否是可随机访问的iterator然后作分支处理,可以随机访问的迭代器直接把iterator相减。 在constexpr if语句中,条件的值必须是可按语境转换到 bool 类型的经转换常量表达式。若其值为 true,则舍弃 false分支语句(若存在),否则舍弃 true分支语句。有意思的一点是,被舍弃语句中的 return 语句不参与函数返回类型推导,也就是说两个分支语句中的return推倒出来的类型允许不一样。

如果没有C++17这种怎么写呢?就是比较麻烦。 一可以加一个参数,但是每个tag写一个

template <typename Iterator>
auto distance(Iterator begin, Iterator end, std::bidirectional_iterator_tag); // one for each tag type

或者用SFINAE写两个

template <typename Iterator>
auto distance(Iterator begin, Iterator end, 
              typename std::enable_if_t<std::is_base_of_v<一大坨>>* = nullptr);