(cppcon)c++11 几个生产中常用的小工具

这个讲的是scope_exit, std::range, make_range , operator <=> ,后面这两个已经进入c++标准了,前面这个有很多案例

先说scope_exit

作者写了个宏,显得scope_exit更好用一些,实际上没啥必要

他的实现类似这个

 template <typename Callable> class scope_exit {
   Callable ExitFunction;
   bool Engaged = true; // False once moved-from or release()d.
 
 public:
   template <typename Fp>
   explicit scope_exit(Fp &&F) : ExitFunction(std::forward<Fp>(F)) {}
 
   scope_exit(scope_exit &&Rhs)
       : ExitFunction(std::move(Rhs.ExitFunction)), Engaged(Rhs.Engaged) {
     Rhs.release();
   }
   scope_exit(const scope_exit &) = delete;
   scope_exit &operator=(scope_exit &&) = delete;
   scope_exit &operator=(const scope_exit &) = delete;
 
   void release() { Engaged = false; }
 
   ~scope_exit() {
     if (Engaged)
       ExitFunction();
   }
 };
 

几个疑问

  • 为什么直接保存Callable,而不是用 std::function来保存
    • 太重,引入类型擦除,效率不行,生成的汇编很差
    • 作者还稍稍科普了下类型擦除的技术,怎么做到的
  • 为什么不用AA的scopeguard或者boost::scope_exit
    • 不好用,还需要传参数,scope_exit好就好在可以传lambda

第二个是make_iterable,对应标准库应该是std::range 本质上还是视图一类的东西。因为自定义类型,不想写一套iterator接口,给个view就行。没啥说的

第三个是operator<=> 实际上是解决comparator这种语义实现的问题

就比如rocksdb,bytewisecomparator

  int r = memcmp(data_, b.data_, min_len);
  if (r == 0) {
    if (size_ < b.size_) r = -1;
    else if (size_ > b.size_) r = +1;
  }
  return r;

内部肯定有三个分支,这是影响效率的,怎么搞?

再比如std::tuple 他是怎么比较的?

然后推导出一个适当的operator<=>来解决上述问题

ref

Read More

(cppcon)用表达式模板实现一个一个简单安全的log

传统写法

#define LOG(msg) \
    if (s_bLoggingEnabled) \
       std::cout<<__FILE__<<"("<<__LINE__<<")"<<msg<<std::endl;

汇编长这样

   0x0000000000400c9d <+53>:    mov    %rax,%rdi
   0x0000000000400ca0 <+56>:    callq  0x400af0 <_ZNSaIcED1Ev@plt>
   0x0000000000400ca5 <+61>:    movzbl 0x201528(%rip),%eax        # 0x6021d4 <s_bLoggingEnabled>
   0x0000000000400cac <+68>:    test   %al,%al
   0x0000000000400cae <+70>:    je     0x400d2c <main()+196>
   0x0000000000400cb0 <+72>:    mov    $0x400e5a,%esi
   0x0000000000400cb5 <+77>:    mov    $0x6020c0,%edi
   0x0000000000400cba <+82>:    callq  0x400ad0 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
   0x0000000000400cbf <+87>:    mov    $0x400e65,%esi
   0x0000000000400cc4 <+92>:    mov    %rax,%rdi
   0x0000000000400cc7 <+95>:    callq  0x400ad0 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
   0x0000000000400ccc <+100>:   mov    $0xe,%esi
   0x0000000000400cd1 <+105>:   mov    %rax,%rdi
   0x0000000000400cd4 <+108>:   callq  0x400a70 <_ZNSolsEi@plt>
   0x0000000000400cd9 <+113>:   mov    $0x400e67,%esi
   0x0000000000400cde <+118>:   mov    %rax,%rdi
   0x0000000000400ce1 <+121>:   callq  0x400ad0 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
   0x0000000000400ce6 <+126>:   mov    $0x400e69,%esi
   0x0000000000400ceb <+131>:   mov    %rax,%rdi
   0x0000000000400cee <+134>:   callq  0x400ad0 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
   0x0000000000400cf3 <+139>:   mov    %rax,%rdx
   0x0000000000400cf6 <+142>:   lea    -0x40(%rbp),%rax
   0x0000000000400cfa <+146>:   mov    %rax,%rsi
   0x0000000000400cfd <+149>:   mov    %rdx,%rdi
   0x0000000000400d00 <+152>:   callq  0x400b50 <_ZStlsIcSt11char_traitsIcESaIcEERSt13basic_ostreamIT_T0_ES7_RKNSt7__cxx1112basic_stringIS4_S5_T1_EE@plt>
   0x0000000000400d05 <+157>:   mov    $0x37,%esi
   0x0000000000400d0a <+162>:   mov    %rax,%rdi
   0x0000000000400d0d <+165>:   callq  0x400a70 <_ZNSolsEi@plt>
   0x0000000000400d12 <+170>:   mov    $0x400e6d,%esi
   0x0000000000400d17 <+175>:   mov    %rax,%rdi
   0x0000000000400d1a <+178>:   callq  0x400ad0 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
   0x0000000000400d1f <+183>:   mov    $0x400b10,%esi
   0x0000000000400d24 <+188>:   mov    %rax,%rdi

