Boost学习笔记

记录boost学习心得。部分已经并入标准的库将以标准代替。

智能指针

shared_ptr通过use_count获得引用计数。引用计数仅对拥有相同祖先的shared_ptr生效。如si2由si1拷贝而得;si3由si1或si2的weak_ptr::lock而得,那么他们的use_count是同步的。si4是直接建立在同一个原始指针上的,那么前三个shared_ptr仍然无法感知si4。这会导致两组shared_ptr可能产生内存溢出。举例如下:

 #include <stdio.h>
 #include <memory>

 int main(int, char**) {
     auto i = new int(1);  // 原始指针
     auto si = std::shared_ptr<int>(i);  // family1: root si
     printf("shared count[si:%lu]\n", si.use_count());

     auto si2 = si;  // family1: si2 is "children" of si
     printf("shared count[si:%lu, si2:%lu]\n", si.use_count(), si2.use_count());

     auto si3 = std::shared_ptr<int>(i);  // family2: root si3
     printf("shared count[si:%lu, si2:%lu, si3:%lu]\n", si.use_count(), si2.use_count(), si3.use_count());
     printf("unique flag [si:%u, si2:%u, si3:%u]\n", si.unique(), si2.unique(), si3.unique());

     std::weak_ptr<int> wi = si3;  // weak_ptr from si3
     auto si4 = wi.lock();  // si4 is generated from si3's weak_ptr
     printf("shared count[si:%lu, si2:%lu, si3:%lu, si4:%lu]\n", si.use_count(), si2.use_count(), si3.use_count(), si4.use_count());
     printf("unique flag [si:%u, si2:%u, si3:%u, si4:%u]\n", si.unique(), si2.unique(), si3.unique(), si4.unique());

     return 0;
 }

如上代码产生的输出:

shared count[si:1]
shared count[si:2, si2:2]
shared count[si:2, si2:2, si3:1]
unique flag [si:0, si2:0, si3:1]
shared count[si:2, si2:2, si3:2, si4:2]
unique flag [si:0, si2:0, si3:0, si4:0]

侵入式指针

之前一直没太注意了解intrusive_ptr,读文档和代码后发现其实相对更为偏向RAII的语义。intrusive_ptr要求其托管的类型支持AddRef()Release()方法,并且要求其语义分别是为对象新增引用为对象减少引用或相对应的语义。

基于此,intrusive_ptr得以对com对象等需要用户自行管理引用计数的类型方便地托管,甚至对于流、句柄等需要RAII的对象进行管理。

实际上,intrusive_ptr管理引用技术的方式与RAII还是有区别的。RAII重在资源的分配与回收,而不在引用计数。只是在实践中的大量场景下,他们的操作有相同之处而已。

指针容器

补充一下指针容器ptr_vector(另外还有xxx_set等对应于原始容器的指针容器)。为什么产生指针容器?根据boost库文档和大家的使用经验来看,总结为三点:
* 语义不同。container< ptr< T > >表达了拥有own权限的对象可被拷贝、存储;而ptr_container< T >表达的是一个拥有own权限的容器本身(学院派?实际场景中的确区分不是特别大);
* 方便性。显然ptr_container的方式更简单一些,相比与container<std::unique_ptr<T>>ptr_container<T>语法简练不啰嗦;
* 效率。在不做优化的前提下,任何智能指针是无法避免overhead的。此时ptr_container可以帮助我们绕过一层实现,直接从container层级支持指针的ownership。

综上所述,在需要存储堆对象的指针时,ptr_container是一个较优越的选择。_当然,由于开启O2之后unique_ptr的开销基本不存在,考虑到编码一致或依赖简单的因素,container<unique_ptr<T>>也是个好选择。

函数式编程