作者说了log相关的指令问题,可能会阻止编译器优化,icache也不友好

针对此,要达到减少指令,保留速度,类型安全还方便,所以就用到表达式模板了,把工作放到编译期

怎么做?把所有打印的参数用表达式墨宝封装一下,typelist登场

拆成两部分,log 和logdata

logdata就是个表达式模板typelist,把所有的参数串起来

using namespace std;
#define LOG(msg) \
    if (s_bLoggingEnabled) \
       (log(__FILE__,__LINE__,LogData<None>()<<msg));

template <typename List>
struct LogData{
    typedef List type;
    List list;
};
struct None{};

template <typename Begin, typename Value>
LogData<std::pair<Begin&&, Value&&>> operator<<(LogData<Begin>&& begin,
                                                Value&& v) noexcept {
    return ;
}

template <typename Begin, size_t n >
LogData<std::pair<Begin&&, const char* >> operator<<(LogData<Begin>&& begin,
                                               const char(&sz)[n]) noexcept{
    return ;
}

template<typename TLogData>
void log(const char* file, int line, TLogData&& data)
noexcept {
    std::cout<< file<<" "<<line<<": ";
    LogRecursive(std::cout, std::forward<typename TLogData::type>(data.list));
    std::cout<<std::endl;
}

template<typename TLogDataPair>
void LogRecursive(std::ostream& os, TLogDataPair&& data) noexcept{
   LogRecursive(os,std::forward<typename TLogDataPair::first_type>(data.first));
   os<<std::forward<typename TLogDataPair::second_type>(data.second);
}
inline void LogRecursive(std::ostream& os, None) noexcept{}

int main()
{
  s_bLoggingEnabled = true;
  string s{"blaa"};
  LOG("sth"<<s<<55<<"!!");
}

   0x0000000000400cab <+67>:    movzbl 0x201522(%rip),%eax        # 0x6021d4 <s_bLoggingEnabled>
   0x0000000000400cb2 <+74>:    test   %al,%al
   0x0000000000400cb4 <+76>:    je     0x400d42 <main()+218>
   0x0000000000400cba <+82>:    movl   $0x37,-0x44(%rbp)
   0x0000000000400cc1 <+89>:    lea    -0x11(%rbp),%rax
   0x0000000000400cc5 <+93>:    mov    $0x40130a,%esi
   0x0000000000400cca <+98>:    mov    %rax,%rdi
   0x0000000000400ccd <+101>:   callq  0x400e24 <operator<< <None, 4ul>(LogData<None>&&, char const (&) [4ul])>
   0x0000000000400cd2 <+106>:   mov    %rax,-0x30(%rbp)
   0x0000000000400cd6 <+110>:   mov    %rdx,-0x28(%rbp)
   0x0000000000400cda <+114>:   lea    -0xa0(%rbp),%rdx
   0x0000000000400ce1 <+121>:   lea    -0x30(%rbp),%rax
   0x0000000000400ce5 <+125>:   mov    %rdx,%rsi
   0x0000000000400ce8 <+128>:   mov    %rax,%rdi
   0x0000000000400ceb <+131>:   callq  0x400ebd <operator<< <std::pair<None&&, char const*>, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&>(LogData<std::pair<None&&, char const*> >&&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&)>
   0x0000000000400cf0 <+136>:   mov    %rax,-0x40(%rbp)
   0x0000000000400cf4 <+140>:   mov    %rdx,-0x38(%rbp)
---Type <return> to continue, or q <return> to quit---
   0x0000000000400cf8 <+144>:   lea    -0x44(%rbp),%rdx
   0x0000000000400cfc <+148>:   lea    -0x40(%rbp),%rax
   0x0000000000400d00 <+152>:   mov    %rdx,%rsi
   0x0000000000400d03 <+155>:   mov    %rax,%rdi
   0x0000000000400d06 <+158>:   callq  0x400f6a <operator<< <std::pair<std::pair<None&&, char const*>&&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&>, int>(LogData<std::pair<std::pair<None&&, char const*>&&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&> >&&, int&&)>
   0x0000000000400d0b <+163>:   mov    %rax,-0x60(%rbp)
   0x0000000000400d0f <+167>:   mov    %rdx,-0x58(%rbp)
   0x0000000000400d13 <+171>:   lea    -0x60(%rbp),%rax
   0x0000000000400d17 <+175>:   mov    $0x40130e,%esi
   0x0000000000400d1c <+180>:   mov    %rax,%rdi
   0x0000000000400d1f <+183>:   callq  0x401002 <operator<< <std::pair<std::pair<std::pair<None&&, char const*>&&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&>&&, int&&>, 3ul>(LogData<std::pair<std::pair<std::pair<None&&, char const*>&&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&>&&, int&&> >&&, char const (&) [3ul])>
   0x0000000000400d24 <+188>:   mov    %rax,-0x70(%rbp)
   0x0000000000400d28 <+192>:   mov    %rdx,-0x68(%rbp)
   0x0000000000400d2c <+196>:   lea    -0x70(%rbp),%rax
   0x0000000000400d30 <+200>:   mov    %rax,%rdx
   0x0000000000400d33 <+203>:   mov    $0x31,%esi
   0x0000000000400d38 <+208>:   mov    $0x401311,%edi
   0x0000000000400d3d <+213>:   callq  0x401054 <log<LogData<std::pair<std::pair<std::pair<std::pair<None&&, char const*>&&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&>&&, int&&>&&, char const*> > >(char const*, int, LogData<std::pair<std::pair<std::pair<std::pair<None&&, char const*>&&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&>&&, int&&>&&, char const*> >&&)>
   0x0000000000400d42 <+218>:   lea    -0xa0(%rbp),%rax

我个人测验,感觉并没有好到哪里去。。gcc开O3的话两个长得基本一致了。。就当学一下typelist转发技术好了

ref

Read More

(cppcon)return values take a closure walk

这个ppt讲的是如何把返回值用lambda包起来,让返回值auto,用作者的图来总结这篇内容

1556158688788

首先思考在Context下的调用

void callWithin(const std::function<void()>& fn){
    ScopedContext context;
    try{
        fn();
    } catch (SomeException& e){
        // handle exception here
    }
}

void printLine(const std::string& text){
    std::cout<<text<<'\n';
}
callWithin([](){printLine("Hello, CppCon");});

回调小函数扔到lambda里 接口都操作lambda

也可以把这个变成模板, 这样 接口可以是任何类型的std::function, lambda

template <typename Callable>
void callWithin(const Callable& fn){
    ScopedContext context;
    fn();
}

进一步,如果想要回调函数的返回值,不需要要变动lambda接口

double sum(double a,double b){return a+b;}
double res = callWithin([](){return sum(3.14,2.71);})

可以在callWithin里改动lambda/function接口,但这降低了灵活性

double callWithin(const std::function<double()>&fn)...//如果返回值不是double怎么办?

解决办法,template auto-decltype

template <typename Callable> 
auto callWithin(const Callable& fn) ->decltype(fn()){
   decltype(fn()) result{};
   auto wrapperFn =[&]()->void
   {
       result = fn();
   }
   callWithImpl(wrapperFn);
   return result;
}
void callWithinImpl(const std::function<void()>& fn);