boost的函数式编程基础库包含了bind、function、ref和lambda,分别解决函数绑定、函数指针、函数式编程中的传引用问题和lambda表达式。这个库的各项功能目前已经完全加入C++11基准库(bind、function、ref)或从语言级别实现(bind的可变参数版本和lambda表达式)。值得一说的是,boost的lambda表达式实现非常地简洁优美,除核心语法支持缘故导致部分单目操作符如std::endl无法方便使用之外,仍然是值得学习的一个库。

由于上述功能均已合并至C++11,在此仅说明一些不同之处或需要注意的地方。

函数式编程传引用解决方案 – Boost.Ref

boost的bind、function中默认采用传值方式,这种设计是可以理解的。但这造成类类型作为参数时的开销。boost::ref和boost::cref(在标准库中为std::ref和std::cref)即用于解决这个问题之一——非占位符参数的引用传递。我们以如下Integer类型作为示例:

#include <stdio.h>

class Integer {
 public:
     Integer(int val) : _val(val) {
         printf("constructed\n");  // 每次构造、拷贝、赋值、移动均打印已trace参数传递过程
     }

     Integer(const Integer& other) {
         int orig = _val;
         _val = other._val;
         printf("copy constructed:%d -> %d\n", orig, _val);
     }
     // 移动构造暂时注释
     //    Integer(Integer && other) {
     //        int orig = _val;
     //        _val = other._val;
     //        other._val = 0;
     //        printf("move constructed:%d -> %d\n", orig, _val);
     //    }

     Integer& operator=(const Integer& other) {
         int orig = _val;
         if (this != &other) {
             _val = other._val;
         }
         printf("copy operated:%d -> %d\n", orig, _val);
         return *this;
     }
     // 移动赋值暂时注释
     //    Integer & operator = (Integer && other) {
     //        int orig = _val;
     //        if (this != &other) {
     //            _val = other._val;
     //            other._val = 0;
     //        }
     //
     //        printf("move operated:%d -> %d\n", orig, _val);
     //        return *this;
     //    }

     bool operator<(const Integer& other) const { return _val < other._val; }

 private:
     int _val = 999;
 };

如下代码测试了使用和不使用std::ref的区别:

 #include <functional>
 // 第三个参数仅用于trace bind和sort过程中产生的值传递开销
 bool compare(const Integer& a, const Integer& b, const Integer&) {
     return a < b;
 }

 std::vector<Integer> vec{ 4, 2, 5 };

 int main(int, char**) {
     using namespace std::placeholders;
     printf("----------------\n");
     printf("in main function\n");

     Integer other(123);    // 创建用于值传递开销测试的实例other
     auto foo = std::bind(compare, _2, _1, other);  // 未使用ref,值传递
     auto bar = std::bind(compare, _2, _1, std::ref(other));    // 使用ref,传引用

     printf("----------------\n");
     printf("sort test foo:\n");
     std::sort(std::begin(vec), std::end(vec), foo);    // 值传递版本会有大量bind产生的拷贝
     printf("----------------\n");
     printf("sort test foo:\n");
     std::sort(std::begin(vec), std::end(vec), bar);    // 引用传递版本仅sort本身产生拷贝

     return 0;
 }

最终如上代码打印的输出如下。可以看到使用ref之后,other的拷贝操作被全部绕过,大大降低了开销。这就是ref的作用了。

constructed                     # 数组元素初始化
constructed
constructed
copy constructed:999 -> 4       # 由于移动构造函数被注释掉,元素被拷贝到数组(vector)中
copy constructed:999 -> 2
copy constructed:999 -> 5
----------------
in main function
constructed                     # other实例初始化的输出
copy constructed:999 -> 123     # 这里就是bind的值传递之处了。std::bind copy数值暂存,产生了拷贝构造
----------------
sort test foo:
copy constructed:999 -> 123     # 每次compare调用,都会新建一个bind实例,将other暂存,产生开销
copy constructed:999 -> 123
copy constructed:999 -> 123
copy constructed:999 -> 123
copy constructed:999 -> 123
copy constructed:999 -> 123
copy constructed:999 -> 123
copy constructed:999 -> 123
copy constructed:999 -> 123
copy constructed:999 -> 2       # sort函数创建临时变量带来的开销,可采用移动构造去除。
copy operated:2 -> 2            # sort函数在待排序元素和临时变量之间调整产生的开销,同上。
copy constructed:999 -> 5
copy operated:5 -> 2
copy operated:2 -> 4
----------------
sort test bar:
copy constructed:999 -> 4       # sort函数临时变量
copy operated:4 -> 4            # sort操作
copy constructed:999 -> 2
copy operated:2 -> 2