注意,这里用局部变量封一层result,弄一个局部的lambda,然后扔进callWithinImpl里,本质是加了一层,把原来的lambda放return的方案内嵌处理

传统方法,context肯定会有context manager,通过manager类接口来搞定, 接口也是固定的

class Contextmanager{
public:
    virtual void callWithin(const std::function<void()>&fn) = 0;
};

然后整合上面的实现,大致这样

class Contextmanager{
public:
  template <typename Fn>
  void callWithin(const Fn& fn, std::false_type) -> decltype(fn())
  {
    decltype(fn()) result{};
    callWithinImpl([&]{result=fn();});
    return result;
  }
private:
    virtual void callWithinImpl(const std::function<void()>&fn) = 0;
};

double result = manager->CallWithin([]{return sum(3.14, 2/71);});

这个方案又有了新问题,原来的直接传void返回的functor不能工作了

特化吧

template <typename Fn>
auto callWithin(const Fn& fn) -> decltype(fn())
{
    return _callWithin(fn, std::is_same<decltype(fn()),void>());
}
// true就直接调用,没有返回值
template <typename Fn>
void _callWithin(const Fn& fn, std::true_type) -> decltype(fn())
{
    callWithinImpl([&]{fn();});
}

template <typename Fn>
void _callWithin(const Fn& fn, std::false_type) -> decltype(fn())
{
    decltype(fn()) result{};
    callWithinImpl([&]{result=fn();});
    return result;
}

新的挑战,callWithin失败

所以还是需要内部callWithinImpl有个返回值,来设定result,需要std::optional 包装一下

template <typename Fn>
void callWithin(const Fn& fn) -> std::optional<decltype(fn())>
{
    decltype(fn()) result{};
    bool ok = callWithinImpl([&]{result=fn();});
    if (ok)
    	return result;
    else
        return std::nullopt;
}

ref

Read More

(cppcon)类型推导以及为什么需要关注这个

作者是Scott Meyers,这个内容就是他写的modern effective c++ 前几条

用一个图来概括

1556106456533

和auto相关的类型推导

一例

template <typenamet T>
void f(ParamType param);
f(expr);//  从expr推导T和ParamType

三种场景,ParamType可能是1)T& /T* 不是T&& 2)T&& 3)T

推导规则十分简单,

  • 如果expr是T&, 就忽略
  • 模式匹配expr类型和ParamType来决定T

` 场景,ParamType=T&`

template <typename T>
void f(T& param);

int x=22;
const int cx = x;
const int& rx = x;
f(x); //  T = int,       param = int&
f(cx); // T = const int, param = const int&
f(rx); // T = const int, param = const int& 注意,此处的T ,expr是T& 直接忽略了&

` 场景,ParamType=const T&`

template <typename T>
void f(const T& praam);

int x=22;
const int cx = x;
const int& rx = x;
f(x); //  T = int,       param = const int& 注意,此处的T ,expr是const T& 直接忽略了
f(cx); // T = const int, param = const int&
f(rx); // T = const int, param = const int& 

场景, ParamType=T*

template <typename T>
void f(T* praam);

int x=22;
const int *pcx = &x;
f(&x);  //  T = int,       param = int *
f(pcx); //  T = const int, param = const int *

场景,ParamType=T&&

如果涉及到万能引用,场景就会复杂,万能引用的退化方向比较多,值语义就是值语义,引用语义就是引用语义,万能引用表达的引用语义就比较杂,大部分场景和引用是一致的,除了

如果expr是左值,能推导出E,T能推导出E&,等价于expr是左值引用就会有引用折叠

听起来很复杂,就是所有左值的T都被推导出T&,右值推导出T&&,如果左值T是T&就折叠一个&,还是T&

template <typename T>
void f(T&& param);
//f(expr)
int x = 22;
const int cx = x;
const int& rx = x;
f(x); //  T = int&,       param = int&
f(cx); // T = const int&, param = const int&
f(rx); // T = const int&, param = const int&   折叠了一个&
f(22); // T = int,        param = int&&  右值专属场景,完美转发

涉及到auto,如何推导?类似上面,引用会退化

int x = 22;
const int cx = x;
const int& rx = x;

auto& v1 = x; //auto = int;
auto& v2 = cx; //auto = const int
auto& v3 = rx; // auto = const int
const auto& v4 = x; // auto = int
const auto& v5 = cx; // auto =int
const auto& v6 = rx; // auto =int
auto v7 = x;// auto =int
auto v8 = cx;// auto = int
auto v9 = rx;// auto = int
auto&& v10=rx;// type=const int&   左值,引用折叠了

指针const auto推导

void someFunc(const int* const param1,
              const int*       param2,
                    int*       param3)
{
    auto p1=param1;// auto = const int* 忽略了最后那个
    auto p2=param2;// const int*
    auto& p2v2=param2;// const int* const&,这个没忽略,注意区别
    auto p3=param3;// int*
}

会忽略指针const , 视*param为一体

  • 如果expr是const 或者volatile,直接忽略cv限定

  • 还有针对函数指针和数组指针退化行为的边角场景,一律退化成指针

  • {}表达式推导成初始化列表,注意函数参数会推导失败

还有一大堆细节不列了,有点语言律师的赶脚

lambda捕获类型推导

默认lambda是const的捕获内部参数不能直接改,需要显式mutable

observing deduced types

作者的一个小技巧,不实现类,来通过编译器的推导和编译报错观察推导类型

template <typename T>
class TD;
template <typename T>
void f(T& param){
    TD<T> tType;
    TD<decltype(param)> paramType;
}

作者也介绍了type_info和boost::type_index 不赘述

decltype推导

  • 左值就是T, 双括号强制T&
  • 左值表达式T&

函数返回值类型推导

  • auto
  • decltype(auto) 表达式通常是T&,左值是T,加括号强制T&

ref

Read More


be smart about pointers

讲了几种pointer以及背后的惯用法

RAII

class C;
class SBRM{
    C* pc;
public:
    SBRM(C* c):pc(c){}
    ~SBRM(){delete pc;}
    operator C*(){return pc;}
};
...
for(int i=0; i<100; ++i){
   SBRM cc(new C(i)) ;
    C* c= cc;
    /*do sth about c*/
}

//没有SBRM管理是这样的
for(int i=0; i<100; ++i){
    C* c(new C(i)) ;
    /*do sth about c*/
    delete c;
}

智能指针

  • boost::scoped_ptr std::unique_ptr
  • shared_ptr +make_shared + enable_shared_from_this + weak_ptr

能做什么

  • RAII/ 定制deleter/ 引用计数/保证删除 -> 没有内存泄漏或者double free问题,代码更好维护,简化代码逻辑,不用担心内存所有权问题

scoped_ptr vs unique_ptr scoped_ptr是c++11前产物,没有定制deleter和转移所有权(move), 针对a数组有scoped_array unique_ptr没有分开,功能合到一起了

shared_ptr RAII,引用计数,可复制,定制deleter, make_shared比new要快(省一次new,避免异常问题)

一个实现结构

Snipaste_2019-05-20_14-52-24

感知shareshare-aware enable_shared_from_this

用this构造shared_ptr是十分危险的,如果这个shared_ptr 多次拷贝,就会有double-free问题

这个问题本质上和多个shared_ptr由同一个裸指针初始化 是同一个场景

struct Bad
{
    std::shared_ptr<Bad> getptr() {
        return std::shared_ptr<Bad>(this);
    }
    ~Bad() { std::cout << "Bad::~Bad() called\n"; }
};
{
    // Bad, each shared_ptr thinks it's the only owner of the object
    std::shared_ptr<Bad> bp1 = std::make_shared<Bad>();
    std::shared_ptr<Bad> bp2 = bp1->getptr();
    std::cout << "bp2.use_count() = " << bp2.use_count() << '\n';
}// UB: double-delete of Bad

struct Good: std::enable_shared_from_this<Good> // note: public inheritance
{
    std::shared_ptr<Good> getptr() {
        return shared_from_this();
    }
};
{
    // Good: the two shared_ptr's share the same object
    std::shared_ptr<Good> gp1 = std::make_shared<Good>();
    std::shared_ptr<Good> gp2 = gp1->getptr();
    std::cout << "gp2.use_count() = " << gp2.use_count() << '\n';
}