然而我们看到sort test bar版本的sort仍然产生了排序导致的开销。这是因为我们的Integer的move constructor和move operator都被注释掉了。C++11的库函数均优先采用移动语义以便降低开销。这就要求我们创建新类型时根据需求实现移动语义,以便控制预期的开销。将Integer定义中的对应注释取消掉后,重新编译执行,得到的输出如下。

...     # 此前的输出没有什么变化,略过不提
----------------
sort test foo:
copy constructed:999 -> 123 # bind自然不能使用移动语义(否则会破坏原始变量),仍然只能拷贝
copy constructed:999 -> 123 # 同上
move constructed:999 -> 123 # sort过程就可以采用移动语义了,后续没有太深究
move constructed:999 -> 123
copy constructed:999 -> 123
copy constructed:999 -> 123
copy constructed:999 -> 123
copy constructed:999 -> 123
move constructed:999 -> 123
move constructed:999 -> 2
move operated:0 -> 2    # 排序过程中的移动操作
move constructed:999 -> 5
move operated:0 -> 2
move operated:0 -> 4
move operated:0 -> 5
----------------
sort test bar:  # ref和move语义加持后,该版本完全去除了拷贝操作
move constructed:999 -> 4
move operated:0 -> 4
move constructed:999 -> 2
move operated:0 -> 2

如上,借函数式编程一章大概记录了bind和ref的用法,并顺道布道移动语义大法。然而需要注意的是,移动语义的加入使得类型定义变得复杂。尤其是在深层的类型组合、嵌套中,可能需要自顶向下review移动语义的正确性,需要谨慎使用。

至于function和lambda,由于前者差别不大而后者可以单开篇幅记录,因此不在此赘述。

Nexus 6p的一些攻略

网络连通性设置

Android 5.0开始,会通过NetworkMonitorwww.google.com/generate_204(或client[N].google.com/generate_204,视系统而不同,但均为google域服务)发送请求,判断网络的连通性。然而GFW所致,国内无法直接访问google相关的站点,因此我们需要修改一下默认的服务URL:

adb shell settings put global captive_portal_https_url "https://httpbin.org/status/204"
  • 注意,不同的major版本下的命令不同,可以google/baidu一下;
  • 顺便记录一下httpbin.org,可以模拟http的各种method/status等,非常方便;
  • 当然国内各个厂商如小米以及一些社区如ve2x也提供了支持,可以作为备选。实在不行自己做一个也可以。

执法缺位下的民众自决

写这一小段文字主要是近期异烟肼及高铁霸座有感。真心短,纯粹抒发胸臆,可能还不如一些帖子长——我毕竟不是善于舞文弄墨的博主:)

例如那些遍撒异烟肼的 人肉无赖男的,招数虽然不是最佳的,可是都是被逼无奈的,虽非最佳,也算次佳了。
——引自网上的一篇帖子(考虑到隐私和安全,没有注明出处和作者,如认为被侵权请给我留言,即刻删除)

异烟肼在这个话题中,其本质是一种约束和惩罚措施(的结合体),正如杀人偿命、损财赔偿等法律规定。可是,难道我们的法律经过这么大量参考、严格制定和多年验证,仍然有这么大的空白需要异烟肼们去填补么?不排除个例的情况下,大抵不是。异烟肼作为一种约束和惩罚措施的出现,其最主要价值不在本身,而在执行——在执法缺位的情况下,人民只能通过有限的方式进行自我保护以至生存环境的维护。这种方法无奈、原始、低效、易于有失公平,但却是无奈的选择。