这个例子也展示出,本质上需要把this提升成shared_ptr,

不放在本体中,就只能放在enable_shared_from_this里,通过继承来侵入,然后调用share_from_this来提升(weak_ptr,shared_ptr有可能循环)

shared_ptr构造中判断是否是enable_shared_from_this基类( 函数重载)来做钩子,构造weak_ptr

引用计数指针的两种实现 侵入式,非侵入式

对象级别引用计数,侵入式 boost::intrusive_ptr(感觉这个早晚也得进标准库) COM (这个应该没人用吧) 方便管理和转换普通指针,但是对象结构复杂

容器级别,shared_ptr<T> 干净,就是不能和普通指针混着用,需要转来转去

ref

Read More


C++ Concurrency In Action读书笔记

C++ Concurrency In Action

为什么使用并发

  • 分离关注点
  • 性能

线程管理

std::thread

  • 当把函数对象传入到线程构造函数中时,需要避免“最令人头痛的语法解析C++’s most vexing parse
    • 解决办法 两层()或统一初始化方法{}
  • 使用lambda规避
  • join和detach
  • 传递参数,注意std::thread构造函数会复制参数(可以移动,不可以拷贝)

转移线程所有权 (本身可以移动,不可以拷贝)

class scoped_thread{
    std::thread t_;
public:
    explicit scoped_thread(std::thread t)t_(std::move(t)){
        if (!t_.joinable())
            throw std::logic_error("No thread");
    }
    ~scoped_thread(){
        t.join();
    }
    scoped_thread(scoped_thread const&) = delete;
    scoped_thread& operator=(scoped_thread const&) = delete;
};

c++17建议std::jthread 自带上面的析构功能,不用手写join 标准库里已经有了

一行循环join,确实比range-for好看点

std::for_each(threads,begin(), threads.end(), std::mem_fn(&std::thread::join));

std::thread::hardware_concurrency() 使用线程数暗示

` std::thread::get_id std::this_thread::get_id()`

线程间共享数据

共享数据带来的问题(多用assert)

  • 竞态条件 互斥与无锁编程
  • 软件事务内存 software transactional memory
  • 接口中的竞态条件
    • 解决方案 传引用
    • 解决方案 异常安全的拷贝构造和移动构造
    • 解决方案,返回指针 shared_ptr
  • 锁的细粒度 以及死锁的解决方案
    • 避免嵌套锁
    • 持有锁,要保证接下来的动作没有危害(用户侧调用就很容易被坑,所以避免调用用户侧代码)
    • 固定顺序开锁解锁
      • 锁,分优先级,层次
      • unique_lock unlock…lock…
      • lock_guard
  • 初始化时保护共享数据
    • std::once_flag std::call_once
    • meyer’s singleton
  • boost::shared_lock shared_lock<>

###

同步并发操作

  • 等待事件与条件
    • 忙等待
    • 条件变量
    • std::condition_variable notify_one wait
    • std::future 一次性时间
      • std::async 类似std::thread 不过最后阻塞的是future.get()
      • std::packaged_task<>
      • std::promise
  • future FP
  • 消息传递同步 wait().handle<>

###

c++内存模型与原子操作

  • 原子操作 store load read-modify-write
  • 同步操作与顺序
    • 内存顺序
    • 顺序一致与内存同步操作,代价。
    • 非顺序一致的memory-order 线程不闭合时间的顺序一致

ref

  1. github翻译地址:https://github.com/xiaoweiChen/CPP-Concurrency-In-Action-2ed-2019
  2. gitbook 在线阅读:https://legacy.gitbook.com/book/chenxiaowei/c-concurrency-in-action-second-edition-2019
  3. 本书源码下载地址:https://www.manning.com/downloads/1954
  4. 第一版github 翻译地址:https://github.com/xiaoweiChen/Cpp_Concurrency_In_Action

先谢指教。

Read More

聊聊永动机乐队演出

场地在sub-live, 场地有点破。高德地图上没有。走到一个破技校门口,下面有个小黑板写着往里走sub-live。我找了半天。路遇一个大哥看我手机上的票二维码,问我是不是也是看乐队演出的。我说是,他说这地儿也太难找了。退票妈的。