人们抨击或追捧异烟肼及其背后针尖对麦芒的两方势力,分析并尝试缓解相关行为带来的正负面影响,并呼吁立法者立法、执法者作为。然而,在这一自发生长的工具面前,这一呼吁恐怕和需要响应这一呼吁的“有关部门”一样苍白无力——如果不是相关方不作为,这种新生事物怎能得到其生长的土壤?一个完美的法治环境无法杜绝此类极端行为(毕竟林子大了什么鸟都有),但却可以通过自我的完善杜绝它成为一种“现象”。

“没有异烟肼的世界”会是什么样?这里就不得不提到前段时间的高铁霸座事件。它将执法缺位造成的另一种后果摆在我们眼前:在人们找到一种自发而可行的自治手段之前,监管的缺位带来的就是无序。这对应于事件的前半段。事件的后半段中,霸座的小哥们迫于舆论压力而发表录像道歉。事情看起来不像完美解决,但总算是有了个交代。“总算是有了个交代”,这不正是人们所追求的最低限解决方案么?而给出这份及格答卷的,正是另一种“异烟肼”:舆论监督。我也会想:异烟肼怎么能跟舆论监督相提并论?到底能不能这样类比?不过加上一些定语,给出一些假设之后,我认为是可以的:舆论监督——在民众自治的环境下——与异烟肼起到了相似的效果。所不同者,前者自诞生开始就经过了多方检验修正并得到成长。虽然它在我们的国家仍然非常羸弱,但却是人们面对强大对手时最为行之有效且性价比最高的武器(我猜它的前身应该就是街坊邻里间口口相传的“吐沫星子”吧)。

异烟肼,这是人民看不到希望的情况下给出的方案;而舆论监督正是成长后的异烟肼。在一个拥有足够体量的社会中,任何缺位都会有人、事、物快速而自然地填补。这些替代品从最初的粗制滥造、顾此失彼默默地发展,逐步壮大,并终将静静地替代缺位者。但愿正在被替代的人们有保存自身、发挥作用、确保不被替代的自觉。

Homebrew灵活自定义Formulae

Homebrew是Mac上经常使用的包管理工具,类似还有pkgsrc等。但前者的sudo-free解放了我对改动mac os这个闭源系统的恐惧,在安装喜爱的软件的同时不必再担心系统罢工。

然而相对于各个Linux/BSD发行版的包管理工具,homebrew在版本管理和依赖冲突管理上的支持还是比较的弱。根据他们社区的交流来看,这两点还在不断地优化中。在这之前,如果遇到版本冲突的问题,我们该怎么办呢?版本冲突一般是如下两种情况:
* 由于tensorflow不支持3.7版本的python,因此我希望将其对python的依赖改为3.6.5版本。无奈软件源已经更新为3.7,有什么办法对python降级?
* 我通过一些hack or offical的方法解决了上述问题,终于可以使用tensorflow。然而某个foo app依赖的是python 3.7,如何进行multi slot安装呢。

对于第一种问题,解决方法为:添加自己的软件源,迁移并修改希望改动其安装配置的软件包的rb文件。这里用到的命令是brew tap user/repo。以tensorflow为例,过程如下:
1. 将Homebrew-core的github repo fork一个,并从Formulae目录中删除除tensorflow和python之外的rb描述文件,注意其它目录也有些相关的描述/文件也最好清空;
1. 提交更改,并在本地执行brew tap my_git_username/my_repo,以便将其加入homebrew的软件源;
1. 通过vim /usr/local/Homebrew/Library/Taps/my_git_username/my_repobrew edit python修改python.rb,并将其sha256url两个字段修改为自己期望的版本的hash和url;
1. 同样修改自己源中tensorflow.rb的deppends_on字段,将其依赖的python改为my_git_username/my_repo
1. 提交更改。

上述操作后,即可通过brew install my_git_username/my_repo/tensorflow来自定义安装tensorflow了。

对于第二种问题,解决方法更简单一些。Foo app依赖的python是在默认源中的。在我们直接安装Foo之后,Cellar目录下即有了两个版本的python,分别是3.6和3.7。之后就是版本选择的问题了,采用如下命令:

brew switch python 3.7

即可在版本之间按需切换。

本文的重点是通过自定义软件源的方式绕过版本管理的问题。其实homebrew本来是有一个homebrew/versions源来支持多版本管理的,可惜无福使用;而且听说其对python的支持只是在major版本之间,minor是不支持的,还是比较弱。还是希望homebrew团队多多更新,天天向上,多多兼容,造福我等小白。

顺带mark版本级别的说明(以python 3.6.5为例)
* major 主版本
* minor 子版本
* patch 小迭代

C++的两段名称查找[two-phrase name lookup]

原理

C++在涉及模板的特化及模板类的实例化时,通过两段查找来确定其定义中的每个命名。顺序大致为:
1. 第一步是模板的特化。此时只需要进行名称替换,也即将模板的形参以实参代替。此时编译器查找所有non-dependent命名,也即非成员函数或变量,并替换其类型或参数类型;
1. 第二步是模板类的实例化。在这之前编译器会进行常规的基类查找,并将所有dependent命名进行解析,也即成员函数或变量的查找(要么在本类的定义中找到,要么在基类或虚表中找到)。

详细原理参见文章:The Dreaded Two-Phase Name Lookup

这种两端查找对带继承的模板类开发有很大影响。有时我们会发现如下代码的错误:

template <typename T>
class Base {
    void foo1();
    void foo2();
};

template <typename T>
class Derived : public Base<T> {
    virtual void foo1();
    void bar() {
        foo1();         // ok
        foo2();         // error
        this->foo1();   // ok
        this->foo2();   // ok
    }
};

int main(...) {
    Derived<int>().bar();   // error occurs here
}

直接调用foo2产生的错误如下:

foo2();

/path/to/def.cpp:427:55: note: declarations in dependent base ‘Derived<int>’ are not found by unqualified lookup
/path/to/def.cpp:427:55: note: use ‘this->foo2’ instead

上述的错误即源于这个two-phrase name lookup。编译器在编译main函数的唯一一行代码时,首先特化Derived的模板,此时未在其定义内部找到foo2函数,故抛出错误。好在编译器给了友好的提示,我们将其改为this-foo2()之后,即编译通过。这么做的主要变化是将foo2这个命名从non-dependent弱化为dependent,从而延后它的解析。

不使用while的循环(setjmp的用法)

setjmplongjmp是ANSI C程序支持/控制库中的一对函数,用于保存env与返回。setjmp将当前env保存在一个jmp_buf中;longjmp返回后的env则从中取出,完全相同。这是一些有栈协程实现的基础。

setjmp和longjmp有一些有趣的应用,如替代while实现循环。如下为示例代码,来自cppreference

#include <iostream>
#include <csetjmp>

std::jmp_buf jump_buffer;

[[noreturn]] void a(int count) 
{
    std::cout << "a(" << count << ") called\n";
    std::longjmp(jump_buffer, count+1);  // setjump() 将返回 count+1
}

int main()
{
    volatile int count = 0; // 在 setjmp 作用域中被修改的局部变量必须是 volatile
    if (setjmp(jump_buffer) != 9) { // 在一个 if 内不等于常量表达式
        a(++count);  // 这会导致 setjmp() 退出
    }
}

程序是经过验证可以执行的,执行结果与cppreference给出的一致。

a(1) called
a(2) called
a(3) called
a(4) called
a(5) called
a(6) called
a(7) called
a(8) called

后续再过协程的时候还要返回来看看,在此mark。

Python map方法真心好用