然后就陆续进场,鉴于自己的身高我就没有硬往前挤,站在第三排就能看见主唱。后面倒是来了几个女生,让她们往前面钻。

刚开场那会大概有五六十人的样子,后续能有百十人。主场说人还可以,不多也不少,不至于尴尬。

然后吐槽了哈尔滨的春天,来了首残酷春日。说实话日式摇滚我还真是不太喜欢。比较直接的几首歌感觉还行。前面的十年所见,北海,这几首老歌都很棒。然后唱了丙申歌合战和京城武斗会的大部分歌曲(除了永不再亮的头像)。唱了玻璃杯+天赐的毒药。这段太牛逼了。我是特别喜欢。然后是后面《永动机》碟的内容+我知道那天晚上你又没睡着。我嗓子都喊哑了。

我之前看主唱列的巡演歌单中没有这么多。应该算是返场吧。多唱了很多,唱到九点十分然后卖纪念册和cd,和衣服(衣服三百,鉴于我自己的购买力就放弃了)

中间主唱也说了点段子。说来哈尔滨也不是“重新认识一下”(本次巡演的主题)而是第一次见。说这些嗑也就给现场的朋友了,音乐电台也不做了。感谢现场的观众养活这个乐队。还说自己牙口不好,这个话题跳的太快。没法接。一度尬笑。

主唱有点否认自己。怕这波歌迷喜欢之前永动机那种“古风”“中国风”之类的刻板印象(应该指的是贝贝,天地一家春这些)。着急的想摆脱这个形象。其实《皇城饮恨录》《战歌》这两张碟的还是很牛逼的,也不是说喜欢这个就不能接收日式摇滚这种。主唱老是否定自己让我很同情。我也是每年都在否定自己觉得自己傻逼。觉得自己过去也没啥闪光点。过去一直存在。否定没有意思。

售后签只买了纪念册(cd用不到衣服也用不到)。纪念册买了也有点后悔,封面说内容十分那啥。我还在猜毁三观的内容能是啥,结果打开看是主唱女装。我日。被人翻到我不就gay了。让主唱签名,忘记让他多签俩字儿了。当时有点紧张。

年轻人可真他妈能蹦啊。我在后面摇头就很累了。这帮年轻人又是蹦又是跳又是摆手的。我旁边有个哥们摇摇晃晃带个耳机像个女生,一说话又是个小男生的动静。让旁边摇头的我十分恍惚。

以上就是演唱会。我看了演唱会返图,还挺好看的。场地的人说其他地方的livehouse也一样破。我没有见识过就不下定论了。

这个乐队后面解散了。我不再去livehouse了。给年轻的乐队一些机会

Read More

Introduction to Modern C++ Techniques

cppnow2012 Michael Caisse Introduction to Modern C++ Techniques

这个讲的是一些小组件,部分在AA的书中介绍过

Functor , RAII, Concepts 这些老生常谈不说了

Policy Class

作者列举了AA书中的NullPointer的例子,其实这个Policy Class更像type traits中的tag dispatch手法。或者说,Concept约束。没啥好讲的

CRTP

静多态

写了个cloneable

template <typename Derived>
struct cloneable{
  Derived* clone() const{
  return new Derived(static_cast<Derived const&>(*this));
  }
};
struct bar : clonealbe<bar>{...};

还有一个经典的例子是enable_shared_from_this, 作为一个观测者(weak_ptr),需要shared的时候抛出去shared_ptr

template <class T>
class enable_shared_from_this{
mutable weak_ptr<T> weak_this_;
public:
    shared_ptr<T> shared_from_this(){
        shared_ptr<T> p(weak_this_);
        return p;
    }
    ...
};

总之这是公共接口静多态实现的一个方法,可以把子类的this拿过来霍霍,比如

template<class Derived>
struct enable_profit{
    float profit(){
        Derived const & d = static_cast<Derived const &>(*this);
        return 42*(d.output)/d.input;
    }
};
struct department : enable_profit<department>}{
    int output;
    int input;
};

Type Traits 没啥好讲的

Tag Dispatching or SFINAE

1556623095110

这个典型就是enable_if

看完感觉就是在复习。

ref

Read More

^