Python下有个map方法,结合一些单目操作符或单参数的函数对iterable实例进行操作真的非常好用。

  1. 如下代码将通过localtime获得的时间信息数组转换为字符串数组:
','.join(map(str, time.localtime()))
  1. 如下进一步控制了转换后字符串的格式,注意分隔符为空,仅转换前六位元素(年月日时分秒):
''.join(map('{0>2d}'.format, time.localtime()[0,6])) # 好吧我承认可以使用strftime
一些备注
  • 新版string format方法的确方便了很多,可以灵活地控制输出格式。如上{0>2d}表示对应位置的整型值至少显示两位,否则左侧补零;
  • 如果希望采用双目或多目运算符,抑或使用多参数的方法,猜测应该需要采用placeholder,或者传入元素为同维度元组的数组。这一点纯猜,没有验证;
  • map的实现应该很简单,但是大大地方便了用户。至于其实现,其实类似stl alogrithm里的函数了,如std::transform或std::for_each等,均可以实现这个效果。不过map这个字眼老实说让用户非常直观地猜到它的意图。能及时跟上概念的演进也是新语言(相对c/c++)的优势吧。

重开博客

自打百度空间关闭以来,大量的博客文章都被下掉。恢复博客这件事,老实说技术上还是可能的,但是繁琐的工作让我从来没有想起要做这件事。近两年每每觉得还是需要个博客来记录自己的所想所做,这次算是成行了。

之前就开过一次wp,但是后来不知出了点什么问题导致打不开。这次重开倒是快了很多(因为之前的经验形成了意识流?),不过还是记录一下期间发生的问题,以备后用。

  1. httpd相关:

1) 需要配置虚拟主机来确保多站区分。具体参见httpd/conf.d/下的配置文件。上着班就不写太详细了;

       2) 站点目录需要设置为httpd专用的用户名,以便httpd拥有写权限。这可以方便wordpress的配置和升级(否则还需要为它额外配置ftp,更麻烦)  。这其实是建站基本知识,知道是知道,只是不记得细节了。

  1. mysql相关:

1) 注意grant privilege和set password所带的indentified by ‘xxx’是等效的。不要在设置密码后的grant操作中将密码覆盖了;

2) 对用户授权时,需要注意grant操作是要指定主机的,同时localhost和%需要分别设定,具体可以参见user表,从中可以看出该表的主键user id与<user, host>其实是一一对应的;

3) 授权完成后记得flush privileges,否则授权无效。

  1. wordpress相关:

如果前面几步都做到了,那么wordpress的安装可以说太简单了。只需要注意两点以便使用方便:

1) 设置用户名时设置为自己喜欢的名字,因为建立之后不能在前台修改。如果后悔了只能跑到后台数据库去裸改,关联的表就多了。好在wordpress好像并没有给这些表建立约束,改起来也没那么麻烦:P

2) 在“设置->固定链接”这个选项中,按照日期+文章名称定位的链接风格为人所喜,但经常会设置失败。查证了一下,需要做的有这么三项:

a. 确认mod_rewrite开启,因为是通过链接重写实现的。这一点基本没有问题;

b. 让httpd的虚拟主机配置项支持两个Option:Includes和FollowSymLinks;

c. 注意你的链接本身,http://hostname/year/month/day/postname是可以做到的,但是http://hostname/index.php/year/month/day/postname却不行;

*d. 补充一点。官方主题总有一个页尾:Proudly powered by WordPress(中文翻译包中叫做“自豪地使用WordPress”)。作为中国人,我选择了中文翻译包,然后为了去掉这东西搜了半天(上面这几个)中文字——搜得到就见鬼了。。到站点目录搜上面的英文吧,搜到的footer直接删除即可。老实说,用人家东西这样做貌似不是很好,不过我实在不愿意作活广告,致谢不也得自愿么~

以上。第一篇文章写在什么工具都还没有的环境下,后面计划慢慢把工具插件之类慢慢建立起来,首先还是赶紧装个markdown插件吧~