Smiley face

我的征尘是星辰大海。。。

The dirt and dust from my pilgrimage forms oceans of stars...

-------当记忆的篇章变得零碎,当追忆的图片变得模糊,我们只能求助于数字存储的永恒的回忆

作者:黄教授

二〇二〇


一月五日等待变化等待机会

我真的是老糊涂了,service运行过程中我去删改可执行文件却一直不见有变化,还有这样的白痴吗?另一个简单的问题是关于c++里的string literal到底是什么类型这种初级问题,我始终有模糊的意识,难道不是const char*吗?我却始终认为是char*const。似乎大多数的c程序员也是这个看法?

一月七日等待变化等待机会

我尝试克隆这个《英雄无敌3》的最全的百科全书网站,这样做是很不道德的,不过我保证这个纯粹是个人收藏,希望能够理解。

一月九日等待变化等待机会

说起来惭愧,我居然也不知道如何编译curl以及这个重要的nss项目。google到这位大侠的文章,值得收藏
这里我摘录一些要点,首先是关于nss的编译,大侠这里有解说

NSS 是什么

NSS 是开源软件,和 OpenSSL 一样,是一个底层密码学库,包括 TLS 实现。NSS 并不是完全由 Mozilla 开发出来的,很多公司(包括 Google)和个人都贡献了代码,只是 Mozilla 提供了一些基础设施(比如代码仓库、bug 跟踪系统、邮件组、讨论组)。

NSS 是跨平台的,很多产品都使用了NSS 密码库,包括:

NSS 支持的密码学算法标准和应用如下:

NSS 提供了完整的软件开发包,包括密码库、API、命令行工具、文档集(API references、man 帮助、示例代码)。NSS 3.14版本开始,升级到 GPL 兼容的 MPL 2.0 许可证。

NSS 符合 FIPS 140(1&2)标准,FIPS 标准是美国政府定义的一种标准,主要是数据编码的标准。NSS 库也通过了 NISCC TLS/SSL 和 S/MIME 的测试(160万输入数据的测试),NISCC 是英国政府提出的安全标准。

在使用 NSS 之前,Mozilla 建议你应该具备一些知识,否则会很晕乎,知识列表如下:

这里摘录了关于编译curl的部分,我感到惭愧的是我居然一直有印象我曾经编译过curl,还debug过一个小问题,只是完全没有印象关于nss有这么多的小trick,可能我是直接下载了ubuntu的libcurl-nss动态库吧? 其实作者应该是对于c/c++编译不熟悉的,所以,我就不详细摘录具体的命令了,一方面当然也是因为www.jianshu.com这个网站有些古怪其中的html代码拷贝有些问题,最最主要的就是一个在configure --with-nss=这里要使用之前编译nss的时候与之同目录下的dist下产生的编译器名字的目录,比如我的就是Linux4.18_x86_64_cc_glibc_PTH_64_OPT.OBJ,它下面有bin/lib/include三元素。而之前我的错误意识是libcurl-nss.so.4是一个独立的库,现在看起来它是在编译curl的时候链接nss产生的一个之间产物,这个可以通过下载Ubuntu的源码看出来因为apt-get source libcurl4-nss-dev实际上下载的就是curl-7.58.0的源代码。(不过这个理由太牵强了,我需要更多的证据)

一月十日等待变化等待机会

总是有很多的麻烦阻挡我干一些真正有意义的事情,比如,现在我的s3同步上传工具遇到了文件名没有扩展名于是不能正确设定aws s3的Mime的问题,我想应用我之前实现的小工具libmagic的链接,但是重新编译我的那个工具实在是一个繁琐的过程

#!/bin/bash
root_dir=$PWD

init()
{	
	if [[ ! -z "$1" ]]
	then
		SRC_URL="$1"
	else
		SRC_URL=http://www.staroceans.org/
	fi
	[ ! -d "libS3" ] && git clone ${SRC_URL}myprojects/libs3-2.0/repo.git libS3
	[ ! -d "curl" ] && git clone ${SRC_URL}myprojects/curl-7.58.0/repo.git curl
	[ ! -d "myLibS3" ] && git clone ${SRC_URL}myprojects/myLibS3/repo.git  myLibS3
	[ ! -d "openssl" ] && git clone ${SRC_URL}myprojects/openssl/repo.git openssl
	[ ! -d "magic" ] && git clone ${SRC_URL}myprojects/file-5.32/repo.git magic
	[ ! -d "libxml2" ] && git clone ${SRC_URL}myprojects/libxml2/repo.git libxml2
	[ ! -d "zlib" ] && git clone ${SRC_URL}myprojects/zlib/repo.git zlib
}

build_openssl()
{
	cd ${root_dir}/openssl
	./Configure --prefix=${root_dir}/openssl-install no-shared linux-x86_64
	make
	make install
}

build_curl()
{
	[ ! -d ${root_dir}/openssl-install ] && echo "curl depends on openssl. build_openssl first" && exit
	cd ${root_dir}/curl
	./configure --prefix=${root_dir}/curl-install  --without-nss --with-ssl=${root_dir}/openssl-install \
	 --enable-libcurl-option --enable-shared=no --without-libidn2
	make
	make install
}

build_libxml2()
{
	[ ! -d ${root_dir}/zlib-install ] && echo "libxml2 depends on zlib. build_zlib first" && exit
	cd ${root_dir}/libxml2
	./configure --prefix=${root_dir}/libxml2-install  --with-zlib=${root_dir}/zlib-install --enable-static \
		--disable-shared
	make
	make install
}

build_libs3()
{
	[ ! -d ${root_dir}/openssl-install ] || [ ! -d ${root_dir}/curl-install ] || [ ! -d ${root_dir}/libxml2-install ] \
		&& echo "curl depends on openssl,curl,libxml2 build_openssl build_curl build_libxml2 first" && exit
	cd ${root_dir}/libS3
	make CURL_LIBS="-L${root_dir}/curl-install/lib -lcurl" \
		CURL_CFLAGS="-I${root_dir}/curl-install/include" \
	   OPENSSL_CFLAGS="-I${root_dir}/openssl-install/include" \
	   OPENSSL_LIBS="-L${root_dir}/openssl-install/lib -lssl -lcrypto" \
	   LIBXML2_CFLAGS="-I${root_dir}/libxml2-install/include/libxml2" \
	   LIBXML2_LIBS="-L${root_dir}/libxml2-install/lib -lxml2" static_only
}

build_zlib()
{
	cd ${root_dir}/zlib
	./configure --prefix=${root_dir}/zlib-install --static --64 -fPIC
	make
	make install	
}

build_magic()
{
	cd ${root_dir}/magic
	aclocal
	autoconf
	./configure --prefix=${root_dir}/magic-install --enable-static --disable-shared
	make
	make install
}

build_mylibs3()
{
	[ ! -f ${root_dir}/libS3//build/lib/libs3.a ] && echo "myLibs3 depends on libS3, build_libs3 first" && exit
	cd ${root_dir}/myLibS3
	make
}

clean_all()
{
	cd ${root_dir}
	rm -fr zlib-install libxml2-install openssl-install curl-install magic-install
	make -C libS3 clean
	make -C libxml2 clean
	make -C zlib clean
	make -C openssl clean
	make -C curl clean
	make -C myLibS3 clean
}

show_help()
{
	echo "usage: init              setup source"
	echo "usage: build_xxx         build xxx"
}

if [ $# -lt 1 ]; then
	echo "number of param: $#"
        show_help;
        exit
fi

case $1 in
init) 
	init $2;
	;;
build_openssl)
	build_openssl;
	;;
build_curl)
	build_curl;
	;;
build_libs3)
	build_libs3;
	;;
build_magic)
	build_magic;
	;;
build_mylibs3)
	build_mylibs3;
	;;
build_zlib)
	build_zlib;
	;;
build_libxml2)
	build_libxml2;
	;;
show_help)
	show_help;
	;;
clean_all)
	clean_all;
	;;	
*)
	echo "don't understand your parameter";
	show_help;	
esac	


一月十三日等待变化等待机会

一个愚蠢的手误可以让你想断肠子,换句话说就是一个傻瓜的错误可以让千百个聪明人想疯了也找不出为什么,这就是为什么人们常常说不要和愚蠢的人争辩,因为这本身就是愚蠢。而我恰恰是这么实践中。多么轻易的一个疏忽让我后悔至今。比如我要为我的myLibS3写一个Makefile

ROOTDIR=$(PWD)

SRC_FILES := $(wildcard *.cpp)
OBJ_FILES := $(patsubst %.cpp,%.o,$(SRC_FILES))

CC=g++
CFLAGS=-Wall -I${ROOTDIR}/../curl-install/include -I${ROOTDIR}/../openssl-install/include \
  -I${ROOTDIR}/../magic-install/include
  
LDFLAGS=-std=c++98 -L${ROOTDIR}/../openssl-install/lib -lssl -lcrypto -lrt \
 -L${ROOTDIR}/../libS3/build/lib -ls3 -L${ROOTDIR}/../curl-install/lib -lcurl -lxml2 \
 -L${ROOTDIR}/../magic-install/lib -lmagic -lz -lpthread -ldl
 
.cpp.o:
	$(CC) -c $(CFLAGS) $< -o $@
    
all: ${OBJ_FILES}
	${CC}  ${OBJ_FILES} ${LDFLAGS} -o myLibS3
	
clean:
	rm -f *.o	

一月十五日等待变化等待机会

这里是一个小的不能再小的细节,就是在webservice一侧,你需要使用realpath去过滤symlink,因为毫无意义的在contenttype返回symbol link是会误导浏览器的,打开文件肯定是文件本身,那么我使用libmagic去获得mime当然要文件本身了。简单的说从来不会有服务器返回symlink。早上进一步使用S3_head_object获得contentType来和本地文件的mime来对比。下一步再考虑更新不符的文件。不过我发现一个小问题,就是file/magic会把含有c/c++代码的Html文件的mime看作txt/c这个很出乎我的预料。我有空再看看mime的定义文件看能不能改进?另一个恼人的问题是我才发现firefox之所以总是下载成临时文件而不是打开那个html文件可能是出于安全的因素。这个以后再解决了。

一月十六日等待变化等待机会

libS3使用libcurl那么disable debug output就是这个地方: curl_easy_setopt_safe(CURLOPT_VERBOSE, 0);
关于puppylinux的机制我始终不是很理解,除了他的文件系统自成一套,还有就是他的内核。这里先摘录一个说明:
tahrpup allows easy changing of kernels with a few caveats.

pick a kernel, download, unzip, copy the vmlinuz & zdrv.sfs
over the installed ones and reboot.

CAVEATS: 
if you've installed any kernel version dependent drivers, 
such as your graphic drivers you won't be able to use the
same savefile so boot with pfix=ram.
even if you haven't installed any drivers, on some setups
you may still need to boot pfix=ram and create a new save.

if you want to use these kernels in any other puppy apart 
from tahrpup-6.0, you'll need to rename the zdrv.sfs and do 
the following in a terminal...

tail -c32 /path/to/puppy.sfs

this will produce an idstring, for example tahrpup-5.8.4
produces this t141012145113ZZZZ5.8.4XXXXXXXXXX which i'll
use just as an example, you will need to use your own
output.

echo -n t141012145113ZZZZ5.8.4XXXXXXXXXX >> /path/to/vmlinuz
echo -n t141012145113ZZZZ5.8.4XXXXXXXXXX >> /path/to/zdrv.sfs

so the vmlinuz & zdrv.sfs should match the puppy.sfs. you can
use the 'tail' command above to check each one.

一月十六日等待变化等待机会

gnome-tweaks 我的无线网卡rtl8821ce之前下载的非主流驱动可能是有问题的于是我需要卸载之前安装的dkms驱动:
nick@nick-HP-Laptop:/lib/modules/5.3.0-26-generic$ dkms status
,, v5.2.5_1.26055.20180108.1, 5.3.0-26-generic, x86_64: installed
nick@nick-HP-Laptop:/lib/modules/5.3.0-26-generic$ sudo dkms remove rtl8821ce/v5.2.5_1.26055.20180108.1 --all
我始终为中文字体所困扰,甚至我一度怀疑是某些有台独倾向的谷歌或者微软里的台湾工程师在捣鬼,现在我倾向于怀疑这个是ubuntu的字体设置问题,当然我并不是完全排除了我的怀疑。这个帖子值得认真阅读与实验。其中在/etc/fonts/conf.avail和/etc/fonts/conf.d都有着一系列的配置文件,当然后者是前者的链接而已。其中有这个连接示范了如何更换默认字体是一个很好的帖子。找到了这个下载字体的网站

一月十八日等待变化等待机会


一月二十一日等待变化等待机会


一月二十七日等待变化等待机会

福尔摩斯探案里常有回忆一些过去侦破的小的有趣的案子,我的回忆也是如此,只不过才发生几天记忆还新鲜。事情是这样子的: 我遇到一个错误:/usr/share/libdrm/amdgpu.ids: No such file or directory在这里有人说下载amdgpu驱动。再次强调一下我的笔记本的配置是这样子的。同时本地保存一个拷贝。我选择amdgpu-install发现它在动态编译内核dkms。然后结果是WARNING: amdgpu dkms failed for running kernel,我是否要使用amdgpu-install-pro试试看呢?

一月二十九日等待变化等待机会

我有若干个mp4文件可是要把它们连城一部电影可没有那么简单,这个语法出奇的复杂,我完全没有把握。这里有一个很好的解释: 这里的例子很难懂。以下是我的命令不确定是否正确。
ffmpeg -i movie.mp4 -i video_01_2.mp4 -i video_01_3.mp4 -i video_01_4.mp4 -i movie5.mp4 -filter_complex "[0:v]scale=720:480,setsar=1[in0];[1:v]scale=720:480,setsar=1[in1];  [2:v]scale=720:480,setsar=1[in2] ; [3:v]scale=720:480,setsar=1[in3]; [4:v]scale=720:480,setsar=1[in4]; [in0][0:a][in1][1:a][in2][2:a][in3][3:a][in4][4:a] concat=n=5:v=1:a=1 [v] [a]" -map "[v]" -map "[a]" output.mp4

一月三十日等待变化等待机会


二月二日等待变化等待机会


二月四日等待变化等待机会

我修改了我的myLibs3工具希望能够在本地存一个缓存以便节省带宽因为前两个月的账单超过了两百块,太贵了。修改的主要是这个脚本。其中有一个小小的问题,就是我编译的一系列静态库要链接-Wl,-Bstatic,可是这个有一个效应是随后所有的链接都成了静态链接,导致dl,ptrhead也变成了静态链接,这个会有问题比如-lgcc_s的问题,所以,我需要-Wl,-Bdynamic来结束静态开始动态。而且还有一个小小的细节我以为我早已滥熟于心的常识,就是链接默认是动态的,尤其是我自己的系统有安装了动态库那么即使你指定了链接的路径而你的静态库也不会得到链接,这就是需要-Wl,Bstatic的原因。

二月五日等待变化等待机会

对于helloworld之类的初级问题有时候我都是一个模糊的印象,我当然已经遇到过很多次类似的问题,但是似乎我总是似是而非的并没有真正的理解。人类难道就是这样子的愚蠢一定要自我毁灭一次才能实现世界大同?当然实现者是被毁灭之后的人类?ifstream的所谓的eofbit我当然明白需要我再次读超过eof才行,可是我的代码依旧是如此的搞笑。这个是最好的方式吧:
bool readS3Objects(const string& strFilename, S3ObjSet& list)
{
	ifstream in(strFilename, ifstream::in);
	if (in.is_open())
	{
		S3Obj obj;
		while (in >> obj.key && in >>  obj.size && in >> obj.time && in>>  obj.tag)
		{
			list.insert(obj);
		}
	}
	else
	{
		return false;
	}
	return true;
}

二月六日等待变化等待机会

你以为最简单的输入输出已经完全掌握了?真是令人汗颜啊,当你混合getline和operator>>会有什么问题呢?所以只能这样子因为还有whitespace没有被除去

string strKey, strTag;
int64_t nsize;
uint64_t ntime;
while (std::getline(in, strKey, '\n') && in >> nsize && in>>ntime &&in >> strTag)
{
	in.ignore(100000, '\n');
}
这里有关于boot/mbr的很好的文章

二月七日等待变化等待机会

摘抄这篇关于bios-MBR-bootloader的图文并茂的好文

The BIOS/MBR Boot Process

Regardless of the computer or operating system, standard (“IBM-compatible”) desktop PCs and laptops all power on and start up using one of two ways: the traditional BIOS-MBR method and the newer UEFI-GPT method, used by the latest versions of Windows, Linux, and Mac OS X on newer PCs, laptops, and tablets. This article summarizes the process by which traditional BIOS PCs load an operating system, covering the basics and details of the BIOS, MBR, and bootsector.

This article has been unofficially dubbed Everything you ever wanted to know about how your PC boots, part one.

Contents [hide]

Overview of the BIOS/MBR Boot Process

In the diagram below, the boot sequence for all standard computers and operating systems is shown:

MBR Boot Sequence

As you can see, the boot process is broken down into several major components, each of which is a completely-separate subsystem with many different options and variations. The implementations of each component can differ greatly depending on your hardware and operating system, but the rules they follow and the process by which they work are always the same.

Components of the Boot Process

 The BIOS

The BIOS is where hardware meets software for the first time, and where all the boot magic begins. The BIOS code is baked into the motherboard of your PC, usually stored on what is called an EEPROM 1 and is considerably hardware-specific. The BIOS is the lowest level of software that interfaces with the hardware as a whole,2 and is the interface by means of which the bootloader and operating system kernel can communicate with and control the hardware. Through standardized calls to the BIOS (“interrupts” in computer parlance), the operating system can trigger the BIOS to read and write to the disk and interface with other hardware components.

When your PC is first powered up, a lot happens. Electrical components of the PC are initially responsible for bringing your computer to life, as debouncing circuits take your push of the power button and trigger a switch that activates the power supply and directs current from the PSU to the motherboard and, mainly through it, to all the various components of your PC. As each individual component receives life-giving electricity, it is powered up and brought online to its initial state. The startup routines and overall functionality of the simpler components like the RAM and PSU is hardwired into them as a series of logic circuits (AND/NAND and OR/NOR gates), while more complicated parts such as the video card have their own microcontrollers that act as mini-CPUs, controlling the hardware and interfacing with the rest of your PC to delegate and oversee the work.

The POST Process

Once your PC has been powered on, the BIOS begins its work as part of the POST (Power-On Self Test) process. It bridges all the various parts of your PC together, and interfaces between them as required, setting up your video display to accept basic VGA and show it on the screen, initializing the memory banks and giving your CPU access to all the hardware. It scans the IO buses for attached hardware, and identifies and maps access to the hard disks you have connected to your PC. The BIOS on newer motherboards is smart enough to even recognize and identify USB devices, such as external drives and USB mice, letting you boot from USB sticks and use your mouse in legacy software.

During the POST procedure, quick tests are conducted where possible, and errors caused by incompatible hardware, disconnected devices, or failing components are often caught. It’s the BIOS that’s responsible for a variety of error messages such as “keyboard error or no keyboard present” or warnings about mismatched/unrecognized memory. At this point, the majority of the BIOS’ work has completed and it’s almost ready to move on to the next stage of the boot process. The only thing left is to run what are called “Add-On ROMs”: some hardware attached to the motherboard might require user intervention to complete its initialization and the BIOS actually hands off control of the entire PC to software routines coded into hardware like the video card or RAID controllers. They assume control of the computer and its display, and let you do things like set up RAID arrays or configure display settings before the PC has even truly finished powering up. When they’re done executing, they pass control of the computer back to the BIOS and and the PC enters a basic, usable state and is ready to begin.

BIOS Boot Handoff

After having configured the basic input and output devices of your PC, the BIOS now enters the final stages where it’s still in control of your computer. At this point, you’ll normally be presented with an option to quickly hit a key to enter the BIOS setup from where you can configure hardware settings and control how your PC boots. If you choose nothing, the BIOS will begin the first step in actually “booting” your PC using the default settings.

Earlier we mentioned that an important part of the BIOS’ work is to detect and map connected hard disks. This list now comes in handy, as the BIOS will load a very small program from the first hard disk to the memory and tell the CPU to execute its contents, handing off control of the computer to whatever is on the hard drive and ending its active role in loading your PC. This hard drive is known as “the boot device,” “startup disk,” or “drive 0” and can usually be picked or set in the BIOS setup.

The Boot Device

Regardless of whether the BIOS was configured to boot from a local hard disk or from a removable USB stick, the handoff sequence is the same. Once the BIOS POST and AddOn ROM procedures have completed, the BIOS loads the first 512 bytes from the hard drive of the selected boot device – these 512 bytes are what is commonly known as the MBR, or the Master Boot Record.

The Master Boot Record (MBR)

The MBR is the first and most important component on the software side of things in the boot procedure on BIOS-based machines. Every hard disk has an MBR, and it contains several important pieces of information.

Master Boot Record

The Partition Table

First and foremost, the MBR contains something called the partition table, which is an index of up to four partitions that exist on the same disk, a table of contents, if you will. Without it (such as on floppy disks), the entire disk could only contain one partition, which means that you can’t have things like different filesystems on the same drive, which in turn would mean you could never install Linux and Windows on the same disk, for example.

Bootstrap Code

Secondly, the MBR also contains a very important bit of code known as the “bootstrap code.” The first 4403 of these 512 bytes can contain literally anything – the BIOS will load it and execute its contents as-is, kicking off the bootloader procedure. 440 bytes is incredibly small. How small? Well, to put things in context, 440 bytes is only 0.3% of the capacity of an ancient 1.44 MiB floppy disk – barely enough to fit any form of useful code – and way, way too small to do something as complicated as call up the operating system kernel from the disk.

Given how tiny the bootstrap code section of the MBR is, the only useful purpose it can really serve is to look up another file from the disk and load it to perform the actual boot process. As such, this bootstrap code is often termed a “stage one bootloader.” Depending on the operating system, the exact place the bootstrap code searches for the “stage 2 bootloader” can change, but on Windows the stage 1 bootloader will search the partition table of the MBR for a partition marked as “active” which is MBR-speak for “bootable,” indicating that the start of the partition contains the next portion of the boot code in its starting sectors (also known as its “bootsector”). On a correctly-created MBR disk, only one partition can be marked as active at a time.4

So the job of the bootstrap code segment in the MBR is pretty simple: look up the active partition from the partition table, and load that code into the memory for execution by the CPU as the next link in the boot chain. Depending on the OS you’re loading, it might actually look up a hard-coded partition instead of the active partition (e.g. always load the bootsector of the 3rd partition) and the offset of the boot code within the partition bootsector might change (e.g. instead of being the first 2 KiB of the partition, it might be the second KiB or 6 KiB starting from the 2nd multiple of the current phase of the moon) – but the basic concept remains the same. However, for legacy compatibility reasons, the MBR almost always loads the first sector of the active partition, meaning another only-512 bytes.

Boot Signature

On IBM-compatible PCs (basically, everything) the final two bytes of the 512-byte MBR are called the boot signature and are used by the BIOS to determine if the selected boot drive is actually bootable or not. On a disk that contains valid bootstrap code, the last two bytes of the MBR should always be 0x55 0xAA.5If the last two bytes of the MBR do not equal 0x55 and 0xAA respectively, the BIOS will assume that the disk is not bootable and is not a valid boot option – in this case, it will fall back to the next device in the boot order list (as configured in the BIOS setup). For example, if the first boot device in the BIOS is set as the USB stick and the second is the local hard disk, if a USB stick without the correct boot signature is plugged in, the BIOS will skip it and move on to attempt to load from the local disk. If no disk in the boot device list has the correct 0x55 0xAA boot signature, the BIOS will then display an error such as the infamous “No boot device is available” or “Reboot and select proper boot device.”

The Partition Boot Sector

As covered above, the bootstrap code in the MBR will usually load a sequence of bytes from the start of the active partition. The exact layout of a partition depends what filesystem the partition has been created or formatted with, but generally looks something like this:

Partition on Disk

Again, depending on the OS and filesystem, the exact layout of the partition will certainly differ. But this represents a close approximation to what you’ll normally see:

This is all usually packed into the first sector of the partition, which is normally again only 512 bytes long, and again, can’t fit too much data or instructions. On modern filesystems for newer operating systems, the bootstrap code can take advantage of enhanced BIOS functionality to read and execute more than just 512 bytes, but in all cases, the basic steps remain the same:

  1. The MBR loads the first 512 bytes of the active partition into the memory and instructs the CPU to execute them.
  2. The very first (three) bytes of the partition bootsector contain a single JMP instruction, telling the CPU to skip xx bytes ahead and execute the next stage of the bootloader from there.
  3. The CPU follows the JMP instruction and seeks to the beginning of the bootstrap code contained within the partition bootsector, and starts to execute.

The bootstrap code in the partition is not the end of the road, it’s only another step along the way. Because of how little space is allocated for the bootstrap code in the partition bootsector, the code it contains normally ends with another JMP command instructing the CPU to jump to the next sector in the partition, which is often set aside for the remainder of the partition code. Depending on the filesystem, this can be several sectors in length, or however long it needs to be to fit this stage of the bootloader.

The second-stage bootloader

The second stage of the bootloader, stored in the partition bootsector in the bootstrap section and, optionally, continuing beyond it, carries out the next step in the bootloader process: it looks up a file stored on the partition itself (as a regular file), and tells the CPU to execute its contents to begin the final part of the boot process.

Unlike the previous bootstrap segments of the MBR and the partition bootsector, the next step in the boot process is not stored at a dedicated offset within the partition (i.e. the bootstrap code can’t just tell the CPU to JMP to location 0xABC and execute the boot file from there) – it’s a normal file stored amongst other normal files in the filesystem on the disk.

This significantly more-complicated bootstrap code must actually read the table-of-contents for the filesystem on the partition,7 The second-stage bootloader from older versions of file systems oftentimes placed complicated restrictions on the bootloader files they needed to load, such as requiring them to appear in the first several kilobytes of the partition or being unable to load non-contiguously allocated files on the partition. This file is the last piece of the bootloader puzzle, and there are usually no restrictions as to its size or contents, meaning it can be as large and as complicated as it needs to be to load the operating system kernel from the disk and pass on control of the PC to the OS.

The Bootloader

The actual bootloader files on the disk form the final parts of the boot loading process. When people talk about bootloaders and boot files, they are often referring to this final, critical step of the boot process.

Bootloader Sequence

Once control of the PC has been handed-off from the BIOS to the bootstrap code in the MBR and from the MBR to the bootstrap code in the partition bootsector, and from there there to the executable boot files on the active partition, the actual logic involved in determining which operating system to load, where to load it from, which parameters/options to pass on to it, and completing any interactions with the user that might be available, the actual process of starting the operating system begins.

Boot Configuration Files

While the executable bootloader files could theoretically contain hard-coded information pertaining to the operating systems to be loaded from the disk, that wouldn’t be very useful at all. As such, almost all bootloaders separate the actual, executable bootloader from the configuration file or database that contains information about the operating system(s) to load. All of the major bootloaders mentioned below have support for loading multiple operating systems, a process known as “dual-booting” or “multi-booting.”

Popular Bootloaders

As discussed previously, there are many different bootloaders out there. Each operating system has its own bootloader, specifically designed to read its filesystem and locate the kernel that needs to be loaded for the OS to run. Here are some of the more-popular bootloaders – and their essential configuration files – for some of the common operating systems:

NTLDR BOOTMGR GRUB

 

Each of the popular operating systems has its own default bootloader. Windows NT, 2000, and XP as well as Windows Server 2000 and Windows Server 2003 use the NTLDR bootloader. Windows Vista introduced the BOOTMGR bootloader, currently used by Windows Vista, 7, 8, and 10, as well as Windows Server 2008 and 2012. While a number of different bootloaders have existed for Linux over the years, the two predominant bootloaders were Lilo and GRUB, but now most Linux distributions have coalesced around the all-powerful GRUB2 bootloader.

NTLDR

NTLDR is the old Windows bootloader, first used in Windows NT (hence the “NT” in “NTLDR,” short for “NT Loader”), and currently used in Windows NT, Windows 2000, Windows XP, and Windows Server 2003.

NTLDR stores its boot configuration in a simple, text-based file called BOOT.INI, stored in the root directory of the active partition (often C:\Boot.ini). Once NTLDR is loaded and executed by the second-stage bootloader, it executes a helper program called NTDETECT.COM that identifies hardware and generates an index of information about the system. More information about NTLDR, BOOT.INI, and NTDETECT.COM can be found in the linked articles in our knowledgebase.

BOOTMGR

BOOTMGR is the newer version of the bootloader used by Microsoft Windows, and it was first introduced in the beta versions of Windows Vista (then Windows Codename Longhorn). It’s currently used in Windows Vista, Windows 7, Windows 8, Windows 8.1, and Windows 10, as well as Windows Server 2008 and Windows Server 2012.

BOOTMGR marked a significant departure from NTLDR. It is a self-contained bootloader with many more options, especially designed to be compatible with newer functionality in modern operating systems and designed with EFI and GPT in mind (though only certain versions of BOOTMGR support loading Windows from a GPT disk or in a UEFI/EFI configuration). Unlike NTLDR, BOOTMGR stores its configuration in a file called the BCD – short for Boot Configuration Database. Unlike BOOT.INI, the BCD file is a binary database that cannot be opened and edited by hand.8 Instead, specifically designed command-line tools like bcdedit.exe and more user-friendly GUI utilities such as EasyBCD must be used to read and modify the list of operating systems.

GRUB

GRUB was the predominantly-used bootloader for Linux in the 1990s and early 2000s, designed to load not just Linux, but any operating system implementing the open multiboot specification for its kernel. GRUB’s configuration file containing a whitespace-formatted list of operating systems was often called menu.lst or grub.lst, and found under the /boot/ or /boot/grub/ directory. As these values could be changed by recompiling GRUB with different options, different Linux distributions had this file located under different names in different directories.

GRUB 2

While GRUB eventually won out over Lilo and eLilo, it was replaced with GRUB 2 around 2002, and the old GRUB was officially renamed “Legacy GRUB.” Confusingly, GRUB 2 is now officially called GRUB, while the old GRUB has officially been relegated to the name of “Legacy GRUB,” but you’ll thankfully find most resources online referring to the newer incarnation of the GRUB bootloader as GRUB 2.

GRUB 2 is a powerful, modular bootloader more akin to an operating system than a bootloader. It can load dozens of different operating systems, and supports custom plugins (“modules”) to introduce more functionality and support complex boot procedures.

The actual bootloader file for GRUB 2 is not a file called GRUB2, but rather a file usually called core.img. Unlike Legacy GRUB, the GRUB 2 configuration file is more of a script and less of traditional configuration file. The grub.cfg file, normally located at /boot/grub/grub.cfg on the boot partition, bears resemblance to shell scripts and supports advanced concepts like functions. The core functionality of GRUB 2 is supplemented with modules, normally found in a subdirectory of the /boot/grub/ directory.

The Boot Process

As previously mentioned, the stage of the boot process is a little more involved than the previous steps, primarily due to the additional complexity of reading the filesystem. The bootloader must also obtain information about the underlying machine hardware (either via the BIOS or on its own) in order to correctly load the desired operating system from the correct partition and provide any additional files or data that might be needed. It must also read its own configuration file from a regular file stored on the boot partition’s filesystem, so it needs to at the very least have full read support for whatever filesystem it resides on.

Bootloader Flowchart

Conclusion

Thus ends the lengthy journey that begins with the push of a button and ends with an operating system’s kernel loaded into the memory and executed. The bootloader process is certainly a lot more nuanced and complicated than most realize, and it has both been designed and evolved to work in a fairly-standardized fashion across different platforms and under a variety of operating systems.

The individual components of the bootloader are, by and large, self-sufficient and self-contained. They can be swapped out individually without affecting the whole, meaning you can add disks and boot from different devices without worrying about upsetting existing configurations and operating systems. It also means that instead of having one, single bit of hardware/software to configure, setup, maintain, and debug, you instead are left with a intricate and oftentimes very fragile chain with multiple points susceptible to breakage and failure. When working properly, the boot process is a well-oiled machine, but when disaster strikes, it can be a very difficult process to understand and debug.

Troubleshooting the Bootloader

The complicated nature of the boot process means that there’s a lot that needs to be set up and configured, and a lot that could potentially go wrong. Some resources that can come handy when troubleshooting the bootloader are listed below:

See Also

These additional articles and resources in our wiki and from other websites online contain additional information relevant to this topic:


  1. Originally, the BIOS was stored on what was termed a ROM (“read-only memory”) chip: the BIOS code was hard-coded into the chip and could never be changed. Updates to the BIOS were rare and far in between, and could only be done by physically replacing the BIOS chip on the motherboard. Over time and with better technology, erasable ROM chips were developed that could be cleared by placing them in a box and blasting them with a dosage of UV (ultraviolet) radiation, then reprogramming their contents with ROM chip programming hardware. When that got old and tiring, electrically-erasable programmable ROM (EEPROM for short) was developed – with it, an electronic signal on specially-selected pins of the EEPROM chip would trigger an erase, and the chip could be programmed directly from where it was located on the motherboard. Easy-peasy-lemon-squeezy, as my wife would say!! 

  2. Almost every major hardware component now has firmware controlling it baked into its logic hardware, but we’re ignoring microcontroller firmware here. 

  3. Depending on the operating system and platform, the bootstrap code might actually only be anywhere from 434 to 446 bytes as parts of that region might be set aside for other purposes, such as the disk signature and disk timestamp. On most modern operating systems, 440 is the upper limit as the last 6 bytes are set aside for the 4-byte disk signature at offset 0x01B8 and a 2-byte field indicating the read-write state of the drive at offset 0x01BC (with 0x00 indicating read-write and 0x5A5A indicating a read-only drive). 

  4. There is a huge caveat emptor here: the bootable/active flag is actually a property of the individual (one of four) partition record, and not the partition table (list of partition records) or the MBR itself. What that means is that technically the actual bit indicating that a partition is bootable can actually be present (set to a value of 0x80) one more than one (or all!) partition(s) simultaneously! This is an invalid configuration and can cause many different boot problems though! 

  5. On a little-endian machine like all x86 CPUs, that would be a single word 0xAA55 while on a big-endian architecture like PowerPC, it would be read and written as 0x55AA. 

  6. Assembly is the most primitive programming language, and consists of individual instructions directly read and executed by the CPU. 

  7. On NTFS, this is called the master file table, or MFT for short. On FAT32, this is called the FAT (file allocation table). 

  8. In fact, the BCD file is a standard Windows registry hive, and it can be mounted for editing with tools like regedit.exe. 


二月八日等待变化等待机会

有没有想过html的规范,我就是不知道才犯了那么多的错误。这里有一个网页的标准。我保存了一个拷贝

二月九日等待变化等待机会

我修改我的file-magic增加了html的识别,是不是听上去很好笑?难道你不能依靠后缀名识别吗?难道有人有识别html文件格式的困难吗?可是天底下就是这样子的,我使用file命令得到的却往往是text/x-c++,这个是因为在magic里的c-lang优先识别c/c++的代码特征。我很不喜欢这个做法,于是只能自己增加了一个简单的html的识别文件
0	string		\<!DOCTYPE\040html\>	my html file
!:mime	text/html
0	string/t		\<!DOCTYPE\040html    my html file
!:mime	text/html
0	string		\<!DOCTYPE\040HTML	my html file
!:mime	text/html
实际上当然这个是很不完全的,这里的定义是<!DOCTYPE html>是不区分大小写的,我也顾不了。
编译file-magic就很tricky了,因为需要编译magic.mgc,可是它的编译却需要先编译file,而我的使用汇编码来把magic.mgc这个资源文件作为二进制码是编译file的一部分,所以,我也只能手动编译两次,不过我在makefile里拷贝magic.mgc很可能以后自己迷惑自己。
然后是推送我的amzonS3版本的git remote,于是遇到了这个错误,我决定使用:git config --bool core.bare true然后我在我的工作区push之后发现还要到remote去git reset --hard,这个真的是很麻烦,我没有使用git的基本经验,尤其是remote部分完全是一片的迷茫。
可是我遇到xml的声明在html之前怎么办呢?

二月十一日等待变化等待机会

我翻来覆去需要查找ascii table,所以,我想把这个很好的表克隆回来。我把ascii以及extended ascii table保存在这里。关于GB18030的regex表达式我还没有很理解,这里看样子很不错,只是我还不能确定能不能用。

二月十二日等待变化等待机会

今天总算解决了我一个头疼的问题,就是我的firefox始终在英文重体字显示上有问题,我曾经完全摸不着头脑,以为是显卡的问题,结果关闭了自动硬件加速也没有用,有人说是软件libfreetype之类的,我总算在无厘头尝试之前用了一下逻辑,我的服务器用的是同样的版本为什么没有呢?而且在网页定义文字为中文下也没有问题,那么只能是字体的问题,可是我已经在firefox设定了固定的字体times new roman,这个是看到chromium的设置而它就没有显示的问题,而且不允许网页使用用户自定义的字体,可是结果还是无效,于是我看到别人使用火狐的调试器来查看字体,果然在实际上的使用的字体是什么AR LUming之类的,总之的确像我的同学G说的那样其实浏览器对于字体是很乱的,不知道它怎么就使用一些莫名的字体,我只好打开font manager禁止了那型字体,看来是语言导致的一些问题吧?目前firefox也不是使用我定义的times new roman,而是DejaVu Serif,我唯一的解释就是也许它缺乏相应的字形大小?或者压根儿没有中文?不可理喻啊。
看到一个经典的范例,是关于boost::regex的搜索html链接的,虽然不是什么高深的,但是对于怎样大小写不限我还是第一次意识到是这样子写的。
这里是gb2312的完全的表,实在是良心的资源啊。当然这些好心人都是作广告挣钱的,我不是良心好而是不会。我决定使用蛮力把这个GB2312硬生生的拷贝,是为什么只有7073个codepoints呢?我总觉得它缺了不少,于是我去中国国标官网来下载。这里下载的文件是受密码保护的,我对此实在是难以理解就去国标局询问,当然要打开免费阅读是可以的,你需要在那个网页上下载那个fileopen的一个AdobeReader的插件,而且这个插件支持的版本很旧了是7,8版本,我非了不少力气去Adobe官网下载这个古董安装包,然后安装firefox的这个插件才能在firefox下打开这个加密的pdf文件实在是太麻烦了,于是我就用打印到文件的办法拷贝了一个GB18030-2005国标的版本。这里的小插曲是我从加密保护的pdf打印出来的ps文件不能直接使用ps2pdf转回pdf文件,因为原来的pdf是密码保护的,那么ps文件大小居然是pdf的好几倍,可是我在打开的ps文件里打印到文件就可以打印回pdf的了。Adobe公司真是一个pathetic的贪婪。
我又一次无耻的把这个htmlcolor名字的列表拷贝下来了,

二月十四日等待变化等待机会

这里下载了一个简单的文档和boost regex官方文档差不多。

二月十六日等待变化等待机会

我不知道别人是否和我一样的情况,总之,每当我以为我明白了什么立刻实践中我就被打脸,感到痛你才知道你其实不明白。GB2312的实现和标准又是两回事,先撇开GB18030不说,因为它又添加了四个字符的编码基本用到的机会要小一些,那么国标究竟定义的是什么?这里的解释似乎让我看到一些曙光,难道编码是一个很复杂的东西吗?似乎学过计算机的人都有嗤之以鼻的豪迈,更有无数程序员声称他们理解unicode,但是具体到实践似乎不是那么样子的。至少我的实验把iconv里关于GB2312和utf8的转换表自己生成html文件在浏览器里显示的是不正确的,也许是浏览器的font不全,也许那个表不是utf-8,我对于iconv的代码还没有看明白不能确定。从其他渠道下载了一大堆的gb2312转换到unicode的表似乎也不正常显示。说明了什么呢?收藏一个关于GB18030的wiki文档
我还有一个疑问就是关于textarea的,照例说其中的这样一些特殊字符&<>需要escape,但是好像现代的浏览器很聪明不需要了,是吗?
关于国标局的GB2312标准我就是在线浏览也需要安装flash,我只好下载了.so,然后拷贝到firefox的/usr/lib/firefox/plugins目录下。但是这个在线浏览服务器实在是太慢了,我没有了耐心就去github下载了一个GB2312-1980影印版
我觉得我部分的被gnu的libiconv的代码误导了,实际上iconv的代码是在glibc-bin这个包里的。

二月十七日等待变化等待机会

我编译了glibc-bin然后很多的可执行文件无法正常运行,报出的错误都是很奇怪的就是说这个可执行文件不存在,不论你使用全路径也是一样的错误,这个当然不会让我百思不得其解,因为我大概几十个思考就明白了,使用gdb不能找出原因,就用strace,同样不行,那么我就使用LD_DEBUG来debug linker loader,这个和我观察是一致的,根本没有调用我们的ld.so,通过查看INTERP看到它使用的是glibc-bin自己编译的,LOADER,这个使用readelf -l比objdump好用,因为它直接打印了interpretor的路径。
与此同时我不再使用libiconv的表,转而使用glibc-bin里的iconv里的表,至少我目前可以正确的确定表里对应的utf-8的GB2312范围的汉字是正确的,文件输出也不要考虑little-endian的short int 的顺序问题,当他是字符串输出就是了。
这里有一个小小的有趣的东西,就是C语言比C++好用的地方就是可以初始化一个数组用他的index,这个在c++里叫做non-trivial initialization不被支持。当然你要用c编译器去编译这个文件也是挺讨厌。当然顺便的当我把GB2312的所有字符都打印出来就检验了我的字库没有短少什么。

二月十八日等待变化等待机会

阅读了一下GB2312的国标我开始有了一个疑惑就是这个所谓的标准实际上是一个不完整的体系,因为和GB18030相比似乎缺少了编码的部分,它本质上就是一个区位码的标准而已?至于怎样和unicode或者什么的实现是另一件事?我不知道我的理解对不对,我又去这个标准网去下载了它所引用的一个更加古老的标准GB2311-2000这个标准更加的生涩难懂,我感觉是更加古老的在完全使用ascii码来表示汉字的办法,仿佛当初ansi-100里面大量的控制符和escape sequence之类的吧?
你再仔细阅读GB2311的编码部分就能明白GB2312是一个完整的编码体系,只不过你要结合前者的94位编码部分来看,这个也是我对于中国标准编篡者的不够严谨的态度的批评,因为国际上的RFC之所以严谨是因为它发出就是准备被别人挑毛病的,所以,不写的完备只能被人诟病,而国标在我看来是有一点点高高在上的官老爷座派,在十分难以理解的地方仅仅是一笔带过对于我这样完全没有那个时代编码常识的人来说是一头雾水。GB2311简单说就是半个byte作列,半个byte作行,以列/行来表示的范围是02/01-07/14||10/01-15/15。看到这里你才能和GB2312的编码表联系起来。
GB 18030 encoding
GB 18030 code points Unicode
byte 1 (MSB) byte 2 byte 3 byte 4
007F 128 0000007F
80 invalid
81FE 40FE except 7F 23940 0080FFFF except D800DFFF
8184 3039 81FE 3039 39420
85 — (12600) reserved for future character extension
868F — (126000) reserved for future ideographic extension
unassigned D800DFFF
90E3 3039 81FE 3039 1048576 1000010FFFF
E4FC — (315000) reserved for future standard extension
FDFE — (25200) user-defined
FF invalid
Total 1112064
之前我在这里下载了不少的编程需要的,当时看的还是一头雾水,事后看glibc-bin/iconv的代码似乎用的就是这里收集的东西。这里是作者的说明部分我保存在这里

二月十九日等待变化等待机会

《慕夏》的歌词真是意境缠绵语意渺渺,收藏一下。

银辉描淡的石桥

桥边嬉戏的垂髫

风吹枝柳折细腰

童谣声声伴长吆

乌蓬水面风寂寥

薄云淡淡月初瞧

江上画舫熏香绕

灯暖酒烫知己邀

西月东落天色微曜

半樽清酒斟得逍遥

白衣纵马风流年少

佳人倾城回眸浅笑

玉笛声声月色皎皎

起舞翩翩清影窈窕

姻缘树下共求月老

执手暮暮朝朝

长夜温柔萤光照

满城锦绣灯火耀

老街小巷复喧嚣

戏台风月增新貌

轻纱遮面舞灵巧

琵琶绕梁音袅袅

唱罢西厢客醉笑

推杯换盏度良宵

西月东落天色微曜

半樽清酒斟得逍遥

白衣纵马风流年少

佳人倾城回眸浅笑

玉笛声声月色皎皎

起舞翩翩清影窈窕

姻缘树下共求月老

执手暮暮朝朝

西月东落天色微曜

半樽清酒斟得逍遥

白衣纵马风流年少

佳人倾城回眸浅笑

玉笛声声月色皎皎

起舞翩翩清影窈窕

姻缘树下共求月老

执手暮暮朝朝

一曲风小筝泡泡的《西江月》真使人柔肠寸断挥之不去啊。

  演唱:风小筝 & 泡泡Hansy
作词:顾哲宣
作曲:吕宏斌
编曲:吕宏斌

风筝:船行渔火星点稀疏
独奏琵琶诉说孤楚
芳心不解谁能明目
琴弦上种下爱的蛊

泡泡:盼有他陪伴
叹这世道乱
当年折柳送别的诗句
停落宣纸让谁心难安

风筝:临摹一帖西江月
笔墨挥毫月下雪
唱起这首西江月
谁在吟诵月下绝
泡泡:青石板 油纸伞
而今邂逅不算晚
怕只怕 阴晴圆缺又离散

风筝:盼有他陪伴
叹这世道乱
泡泡:当年折柳送别的诗句
停落宣纸让谁心难安

风筝:临摹一帖西江月
笔墨挥毫月下雪
唱起这首西江月
谁在吟诵月下绝
泡泡:青石板 油纸伞
而今邂逅不算晚
怕只怕 阴晴圆缺又离散

风筝:临摹一帖西江月
笔墨挥毫月下雪
泡泡:唱起这首西江月
谁在吟诵月下绝
合: 青石板 油纸伞
而今邂逅不算晚
怕只怕 会是伤情又一段

风筝:月色朦胧 回忆看不清
黑白街景 已逝去光阴
西江岸边的柳絮
抒情却只字未提
 
总之,阅读GB2312不如阅读GB18030因为后者包含前者而且更加的完备。但是它依赖于一个GB13000的标准我的中文理解也有问题,GB18030只是说对于GB13000里规定的字形分配了代码,并不是在那个标准里分配。一个标准的制定怎么这么不好理解呢?经过使用网上的工具转换我才能确定所谓的编码这里指的就是utf-8编码,而所谓的区位码才是所谓的GB2312/GB18030的编码,比如utf-8编码0x98ce对应的GB18030编码就是0xb7e7

二月而十日等待变化等待机会

我觉得我总算对于国标编码有了比较初步的认识,那么下一步就是utf编码了,似乎这又是一个很大的领域。首先就是虽然是unicode我总是以为这个一定就是中日韩各自有一套,对也不对,其共用的部分cjk究竟是否包含了汉字的全部呢?这里是我看到的一个完美的解释,通俗的说就是朔本追源中日韩台港澳新马越如果他们有用到汉字的话不管有什么形式上的变化用法的变化,都把他们统一在一个编码上,这个从节省编码的角度是对的,就是共享代码的意思。不明白这一点对于unicode的中文编码肯定是会疑惑的。这是理解的第一步。

Q: If the character shapes are different in different parts of East Asia, why were the characters unified?

A: The Unicode Standard is designed to encode characters, not glyphs. Even where there are substantial variations in the standard way of writing a character from locale to locale, if the fundamental identity of the character is not in question, then a single character is encoded in Unicode.

This principle applies to East Asian scripts as well as to those of other parts of the world. It is well-recognized that the Han characters involved are the same, even when used in different countries to write different languages. In the overwhelming majority of cases where a Han character is written differently in different locales, readers from one locale would recognize the form used in another; in all cases, experts from throughout East Asia would recognize the fundamental unity of the character.

还是说。。。那么编码说完了就是实现的font部分了,这个本来就是一件事的高层与实现问题或者呈现部分的问题。首先下载了unicode的编码部分
终于来到了ut8这个时候才意识到我头脑中的概念有多么的混乱,简直是惭愧啊,ucs2/ucs4和utf8是完全无关的编码方式啊。前者已经说明了它有几个byte,我对于GB18030是不是也是一个ucs2/ucs4的编码的问题有些模糊,应该是否定的,否则也就不需要那个转换的表了,我感觉GB18030的编码实际上是被之前的GB2312的区位码编码限制住了,为了向前兼容所以才有了,当然更古老的原因是为了更早的使用escape的7bits串来表达汉字的编码方式。至于ucs2/ucs4的编码的方式如何我还是有些模糊,也许这里就是cjk的主导的。总之,utf8其实是很巧妙的设计,一个是它完全的兼容ascii,他的起始码和后续码是不会混淆的。当然一个主要的缺点是中文的编码应该都是三个byte的,这个比其他GB18030/GB2312来看是比较浪费一些因为前者大部分常用字是两个byte。

二月二十一日等待变化等待机会

我是否可以在程序里自由的转换编码方式呢?我觉得我昨天始终实验不成功的原因是我的系统压根儿没有安装相应的编码文件吧?这里我找到了一个不错的指导安装各种中文编码的步骤
  1. 安装基础的中文安装包来以便选择需要的中文编码方式:(这个是新版ubuntu的包的名字,原文使用的是老版ubuntu的包的名字)
    
     sudo apt-get install language-pack-zh-hant language-pack-zh-hans language-selector-gnome
  2. 选择需要安装的中文编码:
    sudo dpkg-reconfigure locales
     
    这里我把所有的大中华文字圈都选了进来:
    1. [*] zh_CN GB2312
    2. [*] zh_CN.GB18030 GB18030
    3. [*] zh_CN.GBK GBK
    4. [*] zh_CN.UTF-8 UTF-8
    5. [*] zh_HK BIG5-HKSCS
    6. [*] zh_HK.UTF-8 UTF-8
    7. [*] zh_SG GB2312
    8. [*] zh_SG.GBK GBK
    9. [*] zh_SG.UTF-8 UTF-8
    10. [*] zh_TW BIG5
    11. [*] zh_TW.EUC-TW EUC-TW
    12. [*] zh_TW.UTF-8 UTF-8
  3. 生成locale,我的理解是所谓的cache之类的binary的文件吧:locale-gen随后使用locale -a你可以看到有什么样支持的locale了。似乎不需要在/var/lib/locales/supported.d下的文件里定义什么是支持的locale吧,似乎这个是新版的ubuntu的改进?
  4. 至此你至少在基础上有了locale的支持了在程序里std::cout.imbue(std::locale("zh_CN.gb18030"));是不会再报错了。但是具体是否能够正确显示是另一个问题了。
  5. 这里是一个很好的关于c和c++ locale区别的文章,讲的很好,我花了25秒浏览了一下。

二月二十三日等待变化等待机会


二月二十五日等待变化等待机会

其实我所看到的编码体系学名是EUC-CN(Extended Unix Code)是GB2312国标的通常的编码形式,是基于ISO2022。虽然这里都是任何学习计算机所熟知的常识,可是不仔细领会不得其中真谛:
  1. 兼容Unix的拉丁语言传统只是用7bits-byte
  2. code points为什么是94个?我之前居然不理解,printable的就是94啊,剩下的是space加上33个控制字符。
  3. 我很喜欢这个图,很经典的信息量很大的:
    EUC-CN
    EUCCN encoding.svg
    MIME / IANAGB2312
    Alias(es)csGB2312
    Language(s)Simplified Chinese, English, Russian
    StandardGB 2312 (1980)
    ClassificationExtended ASCII, Variable-width encoding, CJK encoding, EUC
    ExtendsUS-ASCII
    Extensions748, GBK, GB18030, x-mac-chinesesimp
    Transforms / EncodesGB 2312
    Succeeded byGBK, GB18030
  4. 这个表也是非常好编码的例子。就是说GB2312定义的是一个charset的实体,而对于传输层的编码并没有规定,它就是一个区位码系统包含了glyphen的具体实体好像都是另一个国标的事情。我下载过这些国标就是用点阵来表达汉字的bmp位图,当然这个有一系列的不同尺寸的国标。而以下这个表展示了区位码如何实现传输码的办法,最简单的是ISO2022的转化,就是在区位码的列行分别加0x20。而对于EUC-CN编码就是在这个基础上再0x80。而所谓的HZ编码实际上是一个使用escape字符承载EUC-CN编码作为传统的电子邮件等等只接受普通西欧字符的编码协议。
    Various forms of the GB2312 code (0xD2BB) for the character "一" (one)
    Form Code With escape sequences Remarks
    Kuten / Qūwèi / 区位 form 5027 Zone/ward/row (ku/qū/) 50, point (ten/wèi/) 27
    ISO 2022 form 5216 3B16 0E16 5216 3B16 0F16 50 + 32 = 82 = 5216
    EUC-CN form D216 BB16 D216 BB16 5216 ∨ 8016 = D216
    HZ form (standard) 5216 3B16 7E16 7B16 5216 3B16 7E16 7D16 Appears as ~{R;~} without HZ decoder
    HZ form (alternate) D216 BB16 7E16 7B16 D216 BB16 7E16 7D16 EUC form acceptable to at least some decoders

Many languages or language families not based on the Latin alphabet such as Greek, Cyrillic,Arabic, or Hebrew have historically been represented on computers with different 8-bit extended ASCII encodings. Written East Asian languages, specifically Chinese, Japanese, and Korean, use far more characters than can be represented in an 8-bit computer byte and were first represented on computers with language-specific double byte encodings.

ISO/IEC 2022 was developed as a technique to attack both of these problems: to represent characters in multiple character sets within a single character encoding, and to represent large character sets.

A second requirement of ISO-2022 was that it should be compatible with 7-bit communication channels. So even though ISO-2022 is an 8-bit character set any 8-bit sequence can be reencoded to use only 7-bits without loss and normally only a small increase in size.

To represent multiple character sets, the ISO/IEC 2022 character encodings include escape sequences which indicate the character set for characters which follow. The escape sequences are registered with ISO and follow the patterns defined within the standard. These character encodings require data to be processed sequentially in a forward direction since the correct interpretation of the data depends on previously encountered escape sequences. Note, however, that other standards such as ISO-2022-JP may impose extra conditions such as the current character set is reset to US-ASCII before the end of a line.

To represent large character sets, ISO/IEC 2022 builds on ISO/IEC 646's property that one seven bit character will normally define 94 graphic (printable) characters (in addition to space and 33 control characters). Using two bytes, it is thus possible to represent up to 8836 (94×94) characters; and, using three bytes, up to 830584 (94×94×94) characters. Though the standard defines it, no registered character set uses three bytes (although EUC-TW's unregistered G2 is). For the two-byte character sets, the code point of each character is normally specified in so-called kuten (Japanese: 区点) form (sometimes called qūwèi (Chinese: 区位), especially when dealing with GB2312 and related standards), which specifies a zone (, Japanese: ku, Chinese: ), and the point (Japanese: ten) or position (Chinese: wèi) of that character within the zone.

The escape sequences therefore do not only declare which character set is being used, but also, by knowing the properties of these character sets, know whether a 94-, 96-, 8836-, or 830584-character (or some other sized) encoding is being dealt with.

In practice, the escape sequences declaring the national character sets may be absent if context or convention dictates that a certain national character set is to be used. For example, ISO-8859-1 states that no defining escape sequence is needed and RFC 1922, which defines ISO-2022-CN, allows ISO-2022 SHIFT characters to be used without explicit use of escape sequences.

The ISO-2022 definitions of the ISO-8859-X character sets are specific fixed combinations of the components that form ISO-2022. Specifically the lower control characters (C0) the US-ASCII character set (in GL) and the upper control characters (C1) are standard and the high characters (GR) are defined for each of the ISO-8859-X variants; for example ISO-8859-1 is defined[citation needed] by the combination of ISO-IR-1, ISO-IR-6, ISO-IR-77 and ISO-IR-100 with no shifts or character changes allowed.

Although ISO/IEC 2022 character sets using control sequences are still in common use, particularly ISO-2022-JP, most modern e-mail applications are converting to use the simpler Unicode transforms such as UTF-8. The encodings that don't use control sequences, such as the ISO-8859 sets are still very common.

看着微波炉里旋转的羊角面包我呆呆的发问自己在做什么,学习汉字编码这种老掉牙的东西是吃饱了没事干,还是老年痴呆症早期,抑或是提前从事我心中渴望的退休后的发挥余热的计算机考古学工作?也许为了给自己解脱内心的烦恼,掩饰自己碌碌无为昏昏噩噩的虚度光阴?慢慢地我在羞愧无以复加的自责与悔恨中抬起了沮丧的头,似乎要证明我还有学习能力还有求知探索的渴望,莫名的迸出一个聊以自慰的理由:很多年前在个人计算机蓬勃兴起的信息革命大潮中,有很多领军前辈曾经断言中国人因为汉语的巨大编码劣势将被无情的抛弃在信息革命的洪流以外,二十一世纪的大门将不对非拼音文字国家开放,世界的终极是统一在以英语国家为主的灯塔民主自由体制。曾几何时无数的中国青年以学习英语为人生唯一手段以融入欧美社会为人生唯一终极目标。汉字的编码是如此的复杂麻烦的工作,以至于很多人主观的认定中国人无法拥抱互联网,社会上对于文字改革拉丁化时有耳闻。有多少人会预计到中文作为编码通讯的看似效率很低的语言非但没有阻止中国人使用信息革命的成果,似乎还后来居上,在这个历史进程中,汉字编码有着无比重要的意义,重新学习了解这个最最基本的体系过程不但是对于每天接触的基本原理的掌握,更是借鉴古今认识未来的窗口。不了解历史怎能理解当下,又怎么能够预见未来。想到这里,我心安理得的开始吃早餐了。

实际上这是一个编程里的效率问题,关于GB2312编码转换最快的当然是这样的表:

unsigned short int gb2312toutf16[]=
{
	...
	[0xA2EE]=0x3229,[0xA2F1]=0x2160,[0xA2F2]=0x2161,[0xA2F3]=0x2162,
	[0xA2F4]=0x2163,[0xA2F5]=0x2164,[0xA2F6]=0x2165,[0xA2F7]=0x2166,
	[0xA2F8]=0x2167,[0xA2F9]=0x2168,[0xA2FA]=0x2169,[0xA2FB]=0x216A,
	[0xA2FC]=0x216B,[0xA3A1]=0xFF01,[0xA3A2]=0xFF02,[0xA3A3]=0xFF03,
	[0xA3A4]=0xFFE5,[0xA3A5]=0xFF05,[0xA3A6]=0xFF06,[0xA3A7]=0xFF07,
	[0xA3A8]=0xFF08,[0xA3A9]=0xFF09,[0xA3AA]=0xFF0A,[0xA3AB]=0xFF0B,
	[0xA3AC]=0xFF0C,[0xA3AD]=0xFF0D,[0xA3AE]=0xFF0E,[0xA3AF]=0xFF0F,
	[0xA3B0]=0xFF10,[0xA3B1]=0xFF11,[0xA3B2]=0xFF12,[0xA3B3]=0xFF13,
	...
};
但是我已经说过这样的表c++编译器是不支持的只能放在一个c文件里编译,那么你怎么直接使用的呢?只能靠函数调用:
unsigned short int* retrieveUtf8(unsigned short* index)
{
	if (*index < sizeof(gb2312toutf16)/sizeof(gb2312toutf16[0]))
	{
		return &gb2312toutf16[*index];
	}
	else
	{
		return 0;
	}
}
当然它的声明必须是c函数:
extern "C" unsigned short int* retrieveUtf8(unsigned short* index);
这里有一个细节就是我们的表是unsigned short int,这个在little endian里是和byte order相反的,比如汉字“一”的GB2312编码是0xD2BB这个是怎么来的你可以参考这个例子,它对应的utf16的编码是0x4E00,注意这个0xD2BB是byte order,所以,我要创建表的时候必须颠倒它的顺序成为little endian:
unsigned short int gb2312toutf16[]=
{
	...
	[0xBBD2] = 0x4E00,..
};
	
这样子我可以直接把文件中的GB2312编码当作unsigned short int指针:

	char* ucs2 = (char*)retrieveUtf8((unsigned short int*)(strEncode.c_str());
	cout.write(ucs2, 2);
注意这里返回的本来是一个unsigned short int*指针,不要使用unsigned short int*变量因为我们的utf16是一个按照byte order的short int,把它cast成char*指针的话,我们可以继续使用它的byte order,否则你使用short int*指针又要转换,而我们的write函数只能接受一个char*指针,这些都是程序员的基本功,我却脑袋进水一样的折腾了好久。这个被证明是不对的。

三月一日等待变化等待机会

关于utf16-le和utf16-be的问题我其实一开始没有意识到是一个很难办的问题。原因就是几乎无法判断,因为是编码没有作者的提示可能性几乎都是可能的,于是约定成俗的必须有所谓的BOM来提示,而我之前的疑惑在和同学G的交谈中三言两语就点到了说明他的确是造诣很深的,每次学到一点点我的敬佩就一步步加深,天下能人千千万万,我常常感到自己的无知是如此的令人愤慨与无奈。一句话,我的投机是在于不明白这个原理,一个浅显的道理,一旦有了BOM的提示你必须要遵守你的承诺到底是big endian还是little endian,必须要一致,我之前没有这个概念才会如此。所以,在big endian情况下编码就是如此:[0xe7b7]=0xce98。实际上我把脑袋撞墙后我才想起来实际上index无关于big/little endian,所以是big endian是[0xe7b7]=0xce98相似的little endian的结果就是[0xE7B7]=0x98CE原因是我为了充分利用数组的"hash"任意寻址于是其中的[0xE7B7]一定是我当前的little endian的整数形式,因为我要直接把byte order的char*指针cast为short int*指针。当然返回的值我为了能够直接cast成char*指针需要一个littleendian的整数。
絮絮叨叨了半天还没有说到核心的问题,就是utf16-le和utf16-be的BOM。我之前遇到的uchardet返回不能识别编码的结果就是因为我使用utf16但是没有在开头加上这个标识:

      switch (aBuf[0])
        {
        case '\xEF':
          if (('\xBB' == aBuf[1]) && ('\xBF' == aBuf[2]))
            /* EF BB BF: UTF-8 encoded BOM. */
            mDetectedCharset = "UTF-8";
        break;
        case '\xFE':
          if ('\xFF' == aBuf[1])
            /* FE FF: UTF-16, big endian BOM. */
            mDetectedCharset = "UTF-16";
        break;
        case '\xFF':
          if ('\xFE' == aBuf[1])
          {
            if (aLen > 3          &&
                aBuf[2] == '\x00' &&
                aBuf[3] == '\x00')
            {
                /* FF FE 00 00: UTF-32 (LE). */
                mDetectedCharset = "UTF-32";
            }
            else
            {
                /* FF FE: UTF-16, little endian BOM. */
                mDetectedCharset = "UTF-16";
            }
          }
          break;
        case '\x00':
          if (aLen > 3           &&
              aBuf[1] == '\x00' &&
              aBuf[2] == '\xFE' &&
              aBuf[3] == '\xFF')
          {
              /* 00 00 FE FF: UTF-32 (BE). */
              mDetectedCharset = "UTF-32";
          }
          break;
        }

三月二日等待变化等待机会

BOM的官方名称是byte order mark。这个特殊符号U+FEFF出现在文件开头是所谓的BOM,如果出现在文件中间是所谓的zero width no-break space (ZWNBSP), 以下这个表其实不错:
Encoding Representation (hexadecimal) Representation (decimal) Bytes as CP1252 characters
UTF-8 EF BB BF 239 187 191 
UTF-16 (BE) FE FF 254 255 þÿ
UTF-16 (LE) FF FE 255 254 ÿþ
UTF-32 (BE) 00 00 FE FF 0 0 254 255 ^@^@þÿ (^@ is the null character)
UTF-32 (LE) FF FE 00 00 255 254 0 0 ÿþ^@^@ (^@ is the null character)
UTF-7 2B 2F 76 38
2B 2F 76 39
2B 2F 76 2B
2B 2F 76 2F
2B 2F 76 38 2D
43 47 118 56
43 47 118 57
43 47 118 43
43 47 118 47
43 47 118 56 45
+/v8
+/v9
+/v+
+/v/
+/v8-
UTF-1 F7 64 4C 247 100 76 ÷dL
UTF-EBCDIC DD 73 66 73 221 115 102 115 Ýsfs
SCSU 0E FE FF 14 254 255 ^Nþÿ (^N is the "shift out" character)
BOCU-1 FB EE 28 251 238 40 ûî(
GB-18030 84 31 95 33 132 49 149 51 „1•3
关于unicode的plane我还要理解一下

Basic Multilingual Plane

The first plane, plane 0, the Basic Multilingual Plane (BMP) contains characters for almost all modern languages, and a large number of symbols. A primary objective for the BMP is to support the unification of prior character sets as well as characters for writing. Most of the assigned code points in the BMP are used to encode Chinese, Japanese, and Korean (CJK) characters.

The High Surrogate (U+D800–U+DBFF) and Low Surrogate (U+DC00–U+DFFF) codes are reserved for encoding non-BMP characters in UTF-16 by using a pair of 16-bit codes: one High Surrogate and one Low Surrogate. A single surrogate code point will never be assigned a character.

65,472 of the 65,536 code points in this plane have been allocated to a Unicode block, leaving just 64 code points in unallocated ranges (48 code points at 0870..089F and 16 code points at 2FE0..2FEF).


三月三日等待变化等待机会

这篇文章有些令人疑惑,有些部分很浅显,有些似乎不太准确。

三月十四日等待变化等待机会

连日都是在家办公抗疫,前天遇到一个又是初学者级别的问题,就是我要在union里定义的不是所谓的POD(plain old type),这个居然是不被编译器支持的,可笑的是我居然从来没有意识到。
昨天才突然意识到亚马逊中国和亚马逊似乎不是一家公司一样,因为账号是不通用的,申请亚马逊中国账号需要中国政府的ICP就是备案号,这个是外国人几乎不可能的。中美贸易谈判怎么没有把这个谈进去呢?
下载了tencent的微云同步助手的苹果版,我居然从来没有安装app的经历,现在才明白它的安装包都是一个可以被mount的小小的磁盘镜像,而你在没有安装的情况下似乎是以所谓的sandbox的形式试运行,苹果的操作系统的确有很多值得称道的特性,那么安装的时候出现了一个图标,我居然没有意识到它是要我把要安装的app拖到application里就是安装的意思结果双击那个app始终都是在试运行。直到google了这个。看来对于我这个初来乍到的苹果新手这样的基本教程是必要的。可是使用的结果就是我发现还不如直接使用浏览器用微信登陆来的方便,当然同步助手本来就是一个自动化,对于单纯上传大文件未必有什么优势。
我因为没有windows还曾经试图使用wine来安装微云同步助手的windows版本,结果当然是无数的莫名其妙的问题,这种工具虽然简单毕竟有可能使用了比较偏僻的api是游戏玩家所不在乎的东西。总之i,我发现wine还理替代虚拟机差得很远,当然啊,这么说实在是不公平,本来就不是一类的问题。wine is not emulator.

三月十七日等待变化等待机会

下载了c++委员会的文档里的语法定义。保存一个版本。为什么下载呢?我也不太清楚,也许是我想看清楚这个c++转html的代码的关于c++语法的正式定义吧。重新看它的代码还是很有收获的,一个思想是多么的简洁与使用,核心就是重载<<这个operator,这种编程思想其实我上一次已经接触过了,就是有位大师展示如何实现printf的各种的类型打印,使用递归加上重载operator <<
我常常因为我的cursor乱跳而恼火,google了一下才意识到我并不是第一个这么烦恼的人,要禁用我的笔记本的touch pad。有google真好。
TWOA就是腾讯的api,学习看。

三月十九日等待变化等待机会

这个大概是一个非常令人尴尬的问题,我大概是在设置wireshark以便普通用户就能访问网卡搞错了useradd/usermod之类的参数,结果突然之间我发现我的id不再是sudo user的成员组了,那么我也就无法作任何的系统权限的修改,好像我把房门钥匙丢在家里然后锁了门,怎么办?对于这种小失误重装系统是令人可耻的。无非就是用USB启动然后修改/etc/group把自己加回到sudo组就是了吗?文件磁盘系统不加密是至关重要的。

三月二十六日等待变化等待机会

下载一个aliyun的api手册。 这里是api文档。
这个公式值的收藏:

Signature = urlencode(base64(hmac-sha1(AccessKeySecret,  VERB 
		  + "\n" 
          + CONTENT-MD5 + "\n" 
          + CONTENT-TYPE + "\n" 
          + EXPIRES + "\n" 
          + CanonicalizedOSSHeaders
          + CanonicalizedResource)))

Detail analysis:


四月一日等待变化等待机会

关于在launcher中添加一个应用其实很简单,我原本在~/Desktop上放着的一个.desktop文件,我创建一个symlink到/usr/share/applications/下就好了。

四月二日等待变化等待机会

在macbook上mount nfs遇到permission的问题,我还一直想要使用命令行不使用sudo去作mount,后来发现这个是不可能的,因为mount对于非root只能接受一个参数,因此这个是不可能。当然这个并不是不可能使用/etc/auto_master之类的文件来实现非root的mount,但是作为命令行mount是禁止非root有额外的参数的。然后我遇到的问题是mount.nfs使用默认的privilege的port的限制的问题,这个需要参数-o resvport来绕过。然后我遇到了我早就应该明白的问题就是user mapping的问题,这个部分我始终都是模糊的认识,以前在家里所有的电脑都是使用同一个user,在办公室里都是用root从来没有这个问题,现在才遇到这个问题,我摸索了一下还是不确定是哪一个解决了这个问题(其中很多的部分也许都不是必要的): 我遇到一个scp的错误就是macbook抱怨no matching cypher。找了一个解决办法,就是在/etc/ssh/ssh_config里的Ciphers aes128-ctr,aes192-ctr,aes256-ctr,aes128-cbc,3des-cbc必须设置。但是这个macbook是公司的电脑我不想改动,于是可以在~/.ssh/config下设置如下:
host 192.168.1.116
Ciphers aes128-ctr,aes192-ctr,aes256-ctr,aes128-cbc,3des-cbc
老生常谈的问题就是pthread的mutex的特性问题

四月四日等待变化等待机会

我关于mutex的理解有时候真的是很让人愤怒。我实在是不想再提了。在线听大师关于Essence C++的讲座。我下载了剔除前面介绍和后面问答部分的mp3。其中的幻灯片在这里。这里是大师的一本小册子A Tour of C++。这里还有一本更加精简的段文章3rd tour

四月五日等待变化等待机会

一段文字好难懂啊,原因是我看错了一个字atomically我看成了automically,这个实在是搞笑的误读。回过头来看写这种说明文档的人都应该去当律师:

These functions atomically release mutex and cause the calling thread to block on the condition variable cond; atomically here means "atomically with respect to access by another thread to the mutex and then the condition variable". That is, if another thread is able to acquire the mutex after the about-to-block thread has released it, then a subsequent call to pthread_cond_broadcast() or pthread_cond_signal() in that thread shall behave as if it were issued after the about-to-block thread has blocked.


四月六日等待变化等待机会

怎样不使用mutex来解决consumer/producer的问题呢?这里要使用相应的pthread_yield,这个和系统的sched_yield应该是类似的。而这里的yield是一个比sleep更加高效的做法,因为它等于是把线程放到运行队列的后面重来一遍。

volatile unsigned int produceCount = 0, consumeCount = 0;
TokenType sharedBuffer[BUFFER_SIZE];

void producer(void) {
	while (1) {
		while (produceCount - consumeCount == BUFFER_SIZE) {
			schedulerYield(); /* sharedBuffer is full */
		}
		/* Write to sharedBuffer _before_ incrementing produceCount */
		sharedBuffer[produceCount % BUFFER_SIZE] = produceToken();
		/* Memory barrier required here to ensure update of the sharedBuffer is 
		visible to other threads before the update of produceCount */
		++produceCount;
	}
}

void consumer(void) {
	while (1) {
		while (produceCount - consumeCount == 0) {
			schedulerYield(); /* sharedBuffer is empty */
		}
		consumeToken(&sharedBuffer[consumeCount % BUFFER_SIZE]);
		++consumeCount;
	}
}

四月八日等待变化等待机会

我一直对于find的多个命令不知道怎么办,这个是一个很好的例子,其中文件名都是有空格的。 至于说你想调大音量这个是很好的例子

To increase the volume of the first audio track for 10dB use:

ffmpeg -i inputfile -vcodec copy -af "volume=10dB" outputfile

To decrease the volume of the first audio track for 5dB use:

ffmpeg -i inputfile -vcodec copy -af "volume=-5dB" outputfile

四月十三日等待变化等待机会

偶然看到一个貌似很专业的无线路由器评测的网站
摘抄了一些我也不敢确定的关于路由的基本常识的东西。
4,路由器知识讲解
4.1 路由器的单双频和传输速度

4.1.1 路由器单双频啥意思
路由器的频段粗分为两个,2.4G频段和5G频段
单频的其实就指的是只支持2.4G的802.11b/g/n三种协议
双频又叫ac路由器指的是同时支持802.11bgn和802.11ac

4.1.2 各个频率的传输速度
2.4G里的协议分为三代
802.11b:理论最高11Mbps(1.375MB每秒钟这个的确可以做到)
802.11g:理论最高54Mbps(6.75MB每秒这个的确很虚了)
802.11n:理论最高600Mbps(能做到就见鬼了)

在家里实际拿路由测试过,差一点的路由
比如水星的2.4G频段跑满大概是4MB/s,好一点的路由能跑到6-8MB/s
也就是说2.4G最多做到大概是802.11g的理论峰值而已
因为现实世界里,对于2.4G频段的污染实在是太多
蓝牙,手机4G信号,无线键鼠各种无人机遥控甚至连微波炉(当然不往外泄漏)
都是2.4G频段的信号,所以不仅达不到理论峰值,而且路上干扰特别多
因此拿笔记本接2.4G信号打实时网游,根本就等同于自杀

5G的话细分是也有不少
但是主流的是802.11n和802.11ac
802.11n可以在5G频段工作,不过性能远不如ac的好
用公司里的优科Ruckus的无线ap测验过
100M宽带下的实际下行速度其实就8MB/s不到甚至更低
802.11ac的话,最高可以达到40-60MB/s,100M宽带可以轻松跑满
重要的是,在802.11ac(个人认为这个叫真5G)环境下
无线设备和路由器的连接,延迟可以媲美有线
在Netgear R6100 配上5G usb网卡的情况下,打MC在线完全没问题。

4.2 穿墙性能详解

4.2.1 天线数量与穿墙的关系
其实没有关系
大家总觉得天线越多,穿墙性能越好其实根本就是扯淡
路由器里面有个叫做MIMO的技术(Multiple-Input Multiple-Output)
指的是多入多出,比如一个路由器有2T2R,那么就是说可以支持同时两个设备在线上传下行数据并且互不干扰,缩写成2X2。
一般情况下,天线的数量靠这个来设置是最靠谱的。
如2.4G路由器里,802.11N一般人为设定150Mbps作为单个设备需要带宽的单位(20Mhz频宽,最多可以到40Mhz)
而在5G里,以433Mbps作为单个设备需要的带宽单位(20Mhz频宽,最高可以扩展到80Mhz)
所以我们可以看到,一般情况下,150Mbps的路由器,一根天线足矣
300的两根,450的三根,如果出现了2.4G路由器出现了好多根线,其实一般都是。。
两根三根并作一根用。。。
如果是2.4G和5G并存的路由,那么,一般是有双数的天线
一半用来2.4G一半用于5G
所以说,天线的多少只能证明其带宽数量等等,其他的没用。

4.2.2 功率和穿墙性能的关系
其实关系非常大,不过大陆的路由器基本没有能说自己穿墙好的
因为工信部把无线路由器的发射功率限制死了在100毫瓦
100毫瓦是个啥概念?手机天线的发射功率都有800毫瓦
所以某些品牌说自己的路由器有穿墙模式,顶多就个噱头
当然也有些品牌会隐藏自己更改发射功率的能力
比如磊科的某些路由器可以开到200mw,D-link的某些路由器可以开到300mw左右
Netgear的某些路由器,通过更改固件,换运行区域或者插代码,可以达到500mw的发射功率
不过呢,发射功率越大不见得效果越好,发射功率和接受信号优劣仅仅是部分正相关
如果发射功率太高,而本身路由器天线质量很差,这样信噪比会变得很恶劣
而如果路由器本身的承载能力处理能力不足,你会在很远的地方都收到路由器信号
而且,网络根本连接不上,俗称假信号。
路由器的NAT能力远远比无线信号功率更重要

4.2.3 穿墙路由存在吗
其实穿墙路由的确很难存在,因为钢筋混凝土的结构对于短波超短波的阻隔作用太过明显,比如我家住的房子,原本只是区区100平,结果三个路由器才能全部覆盖(一个R7000,一个腾达AC6 Client+AP 无线桥接,一个AC6走飞线在客厅给iptv用),屋子是二十多年前买的,那个年头做的东西较为结实。。。
还有一个问题就是穿墙只限于2.4G,5G的事情就别想穿墙还有多好了
绝大部分的5G信号穿墙之后,信号强度至少砍半
无线电的频率越高,传输速率越快,但是遇到障碍物的衰减就越厉害(当然无障碍环境下就没问题)
【2016.9.27更新:其实并不是越高就传输速度越快,而是目前而言,频率越高,能给出的速度和频宽就高,并且干扰越小,比如5G能给出80Mhz的频宽,2.4G只有20/40Mhz,如果未来的802.11ad就算只需要一个信道,但是照样可以达到7Gbps
的带宽,只能说目前是正相关,具体涉及到QAM等内容,就不继续不懂装懂贻笑大方了】
不过现在有的路由器已经有了较为先进的科技,比如Netgear的睿动天线
Beamforming技术,腾达也有自己的Beamforming+,字面意思就是
路由器的信号会追着你打过去,可以看出效果是有的,就是意义大于实际
很多时候房子的角落头还是没信号
还没有试过更高级的东西(比如R8500那些),有机会希望试试。

4.3 带机量的问题
大家会注意到,普通的百元以下路由,连接10个左右的设备其实都快挂掉了。
因为本身设计的带机量,家用的大概就这么多了。
带机量由这些东西决定:处理器性能,运行内存大小,支持多少出入以及哪种MIMO技术
普通的路由比如那台99包邮的,处理器主频400Mhz,运行内存8M,闪存2M,支持3T3R
这样的机子明显不适合带多个的,就那小破运存玩啥。。
不过如果是粗粮这样的设计,处理器主频双核1Ghz,运存128M,闪存128M,
双频
但是粗粮路由说自己带机量高达64台我都被吓到了,实际测试效果看论坛
小米路由器的带机量是多少

为什么粗粮这么优秀的硬件账面带机量依旧不多
这个涉及到了带机量最重要的技术指标,就是新一代的MU MIMO技术
指的是多重多进多出。。。
以前的路由如果是传统的2*2MIMO,2T2R的话,可以做到两个设备全双工工作,但是如果是四个同时接进来呢
那么会先管一个设备的上传,上传完了,再做另一个设备的上传(经指正后已经将表述改正)
这种传统的MIMO技术本质是单线程同步上传,要先左手画圆,再右手画方,如果一瞬间大家都在视频聊天
这样铁定会卡了,因为大家都要卡上传,哪怕是局域网
因此新技术MU MIMO的出现,实现了给所有连接进来的设备合理分配带宽,可以异步多线程全双工工作,像周伯通一样同时左手画圆右手画方,而且还不止两只手了。
这样带机量会大幅上升,直接从十几个带机量,一下子进化到上百带机量
而目前市面上,低于1000元的路由器普遍不具备这个技术(JCG那个599爆款倒是有)
就算是众多的商用路由,也只是盲目的堆砌硬件,增加进出的数量以此增加到实际64左右的带机量,当然像Ruckus那种奇怪的品牌,巴掌大的ap实测带机量100多个的,不知道原因。。。

换句话说,等新的技术 MU-MIMO wave2得到普及和降价的时候,带机少的问题就可以得到解决了。2017将会是wave2爆发的一年,粗粮也已经在CES发布了他们的wave2旗舰产品,想必未来联发科方案的便宜产品也将会走进千家万户

阅读毛泽东著作《星星之火可以燎原》
我依然还是想把我的笔记本的wifi功能恢复:

四月十五日等待变化等待机会

关于boost的仿函数始终是一个让我头疼的话题,可是实际上我看了function才发现这个似乎不大像是仿函数,因为它就是一个函数对象。这种说法也没有什么理由,总之, 下面这个例子让我对于文档的一个关于stateful的reference产生了疑惑。这个reference和我们平常理解的似乎不同,如果把函数对象当作指针的话那么它的reference当然是无意义的,因为一个指针能有多少内存没有必要使用reference,这里的function还是一个很轻量级的对象,简单看了一下就是function_base的一些data。那么你对于一个函数对象的reference不要认为能够超越指针的常识。也就是说以下的结果应该是我早该预料到的没有必要大惊小怪。我这里有疑惑说明我对于指针的reference的疑惑:f1首先指向TestClass的成员函数add1,f2是指向f1的一个reference,自然的f2也是指向add1,然后f1先后指向setBase和minus1,那么f2指的是什么?还是add1!原来我对于boost::ref一知半解望文生义,它压根是一个传递参数的机制,根本不是我所以为的reference的机制。也就是说你可以看作它是f2的constructor的参数传递了f1的reference以便节省pass by copy的开销。另外我的这个简单的实验也是进一步加深认识所谓的stateful object的概念。否则既然有了free function那么类的成员函数压根儿就没有存在的必要了。实际上,这里我又修改了一下我的例子增添了一个真正的函数对象的reference f3,它才是真正的一个f1的reference。
这里是我摘抄的关于boost::ref的说明,实际上我读了还是不甚了了,之后依然犯错误。现在想来它的使用应该是有局限的吧,我以为只有在function这里可以使用吧?

The Ref library is a small library that is useful for passing references to function templates (algorithms) that would usually take copies of their arguments这就是我所说的它的目的. It defines the class template boost::reference_wrapper<T>, two functions boost::ref and boost::cref that return instances of boost::reference_wrapper<T>, a function boost::unwrap_ref that unwraps a boost::reference_wrapper<T> or returns a reference to any other type of object, and the two traits classes boost::is_reference_wrapper<T> and boost::unwrap_reference<T>.

The purpose of boost::reference_wrapper<T> is to contain a reference to an object of type T. It is primarily used to "feed" references to function templates (algorithms) that take their parameter by value.

To support this usage, boost::reference_wrapper<T> provides an implicit conversion to T&. This usually allows the function templates to work on references unmodified.

boost::reference_wrapper<T> is both CopyConstructible and Assignable (ordinary references are not Assignable).

The expression boost::ref(x) returns a boost::reference_wrapper<X>(x) where X is the type of x. Similarly, boost::cref(x) returns a boost::reference_wrapper<X const>(x).

The expression boost::unwrap_ref(x) returns a boost::unwrap_reference<X>::type& where X is the type of x.

The expression boost::is_reference_wrapper<T>::value is true if T is a reference_wrapper, and false otherwise.

The type-expression boost::unwrap_reference<T>::type is T::type if T is a reference_wrapper, T otherwise.

这是我的测试程序

class TestClass
{
public:
	int add1(int x)
	{
		return x+m_base;
	}
	int minus1(int x)
	{
		return x-m_base;
	}
	int setBase(int base)
	{
		m_base = base;
		return m_base;
	}
	TestClass(int base=0);
private:
	int m_base;
};
TestClass::TestClass(int base):m_base(base)
{
}
int main()
{
	boost::function<int (TestClass*, int)> f1 = &TestClass::add1;
	boost::function<int (TestClass*, int)> f2 = boost::ref(f1);
	boost::function<int (TestClass*, int)>& f3 = boost::ref(f1);
	if (!f1.empty() && !f2.empty())
	{
		TestClass x;
		cout << "f1 points to add1(3):" << f1(&x, 3) << endl;
		cout << "f2 also points to add1(3):" << f2(&x, 3) << endl;
		cout << "f3 also points to add1(3):" << f3(&x, 3) << endl;
		f1 = &TestClass::setBase;
		cout << "f1 setBase(3) mow base:" << f1(&x,3) << endl;
		f1 = &TestClass::minus1;
		cout << "f1 now points to minus1(3):"<< f1(&x, 3) << endl;
		cout << "f2 still points to add1(3):" << f2(&x, 3) << endl;
		cout << "f3 is actually points to minus1(3):" << f3(&x, 3) << endl;
	}
	return 0;
}
这里是运行结果

四月十六日等待变化等待机会

王菲的一首歌,歌词是: 有时候,有时候。 叫什么? 这句歌词出自于王菲的《红豆》
			《红豆》-王菲

填词:林夕

谱曲:柳重言

还没好好地感受  雪花绽放的气候  我们一起颤抖

会更明白什么是温柔  还没跟你牵着手  走过荒芜的沙丘

可能从此以后  学会珍惜天长和地久

有时候  有时候  我会相信一切有尽头

相聚离开都有时候  没有什么会永垂不朽

可是我  有时候  宁愿选择留恋不放手

等到风景都看透  也许你会陪我看细水长流

还没为你把红豆  熬成缠绵的伤口  然后一起分享

会更明白相思的哀愁  还没好好地感受  醒着亲吻的温柔

可能在我左右  你才追求孤独的自由

有时候  有时候  我会相信一切有尽头

相聚离开都有时候  没有什么会永垂不朽

可是我  有时候  宁愿选择留恋不放手

等到风景都看透  也许你会陪我看细水长流

有时候  有时候  我会相信一切有尽头

相聚离开都有时候  没有什么会永垂不朽

可是我  有时候  宁愿选择留恋不放手

等到风景都看透  也许你会陪我看细水长流
这里摘抄boost.function对垒普通函数指针的优劣点,我对于第一条兼容任何函数对象而不限于函数签名这一点不太理解,难道你不需要模板参数来描述函数签名吗?我现在又想了一下大约明白了就是函数指针的类型实际上就是一个有名的类型,这样子的typedef之后哪怕是同样的函数签名也是不同的类型而不兼容。这一层的道理是如此的简单又是每一个使用函数指针的令人头疼的第一件事而我却没有意识到真的不知如何说才好!但是话说回来原文的确是写的不必拘泥于函数签名,而这个正是让我迷惑的地方,也许我的理解不对或者原文有错误?

Boost.Function vs. Function Pointers

Boost.Function has several advantages over function pointers, namely:

And, of course, function pointers have several advantages over Boost.Function:

对于function的==和!=的操作符的使用我一开始感到比较难以理解。就是说一个function的对象与另一个function对象能够比较吗?这个有一点点像是比较两个指针变量,他们本身的地址绝无相等的可能,因此只有比较他们指向的地址的相同与否,同样的为什么这是一个反例呢?比如以下为什么是一个显而易见的错误呢?为什么编译报错呢? 其实查看function.hpp就明白压根operator ==就没有重载这种两个function对象比较的形式,你要比较的目的是什么呢?你要查看你的function指向的是什么函数指针!比如:
一个原则就是function指向的函数指针才是我们需要比较的目标,不管function是使用copy还是boost::ref获得的都是相同的函数指针。其实应该有一个很浅显的道理就是函数指针毕竟不是一个真正的变量,在编译时候就是确定的,是无法改变的,这里的函数指针我指的实际上应该是函数地址,它并不是一个变量。boost的function对象在我看来是一个比std的functor更加强大的东东,因为使用上是更加的直观,当然两者不是替代的关系,我只是说function对象是一个函数的包装,它同样可以包装functor,就像上边的例子,它对于成员函数的包装完全是做到了高超的透明化,只是多了一个额外的类指针参数的free function,甚至可以直接使用std::mem_fun搭配std::bind1st来变身为一个普通函数,当然这个是意料当中的。
而很多高深的用法需要研究这些测试代码。我只能再次的长吁短叹,boost真的不是那么容易的啊,c++11以后的新东西我感觉更加的难以接受,因为很多问题毕竟使用library来解决要容易过在语言本身来解决吧!这个就是我之前下载c++之父的讲座的原因,大师认为语言还处于发展阶段,而我已经无力进一步与时俱进了。我宁可借助于boost这样的库来间接的实现一些高级功能而不是依赖于语言本身的进步。纵然有局限,但是毕竟兼容旧的编译器和代码。

四月十七日等待变化等待机会

这个boost的测试代码里展示的target的用法让人头疼了许久。这个是我的体会,就是function_base里记录的函数指针类型如果我们需要直接使用的话那么需要依靠模板参数,返回一个函数指针的指针。比如我们有一个自由函数static int forty_two(){return 42;}那么我们使用一个函数对象boost::function<int()> f=&forty_two;那么怎样直接使用f的target来直接获取函数指针呢?首先,我们要明确&forty_two的类型是什么?经常写函数指针的应该很快知道他是int(*)();那么你应该知道这个判断的结果了吧?if (* f.target<int(*)()>() == &forty_two)。注意target成员函数返回的是函数指针的指针。究竟是否需要使用target呢?直接使用if (f == &forty_two)不就行了吗?我现在也想不出来究竟为什么需要使用target的应用场景。

四月十八日等待变化等待机会

我一直在困惑boost重新定义function adapter的意义,终于开始有点明白是原本stl的一些缺陷,这些始终是我的痛点,它的使用对我来说还是很难。这里给了一些学习的启发,就是参数不可能是reference of reference。这里是解释call_traits的意义,我开始有些明白了因为它能够帮助你把传入的函数指针的类型和参数的类型给出定义,那样子你才可能使用他们来定义类似negate之类的,因为你需要把传入的函数指针作为参数来再次使用?大概是这个意思吧?

function_type

This is the type of the function or function object, and can be used in declarations such as

template <class Predicate>
class unary_negate : // ...
{
  // ...
  private:
    typename unary_traits<Predicate>::function_type pred;
};

If this typedef were not provided, it would not be possible to declare pred in a way that would allow unary_negate to be instantiated with a function type (see the C++ Standard §14.3.1 ¶3).

param_type

This is a type suitable for passing the function or function object as a parameter to another function. For example,

template <class Predicate>
class unary_negate : // ...
{
  public:
    explicit unary_negate(typename unary_traits<Predicate>::param_type x)
        :
        pred(x)
    {}
    // ...
};

Function objects are passed by reference to const; function pointers are passed by value.

总之,目前来看boost的这个functional对我来说是很高深的东西,比如怎样实现negate呢?
出去散步回来又想发一通感想,就是boost的精深奥妙我第1001次的发出感叹,boost的新定义形态和一种新的语言无异,难道不是吗?c++新的语法中就有借鉴的部分。而学习的过程是无比的艰难,不仅因为其博大精深需要长时间的学习,更需要有广泛的实践否则就是学了也是屠龙术,没有用。这真的像金庸武侠小说中的上乘武功,你没有武功秘籍无法学习其心法口诀,但是学习心法口诀没有高人师傅指点,口诀心法根本无法领悟。但是纯粹靠别人指点而不系统学习心法口诀又很难真正学习这门武功。这注定了是一个学习实践再学习再实践的循环往复螺旋上升的过程。而这里心法口诀就是定义语义函数方法等,而师傅高人的指点正像是一个解说例子,而自己的实践一方面是阅读钻研理解这些解说文档例子应用,更重要的是如何能够和自己的实践经验结合真正的把boost的强大转化融入自己的实践应用中,这个才是更难的部分。而boost的广泛覆盖更注定了这个过程的难度,很多编程实践没有到达的领域那就是一个极其难以理解实践的领域。这就是为什么这么困难的地方。
顺便说一句闲话,目前流行全球的大瘟疫还看不到尽头,可是我已经看到了一个未来的结果。Here mark my words now: 瘟疫过后最大的赢家必定是中国!
我有些想当然的认为既然这里bind1st,bind2nd都可以使用成员函数指针那么使用functor应该也是可以的。是吗?这里从读代码的角度来看压根儿就没有可能。我其实本来对于std::bind1st/bind2nd就是一知半解,根本不明白所以然,所以压根不明白这个boost的实现是仅仅的补缺补漏为了解决之前的reference for reference的问题。其实按照stackoverflow的说法压根就没有用。因为你有了boost::bind何必还要bind1st?除非你就是要移植std::bind1st的代码而已?
首先对于std::bind1st我都使用很少,完全忘记了它是需要所谓的adaptable function pointer就是说有定义了函数的返回值参数类型等等,所以使用std::ptr_fun或者std::mem_fun或者mem_fun_ref。以下就是一个小的不能再小的问题,我之前就遇到过却总是不理解。就是如何定义一个functor并且使用bind1st传递固定参数。这里的关键是operator()函数必须是const因为我使用的for_each
而这里的move constructible的定义就是一个r-value难怪需要const
似乎是把functor或者说函数指针的情况都当作const,当然啊我的是functor,我想ptr_fun也许就不会吧?当然这里主要还是提醒我自己使用bind1st你默认就是用std::binary_function这类adaptable的functor,注意这里的binary_function的模板参数是返回值在最后的。接下来使用boost::bind就是为了剔除这类限制。(还不知道)
然后我需要bind1st
看到这篇非常详尽的关于央行数字货币DC/EP技术分析收藏一下。顺便说一下,我想把一些无用的图片文件删除用了这么一行脚本: 这里grep -c实际上返回找到的行数,脚本支持negate(!)。

四月二十日等待变化等待机会


四月二十一日等待变化等待机会

这些都是学习笔记,随时翻看更有增益,子曰:学而时习之。
  1. 这似乎是一个很简单的工作但是我却茫然理不出头绪。我想实现一个类似std::generate的函数生成随机数:这里的错误就是for_each根本不可能做到_1=因为它是一个const的*iterator,至少理想是这样子的。所以我的赋值根本无意义。这里再次阅读boost的原作者的注解我才刚感到羞愧万分,只怪自己读书粗疏的毛病不改,妄加揣测,贻笑大方。
    Strictly taken, the C++ standard defines for_each as a non-modifying sequence operation, and the function object passed to for_each should not modify its argument. The requirements for the arguments of for_each are unnecessary strict, since as long as the iterators are mutable, for_each accepts a function object that can have side-effects on their argument. Nevertheless, it is straightforward to provide another function template with the functionality of std::for_each but more fine-grained requirements for its arguments.
  2. 但是出人意料的是这个随机数是被填充到了vector的每一个格子。就是说我传递的是rand的一次返回值常数而不是在for_each里每次都调用这个pred,类似于lambda在使用类似于boost::bind把rand作为一个常数绑定了。我意识到我如果能够传递一个函数指针而不是绑定参数int也许能够,但是怎么能够呢?这里有一个细节就是boost::bind和boost::lambda都有定义这样子的placeholder如_1,_2,_3...那么当包含这两个库的时候编译器不明白你用的是哪一个,所以需要使用名字空间全部来:对于bind,它是boost::placeholders::_1,而对于lambda他是boost::lambda::_1。我一开始使用boost::function来达到类似于lambda的工作,但是也是卡在同样的问题上。不过首先我要确认我使用function的做法是可行的: 那么我使用boost::function+bind得到的是什么呢?
  3. 从结果来看似乎这个是一个boost.1.65的一个bug,我gdb之后感觉function_allows_small_object_optimization导致的一个问题?也许使用新版的boost可以解决?现在回过头来看当初的怀疑是错误的,因为for_each没有赋值的功能,原本也不主张你去改变数组元素,因为pred都规定是传递一个copy或者const reference,乃至于functor的operator都要是const,那么我的错误在于没有使用lambda::_1=module。我的原来代码放在transform上是对的,这个我在之后的一天已经意识到了,现在重温感觉加深记忆了。
  4. 当然更大的可能是我滥用了boost的语法,因为如果我改动如下根本编译不过:这个也许更能说明boost实现上的问题???这个是对于boost::ref的错误理解,需要好好阅读掌握。
  5. 所以,我的解释是也许强制使用boost::ref导致编译器检查函数指针类型,而不使用的话,传递的是一个不可用的函数指针运行期由于优化的关系直接就不去调用非法指针了。 似乎另一个证明是这个判断为false: module == &rand换句话说我这个函数指针压根没有指向我希望的rand函数。我是昏了头了,我已经直接调用module(15)来检验过我的函数调用了,没有问题的,应该是boost的问题
  6. 这个实在是一个笑话,因为我根本就是使用了错误的函数,for_each不是用来修改成员的,我需要的是transform,难道我从来都没有用过transform吗? 这里还有一个细节就是lambda的使用是可以支持operator的,多个operator的表达式用','隔开,但是每一个表达式里必须要lambda::_x,就是说以下两种写法都是不行的: 第一个是因为第二个表达式里没有lambda不接受现在看来原因应该是endl导致的函数重载,第二个是因为endl或者"\n"之类的使用了一个不同的函数(就是cout<<重载了不同类型而重载函数有限制)这里有很详细的解说。所以,我不得不使用'\n'因为这个char和int是兼容的吧?这里还要强调一点就是lambda的用,链接的两个语句并不是并列执行的,而是第一个为初始化只执行一次,并且每个语句都需要lambda::placeholders作为lvalue,如果不行的话也要使用lambda::var包裹其他非lambda变量,这个是理解lambda语句的一个误区,我现在才明白过来。
这个是我关于央行的数字货币的一些个人想法与理解。我以为央行发行数字货币的成功与否取决于是否能够真正做到匿名性,因为发行DC/EP的目的不是模仿微信钱包和支付宝,而是否能够实现国际化与被消费者广泛接受就是要做到真正的匿名性,否则就没有意义了也不可能受到用户的接受。因此,我以为在发行的时候就可以像国库券一样由消费者选择是记名还是不记名。所以它的可控匿名性应该是由用户选择而不是央行强制的。这个是我目前看到的最大量的误区就是这个是央行的企图是对货币有更加全面的控制,我以为这个是阴谋论者的揣测。
这里是一个非常有良心的字幕下载网站。我现在要把一些我以前下载的没有字幕的video加上英文字幕,原因是我的smartTV播放的时候很多不懂得怎样选择字幕语言。当然播放器上也没有菜单来选择。所以我需要直接把字幕burn在video里。

四月二十二日等待变化等待机会

我根据这里的boost::bind对于functor的做法发现推理operator的选择似乎有个bug,现象是这样子的,就是如果functor有两个operator而且他们的参数类型数目相似的话仅仅区别于函数返回值是无效的,就是说在编译期虽然bind正确的绑定了返回值不同的operator,但是在运行期似乎又选错了。这个是很令人难以理解的。我直到才发现了问题的根源,这其中的问题相当的复杂,一方面是我错误的使用boost::bind而不是使用lambda::bind,另一方面我没有意识到transform是期待你的functor返回和lambda::_1同类型的,那么我无意识的设定了错误的返回值voidoperator(int),而lambda非常的强悍自作聪明的选择了唯一可能的另一个operator,这里似乎没有const的问题。这个问题我还没有明白。
  1. 首先我设计了两个类似的functor,其中RandomModulus1的两个operator参数类似返回值不同,此外注意一个是const,这个很重要因为for_each和transform不同,前者只能接受const的pred,那么要求它的operator也必须是const,这一点就已经明确体现了在编译期bind是能够正确的区分两个operator的,因为从函数类型的角度来看返回值并不是函数签名的一部分只有参数和const这类decorative才是,那么boost::bind的模板参数仅仅依靠函数返回值来确定是哪一个operator是令人难以置信的,所以,我对于那种portable的把函数类型作为bind的第一个参数来用最原始的bind的方法是可以理解的。
  2. 我的测试程序其实很简单就是把一个数组初始化为随机数,这个使用transform并调用functor的返回值为int的operator,这个似乎对于两个functor都是没有问题的。在RandomModulus2我故意把operator的参数int去掉为的是和另一个operator有所区别,看下面的解说就知道。
  3. 对于RandomModulus2的初始化数组也是类似的,不过因为我有意把相应的operator的参数拿掉所以transform调用的pred也不同。这也进一步说明在编译期是正确的。
  4. 第二步是把数组里的数字打印出来,这个在两个functor里都是一样的必须是const否则编译也通不过。因为这个使用了for_each而他的pred是一个const的object所以它的member operator也必须是const。
  5. 而这里是最奇怪的地方,对于RandomModulus1来说虽然在编译期bind正确识别了在for_each里应该使用那个const的operator,可是运行期它依旧调用了另外一个operator,这个可以从运行结果中在调用打印出的函数签名看出来。 这里的结果显示了错误的函数调用:
  6. 这里是RandomModulus2因为特别的让两个operator的参数不同注意const也是函数签名的一部分实际上两个operator的签名就算是返回值一样也是不同的根本没有模糊空间。 而它的运行结果是正确的:
std::generate其实可以使用transform来替代:
  1. 使用一个functor这里的generate的pred是不需要参数的,这个是transform有优势的。当然generate是一个更加简单的函数。
  2. 这个是generate
  3. 这个是使用transform
我再次翻看boost::lambda的开篇例子感觉是有些误导的,因为这个例子是把一个数组全部初始化一个常数,这个始终就是一个常数因为你使用rand就像我想要作的那样始终不行。

四月二十三日等待变化等待机会

我始终想用lambda去实现一个初始化数组为随机数的方法这并不是一个完美的解决因为它调用了两次transform,而直到左五月五日我才找到办法,因为这个是一个非常常用的。但是至今没有找到很好的办法。
  1. 这个是我能想到的最接近的办法
  2. 因为我如果要把随机数限制范围rand()%100实际上不是一个函数指针而是一个数字,这就是我不能使用 或者这个形式也是不行
  3. 我始终认为boost::lambda的这个introductory example是误导,因为_1=1并不是一个赋值语句,而是一个常量函数,因为它实际上就是 但是因为必须有lambda出现才能被认为被接受“改造”为一个函数,所以_1=1这个函数实际上被evaluate为一个常数“1”,因为本来a=1就是返回值就是1。所以它的本质是如何构造一个常数函数而我们使用_1=1就构造了这么一个常数函数。这就是为什么for_each里传递给我们的是dereference的数组的element给我们的错觉是element作为常参数最后居然能够赋值。recall for_each的实现 fn (*first);这个语句等同于*first=1。转念一想似乎也没有错因为*first=1;这个赋值语句有错吗?
  4. 我似乎脑子充满了浆糊始终转不过弯来,又回过头来否定之否定,因为如果赋值语句说法能够成立为什么这个rand函数只是第一次呼叫被当作了常数呢?结果却是一些常数呢?所以我还是坚持这里的_1=rand()是被evaluate成了一个常数而不是函数指针。因为rand()不是rand。前者是invoking,后者才是一个行数指针。顺便说一下,这个例子也是boost::lambda的introductory example,我真的很遗憾没有人提出异议吗?
  5. 我又发现了一个奇怪的现象就是list和vector究竟有什么大的区别呢?比如这个例子就可以 可是如果你改成list就不行?google了一下才意识到也许list天生就不支持sort因为据说sort是使用类似于quicksort之类的实现的吧?我也忘了为什么有这个限制。但是编译的错误非常的让人困惑因为它一直在抱怨我使用了operator-,我明明是用operator<。这就是我总是抱怨模板的地方,一旦出错那个信息是非常的晦涩难懂。
  6. 这个例子就是更加的离谱了,因为我连编译都不行。 我尝试了许久始终看到的错误就是说int* 不能和int** 兼容,这个是不错,因为_1的类型就是int*,那么我要怎么取的interator的地址呢?原文说
    The expression &_1 creates a function object for getting the address of each element in v. The addresses get assigned to the corresponding elements in vp.
    我表示怀疑,因为从for_each代码来看_1就是*iterator,那么你能够对它取地址吗?
  7. 这里的解说关于lambda的实参解说似乎不对。实际上不管是不是rvalue,lvalue,都可以编译。 我的简单的测试如下,完全没有什么问题: 结果是这样

四月二十四日等待变化等待机会

学习lambda简直就是一场噩梦,而且我越来越有着一个可怕的感觉难道boost已经放弃了lambda吗?是这样子的吗?
  1. 我发现boost::lambda的文档非常的过时了,比如这个关于unlambda的说法似乎已经被修正了。的确是修正了,我看的文档是1.55版的,不知道怎么搞的。在1.65版的文档中已经没有这个unlambda的叙述了谁说没有!lambda的文档似乎已经停止更新了,这才是我最担心的。。我还专门实验了这个nested例子,其实还是一个很好的例子,充分展示了lambda的强大我就算理解它的正确性都不容易这个是调用过程 这个是结果,你能理解吗? 怎么理解呢?第一个nested调用没有什么好解说的很普通的调用了nested,内部再去bind传入的foo函数指针调用foo。那么第二个nested呢?首先作为形式参数的bar在参数阶段就是绑定而已,真正的bar被bind之后才调用的。这有什么特别吗?早期的lambda也许是像宏一样简单的替换就是会有问题,比如我一开始就在想在型参阶段bind究竟把结果当作什么传入nested呢?似乎从作者的意思是已经把bind之后的返回值当作参数才有所谓的int而不是函数指针作为参数的错误,这一点让我很难理解也一开始这样子误导了我,因为bind毕竟没有invoke,所以作为参数的是函数指针而不是函数调用后的返回值啊!!!所以我被搞糊涂了。现在看来这个unlambda也许根本就没有用了,是早期lambda实现缺陷的补救而已。我又错了,1.65的文档是类似的,似乎压根没有人更新lambda的文档。
  2. 我找到了一个挺不错的boost的网站,不过还没有仔细看。这个网站太简陋了,纯粹是给初学者的广告网站。
  3. 我越是学习boost::lambda就越是胆战心惊,lambda是否走入歧途了呢?我们希望在c++语句中间插入脚本还是怎么呢?因为lambda已经成为另一种语言了,你看其中强大的switch我们需要吗?最初的lamba的吸引人之处是短小精悍的一些常用的无名函数语句,可是写成函数很多时候就是为了复用,如果你写了太多的私有函数,每次遇到相似的情景你只能拷贝粘贴,这是我们的初心吗?这个小小的初始化数组成01,我费了这么大劲,我觉得我还不如写一个循环呢!!! 你看我为了Index的奇偶值我要单独声明一个变量,同时为了if_then_else,我还要重复让变量var累加。这个是我需要的lambda吗?我觉得我敢肯定有很多程序员不喜欢这个风格。无端端的增加了debug的难度,和代码的可读性,我们得到了什么呢?能不能说这是奇技淫巧呢?
  4. 这个是一个小小的练习就是怎样读取文件并打印。因为每天也许都要用,所以,这种oneliner其实是我所喜欢的。
我觉得应该总结一下我最近这几天的学习了,其实大部分都是boost的官方文档,可是真正实践中理解掌握就是另一回事了。
  1. 首先是一些基本的关于function和bind的常识,其实他们和lambda有着千丝万缕的联系,掌握不了基本的function和bind就不要奢谈lambda了。首先是一个基本的错误观念,我以前不知道怎么建立的观念就是bind总是用来绑定固定参数的,也就是说只能把一个函数签名的参数减少而不能增加,这个是多么的奇怪的想法,对于函数来说参数你要不要用是你的事情,函数签名是可以任意的,当然你使用不存在的参数是有危险的,不过这个是另一个问题。以下就是我独撰了一个functor然后用function把它改造成了一个本来不接受参数的接受一个参数的函数指针。
  2. 对于像transform这样子的接受一个functor它有特殊需求就是必须有result_type,这个应该是bind的一个限制就是它不知道你要哪一个operator的函数,所以,我不得不自己定义了result_type给这个functor,所以,你就可以这样子调用它了。 如果你嫌这个麻烦的话,就用这个方式来bind<int>这个效果等同于定义functor的result_type
  3. 传统的不使用function和bind的话就是要使用所谓的adaptable的stl的functor adaptoer。比如 这种传统的调用就是和你定义一个自由函数一样的 这样子你在调用这个unary_function继承而来的functor也好还是这个自由函数myRandom都可以直接传参数给transform。所以回到从一开始推出functior/bind的原因,就是我们有现成的functor也许就是参数不太一致,我们不想改代码就能重用。
  4. 以上总结的都是针对functor或者自由函数,其实和lambda关系不大,就是说都是使用transform压根没有理由使用lambda的,只有使用for_each对于lambda作赋值动作才代表使用了lambda,但是这个使用是非常的tricky的,就是说我目前的猜想是这个所谓的赋值=更像是初始化,于是就有一个evaluate的问题。比如 这个结果并不是我们预期的随机数,而是第一次调用的返回值的重复 在我看来这个也许就是lambda的极限了,说到底它是一个参数替代,并没有在简单定义的函数操作中调用另一个函数的能力,这个让我想起了当初编译器课程里实现递归的困难程度,因为很多脚本语言是不支持递归的,看上去在一个函数里调用另一个函数似乎和操作几个参数的加减乘除一样没有什么特别的,实际上这个在实现层级是不可同日而语的。所以对于这个结果我就死心了吧,它也许是最大的可能性了,我要添加modulus需要另一个transform? 这个结果虽然我不满意但是还有什么更好的办法呢? 除非我去定义一个自己的myRand函数可是这个样子我何必要lambda?似乎这一切都说明了在这个方面我不能指望lambda,只能使用function/bind配合functor或者free function,而lambda的领域在于简单的操作参数,一旦有需求调用第三方函数这个超出了lambda的范畴?
  5. 我的结论是不要再进一步花精力在lambda上了,因为也许不值得,目前我掌握的已经足够了吧?

四月二十五日等待变化等待机会

看美国乱象

关羽刮骨疗毒术,
注射抗疫笑川普。
红颈持枪招摇市,
人猿何时曾殊途。
注:美国大统领川普日前被爆不通常识忽发奇想欲在人体皮下注射抗菌素以对抗瘟疫,实在令人震惊,以世界第一大国领袖竟然如此无知可想而知其国民普遍素质之粗鄙。 美国中西部民风彪悍,喜持枪,州法律亦纵之。因常年劳作于日光下,背颈通红,被戏称红脖子,有贬低劳动人民嫌隙,但彼辈不思进取,沉迷基督耶稣,排斥科学,幻想在美帝霸权保护下依然保持拓荒时代蒙昧不化之生活方式实在不可取,也不可行,此乃实实在在逆潮流之反智主义劣行,既挞之也怜之,并无贬低其人类属性之意。
  1. 这是一个高难度的动作,我从来没有学习怎样写模板的模板,比如这里就是我想要的。想法其实是再普通不过了就是你看到那么多的模板的container,实际上我要做的工作是类似的,比如显示其中的元素, 因为我们已经有了ostream对于众多元素的重载,我们完全可以依赖一个通用的iteration来调用cout。作者的说明和例子几乎是完美的,只是我还不愿意我依赖于c++新的语言特性,所以我稍微改动了一下 不过呢这里我还没有办法使用lambda直接调用,不得已我才只好用普通的const_iterator的循环,而且必须是const_iterator,普通的iterator有错误。我想这是因为我的传入参数container被定义为了const的类型的缘故。这里的要点是c++的模板参数也是支持不定参数...的,就是说你的第一个class C是确定的第一个参数,后续的不定参数可以使用class ... Args来代表。这个是比较烧脑子的,我曾经接触过几次这种不定参数在类似printf里总是心有余悸。
  2. 接下去就是顺坡下驴了,比如我重载了+=<<操作符并且后者还可以连续使用,为什么+=我不能这样做呢?难道是两者执行的顺序不同,一个从左到右一个从右到左?我自己笔记我自己都看糊涂了,我说的是不能实现连等比如返回值即便是container&也没有用。因为这两个是完全不同类型的operator,前者是赋值的,后者压根儿被归结在了算术操作符,(即便是所谓的bitshift也是?)我一开始误解为<<=,这个才是所谓的赋值操作符,我估计这个是同样不能实现连等的,只有<<才行
    这个是简单的测试程序
    运行的结果如下

四月二十七日等待变化等待机会

我对于央行发布的数字货币的原理机制有很多的疑惑,而搜索到的很多的文章的刨析让人觉得似是而非。这里一篇整个系统据说是比较关键人物姚前的论文,这个也许有很多的端倪可以窥究。
  1. 数字货币的定义:
    CBDC 在形式上就是一串经过加密的字符串.CBDC 表达式本质上是对货币制度主要构成要素及权属的加 密处理,是 CBDC 系统安全运转的基础.理想的 CBDC 与传统的电子货币并不相同,它以精巧的数学模型为基础, 模型中包含了发行方、发行金额、流通要求、时间约束、甚至智能合约等信息.具体来讲,理想的 CBDC 应具 备以下特性:不可重复花费性、匿名性、不可伪造性、系统无关性、安全性、可传递性、可追踪性、可分性、 可编程性 [9] .
  2. 那么数字货币的表达式应该是什么呢?作者首先指出了当前被普通人“誉为”数字货币代表的比特币的缺陷:
    仅表达特定地址的缺省单位下的数字货币数量, 这种方式被各种虚拟数字资产所采用, 这种表达实质是抽象、概念化的Token, 无法具体表达实际货币应有的属性.
    然后给出了自己的答案:
  3. 我对于货币发行顶层设计不再关心,因为很多文章大体说的不错也没有什么技术难度。而主要的是交易的细节实现。这里首先一个定义:什么是转移?
    CBDC字串转移是指将代表CBDC的加密字符串以数据包的形式在发送方和接收方保管CBDC的系统之间进行传输.
    不过呢,我看到这里才明白转移并不是交易的意思而是指的商业银行和中央银行之间的转移,这个问题其实解决起来还是容易的,还看不到作者的核心方案所在于。要接着读下去。 看到这个也就是明白交易实际上就是字符串的发送与接收过程,双方当然要使用可信的加密方式,这个都不是问题,关键怎么解决一系列的
    不可重复花费性、匿名性、不可伪造性、系统无关性、安全性、可传递性、可追踪性、可分性
    才是核心重点。
  4. 看到这里我觉得没有讲出一些我寻找的关键的点,似乎在谈论一些实现的细枝末节的看似无关的细节。当然也许我是不得要领误解了作者。
  5. 这篇文章的一个好处你可以看到一些相关的文献,这一篇也许有些我所像看的细节,从标题来看是加密的实现机制?我还为此花了几块钱,不看就亏了。
  1. 早上看到的关于为什么不应该使用set的文章。还没有看完。
  2. 遇到一个根lambda有关的很奇怪的现象。比如我要用lexicographical_compare实现一个incasesensitive的比较字符串的功能,使用lambda结果死活必须使用locale的函数而不是普通的toupper,前者需要一个额外的locale参数,然后就是转化的错误exception: std::bad_cast这个问题是非常复杂的我直到差不多将近半个月五月六日才搞明白然后解决方案把我自己都吓坏了,如此复杂的一个caseinsensitive的比较值的吗? 而自由函数不存在这个问题 而且自由函数两个形式的toupper都是可以的 这里是自由函数的实现
  3. 怎么使用bind来绑定成员函数呢?比如我有一个vector我想把它的成员函数比如size,back当作一个自由函数来调用 我本来以为我已经明白,结果发现还是不行!一个没有参数的没有overloaded的成员函数是比较容易的,这个是轻松容易的 结果当然不出意料的 可是对于众多有overloading的成员函数怎么办呢?我差不多要吐血了才找到这个大侠的指点迷津。讲老实话我要是没有找到这个我真的要疯了。核心其实我应该有遇到类似的问题,就是说编译器在bind的时候无法自动resolve overloaded的成员函数指针,其实我心底里知道对于这样两个函数原型我要怎么告诉bind呢? 这里两个原型不只是返回值还有我头疼的const装饰。我目前先选择简单的使用static_cast来强制转函数指针的类型 结果当然是正确的了 。 至此我总算可以去吃饭了,实在是太饿了,这个真的是很不容易啊!
  4. 那么另一个const的overloaded back函数要怎么办呢? 你要怎么证明我们的f1和f2是指向了两个不同的back函数呢?以下是证明,f1是一个reference我们可以改变,而f2返回的const_reference是不能改变的: 结果是这样子的: 很明显的这个编译会出错的因为const_reference是不能改变的

四月二十八日等待变化等待机会

  1. 这个是昨天的继续,我打算把vector其他的函数也都用boost::function来“自由化”,原因是我在string的compare上我遇到困难,那么先从简单的做起吧,vector里有两个resize,参数不一样,其中后一个多了一个初始化参数,这类成员函数实际上是没有模糊的空间的编译器顺利的完成了。 这里是测试程序和结果 这里的结果显示了resize成功设定为4元素是默认的0,然后resize到6并初始化为25
  2. 很多时候读了这些文档我完全不理解,只有在实际中碰了壁才恍然大悟,比如在bind是否使用模板参数的问题上我原来就是不明白

    What is the difference between bind(f, ...) and bind<R>(f, ...)?

    The first form instructs bind to inspect the type of f in order to determine its arity (number of arguments) and return type. Arity errors will be detected at "bind time". This syntax, of course, places some requirements on f. It must be a function, function pointer, member function pointer, or a function object that defines a nested type named result_type; in short, it must be something that bind can recognize.

    The second form instructs bind to not attempt to recognize the type of f. It is generally used with function objects that do not, or cannot, expose result_type, but it can also be used with nonstandard functions.For example, the current implementation does not automatically recognize variable-argument functions like printf, so you will have to use bind<int>(printf, ...). Note that an alternative bind(type<R>(), f, ...) syntax is supported for portability reasons.

  3. 关于overloaded函数指针这个例子非常的好解说,可惜我还是没有找到我需要的。
  4. 这个是我一直想要收藏的关于操作符的语法,而这里是关于operator的大全,我感觉哪怕学了很多年的c++编程也不一定对于所有的操作符都使用过重载吧?其中的优先级也是经常很模糊的这个表的意义在哪里呢?因为我异想天开想实现一个operator[]的重载,那么这个根据定义是只能是类内部的,我怎么能够把它定一个自由函数呢?这个就是概念的力量。没有想明白这一点我还在黑暗中摸索。我不正是想把operator从类的内部解放出来,为什么不可以呢? 终于我找到了我的错误,这个是实现了一个vector的operator[]的bind的,其实就是static_cast的类型错误,但是函数类型完全不可以转变,一定要完全吻合。
    Operator name Syntax Over​load​able Prototype examples (for class T)
    Inside class definition Outside class definition
    subscript a[b] Yes R& T::operator[](S b); N/A
    indirection *a Yes R& T::operator*(); R& operator*(T a);
    address-of &a Yes R* T::operator&(); R* operator&(T a);
    member of object a.b No N/A N/A
    member of pointer a->b Yes R* T::operator->(); N/A
    pointer to member of object a.*b No N/A N/A
    pointer to member of pointer a->*b Yes R& T::operator->*(S b); R& operator->*(T a, S b);
    Notes
    • As with most user-defined overloads, return types should match return types provided by the built-in operators so that the user-defined operators can be used in the same manner as the built-ins. However, in a user-defined operator overload, any type can be used as return type (including void). One exception is operator->, which must return a pointer or another class with overloaded operator-> to be realistically usable.
  5. 在这个表里给了我们一个更清晰的总结,温故而知新吧。其中关于四个cast的定义我始终是模糊的,以前就是用c-style cast图省事。
    Common operators
    assignment increment
    decrement
    arithmetic logical comparison member
    access
    other

    a = b
    a += b
    a -= b
    a *= b
    a /= b
    a %= b
    a &= b
    a |= b
    a ^= b
    a <<= b
    a >>= b

    ++a
    --a
    a++
    a--

    +a
    -a
    a + b
    a - b
    a * b
    a / b
    a % b
    ~a
    a & b
    a | b
    a ^ b
    a << b
    a >> b

    !a
    a && b
    a || b

    a == b
    a != b
    a < b
    a > b
    a <= b
    a >= b
    a <=> b

    a[b]
    *a
    &a
    a->b
    a.b
    a->*b
    a.*b

    a(...)
    a, b
    ? :

    Special operators

    static_cast converts one type to another related type
    dynamic_cast converts within inheritance hierarchies
    const_cast adds or removes cv qualifiers
    reinterpret_cast converts type to unrelated type
    C-style cast converts one type to another by a mix of static_cast, const_cast, and reinterpret_cast
    new creates objects with dynamic storage duration
    delete destructs objects previously created by the new expression and releases obtained memory area
    sizeof queries the size of a type
    sizeof... queries the size of a parameter pack (since C++11)
    typeid queries the type information of a type
    noexcept checks if an expression can throw an exception (since C++11)
    alignof queries alignment requirements of a type (since C++11)

  6. google到了一个不错的c++网站,其中有很多的肺腑良言,比如关于宏的四宗罪,evil#1, evil#2, evil#3, and evil#4. 这些都是要反复重温的,我记得我有一次debug宏的编译导致的歧义根本无法用通常的debug方式找,最后是用preprocessor的输出一行一行的阅读才找到端倪。宏绝对是一个evil,就是在于它的强大与不可知。
  7. 这个网站的例子同样解决了我昨天的关于成员函数overloaded的问题,只是太晚了,我现在想要知道的是如何使用boost::function指向一个operator成员函数。也许根本不可能吧?
  8. vector的at和back非常类似也是分两个overloaded成员函数
  9. 终于我在iterator上遇到了类似string的问题,我完全摸不着头脑究竟是哪里的问题。这是不能编译的代码,唯一的不同之处也许就是iterator的类型了其实这是一个多么愚蠢的错误啊,但是从编译器报出的错误的确很难让人理解,我直到google到了这个解说才明白原来错误(你要加上boost::bind+在前面才能找到正确的页面) 的真实含义是因为我的function的函数签名不对那么十有八九是参数不对,我真的是糊涂,难道begin有参数吗? 所以正确的是这样子的
  10. 这个也是有些难度的,就是怎么取得成员函数operator+=的地址呢? 难道这个有出乎你的意料吗?之前我有困难一定是别的什么因素,我怀疑是iterator的问题。这个是测试程序和结果 结果你不相信吗?
  11. 其实你完成了一个operator的实现其他都是类似的,这里我不过是把operator换成了[] 测试程序和结果是很类似于at的。

四月二十九日等待变化等待机会

  1. 感觉就是机械的把一个个成员函数转化成自由函数,这个有意义吗?只是为了加深记忆还是图谋消磨时间?这个是string的operator+=的overloaded的一个形式接受一个字符串 结果当然是这样子的
  2. 顺便也终于解决了我之前一直不明白为什么iterator的错误,其实根本无关的,原来是参数个数搞错了 所以srtring::begin是这样子的 结果当然是这样子的
  3. 这个我遇到了这个哥们相似的locale导致的bad_cast的exception,我摸索了很久大概知道了问题的范围,可是还是不知道怎么解决 这个是很奇怪的问题,因为我的函数指针压根没有被调用的时候就这个样子报出了bad_cast的exception,跟踪到了 我看到和他相似的结果但是依然是茫无头绪。

    It calls __throw_bad_cast() because __i==46 which equals _M_facets_size.

    终于找到这个例子大概明白也许使用自己的facet才能避免原因当然是我的locale没有这个不知名的facet的安装了,可是我是不太敢和愿意费这么大劲儿做这个吃力不讨好的,肯定是我的系统库里没有安装相应的库吧?那么我打算把我的系统改回成英文试试看。我把我的语言修改回了英文错误依旧如此,看来这个和global的locale无关,纯粹就是facet没有生成吧。 现象其实越来越明确就是在lambda的调用过程中locale不知道为什么出错了,比如这么一个简单的功能 如果改用lambda就会有问题 前者实际上改为调用 问题是同样的前者怎么就知道调用do_toupper,而后者还是调用locale的facet的convert?
  4. 我看我只能开始学习boost::locale了
  5. 我深切的怀疑这个是lambda的一个bug,也许和之前绑定重载的成员函数一样有歧义? 总之我做了一个冗长的转换部分的解决了,但是我还是不清楚为什么for_each依然不可以: 结果是预期的,但是这个和我之前的for_each不是一类问题吧?
  6. 这里有一个小插曲就是我要绑定cout<<(char)ch;这样一个函数,那么很自然我认为ostream有一个类型为char的重载,可是实际上没有,有很多类型比如unsigned long, void*但是唯独没有char,于是我最后才发现是这么一个结果 这是一个奇怪的现象,因为这个是basic_ostream的成员函数,而我需要的全局函数。 注意我根本不需要写成那个样子因为ostream就是basic_ostream<char> 所以

四月三十日等待变化等待机会

  1. 终于搞明白了昨天的问题,就是我想去绑定的那个成员函数是basic_string的protected的方法,这个现在想也是合理的,因为operator <<最起码是可以处理char这个简单参数的,只是更复杂的参数才交给继承类来处理。我说的是这个函数 这个“小”插曲差不多耗费了半天的时间。
  2. 我现在依然面临着关于locale的疑惑,是否我应该完全放弃std::locale而完全转用boost::locale呢?这里是一个关于locale非常重要的概念
    • Even if your application uses wide strings everywhere, you should specify the 8-bit encoding to use for 8-bit stream IO operations like cout or fstream.
    • The default locale is defined by the environment variables LC_CTYPE , LC_ALL , and LANG in that order (i.e. LC_CTYPE first and LANG last). On Windows, the library also queries the LOCALE_USER_DEFAULT option in the Win32 API when these variables are not set.
    关于locale的标识这一点是天天在用的可是你知道这个boost标准吗?所以中文简体才是这样子的:zh_CN.UTF-8

    Each locale is defined by a specific locale identifier, which contains a mandatory part (Language) and several optional parts (Country, Variant, keywords and character encoding of std::string). Boost.Locale uses the POSIX naming convention for locales, i.e. a locale is defined as language[_COUNTRY][.encoding][@variant], where lang is ISO-639 language name like "en" or "ru", COUNTRY is the ISO-3166 country identifier like "US" or "DE", encoding is the eight-bit character encoding like UTF-8 or ISO-8859-1, and variant is additional options for specializing the locale, like euro or calendar=hebrew, see Variant.

  3. 对于collation有概念吗?我是没有我原来以为ctype之类的facet是干这个比较的工作的,现在看来似乎完全不是这么回事。这里的四个层面的比较是对于stl的升级吗?再强调一下,目前只有这个使用collator进行字符串比较的是唯一正确的办法
    1. Primary – ignore accents and character case, comparing base letters only. For example "facade" and "Façade" are the same.
    2. Secondary – ignore character case but consider accents. "facade" and "façade" are different but "Façade" and "façade" are the same.
    3. Tertiary – consider both case and accents: "Façade" and "façade" are different. Ignore punctuation.
    4. Quaternary – consider all case, accents, and punctuation. The words must be identical in terms of Unicode representation.
    5. Identical – as quaternary, but compare code points as well.
    作者使用了法語里的声调来展示这个比较的强大,可是这个似乎没有办法得到中文的检验,这个因该完全在于编码的设计,我依稀记得GB2312里面有这些设置,但是这个也许不是那么容易吧?至少我还没有理解标点符号被忽略是什么意思,因为我没有办法看到多一个或者少一个标点符号居然比较成功的例子。至于简体和繁体汉字的实验只能说明我太天真了,这个超出了计算机语言的范畴了。但是有一点是确定的就是这个是一个很好的不区分大小写的比较: 这其中的绑定当然是不必须的,如果让oneliner来写的话想必是这样子的:
  4. 概念其实很重要,这里有不少的标准定义。其中title case,case folding是我第一次听说,看起来挺有用的。
    • Basic Multilingual Plane (BMP) – a part of the Universal Character Set with code points in the range U-0000–U-FFFF. The most commonly used UCS characters lay in this plane, including all Western, Cyrillic, Hebrew, Thai, Arabic and CJK characters. However there are many characters that lay outside the BMP and they are absolutely required for correct support of East Asian languages.
    • Code Point – a unique number that represents a "character" in the Universal Character Set. Code points lay in the range of 0-0x10FFFF, and are usually displayed as U+XXXX or U+XXXXXX, where X represents a hexadecimal digit.
    • Collation – a sorting order for text, usually alphabetical. It can differ between languages and countries, even for the same characters.
    • Encoding - a representation of a character set. Some encodings are capable of representing the full UCS range, like UTF-8, and others can only represent a subset of it – ISO-8859-8 represents only a small subset of about 250 characters of the UCS.
      Non-Unicode encodings are still very popular, for example the Latin-1 (or ISO-8859-1) encoding covers most of the characters for Western European languages and significantly simplifies the processing of text for applications designed to handle only such languages.
      For Boost.Locale you should provide an eight-bit (std::string) encoding as part of the locale name, like en_US.UTF-8 or he_IL.cp1255 . UTF-8 is recommended.
    • Facet - or std::locale::facet – a base class that every object that describes a specific locale is derived from. Facets can be added to a locale to provide additional culture information.
    • Formatting - representation of various values according to locale preferences. For example, a number 1234.5 (C representation) should be displayed as 1,234.5 in the US locale and 1.234,5 in the Russian locale. The date November 1st, 2005 would be represented as 11/01/2005 in the United States, and 01.11.2005 in Russia. This is an important part of localization.
      For example: does "You have to bring 134,230 kg of rice on 04/01/2010" means "134 tons of rice on the first of April" or "134 kg 230 g of rice on January 4th"? That is quite different.
    • Gettext - The GNU localization library used for message formatting. Today it is the de-facto standard localization library in the Open Source world. Boost.Locale message formatting is entirely built on Gettext message catalogs.
    • Locale - a set of parameters that define specific preferences for users in different cultures. It is generally defined by language, country, variants, and encoding, and provides information like: collation order, date-time formatting, message formatting, number formatting and many others. In C++, locale information is represented by the std::locale class.
    • Message Formatting – the representation of user interface strings in the user's language. The process of translation of UI strings is generally done using some dictionary provided by the program's translator.
    • Message Domain – in gettext terms, the keyword that represents a message catalog. This is usually an application name. When gettext and Boost.Locale search for a specific message catalog, they search in the specified path for a file named after the domain.
    • Normalization - Unicode normalization is the process of converting strings to a standard form, suitable for text processing and comparison. For example, character "ü" can be represented by a single code point or a combination of the character "u" and the diaeresis "¨". Normalization is an important part of Unicode text processing.
      Normalization is not locale-dependent, but because it is an important part of Unicode processing, it is included in the Boost.Locale library.
    • UCS-2 - a fixed-width Unicode encoding, capable of representing only code points in the Basic Multilingual Plane (BMP). It is a legacy encoding and is not recommended for use.
    • Unicode – the industry standard that defines the representation and manipulation of text suitable for most languages and countries. It should not be confused with the Universal Character Set, it is a much larger standard that also defines algorithms like bidirectional display order, Arabic shaping, etc.
    • Universal Character Set (UCS) - an international standard that defines a set of characters for many scripts and their code points.
    • UTF-8 - a variable-width Unicode transformation format. Each UCS code point is represented as a sequence of between 1 and 4 octets that can be easily distinguished. It includes ASCII as a subset. It is the most popular Unicode encoding for web applications, data transfer and storage, and is the de-facto standard encoding for most POSIX operation systems.
    • UTF-16 - a variable-width Unicode transformation format. Each UCS code point is represented as a sequence of one or two 16-bit words. It is a very popular encoding for platforms such as the Win32 API, Java, C#, Python, etc. However, it is frequently confused with the UCS-2 fixed-width encoding, which can only represent characters in the Basic Multilingual Plane (BMP).
      This encoding is used for std::wstring under the Win32 platform, where sizeof(wchar_t)==2.
    • UTF-32/UCS-4 - a fixed-width Unicode transformation format, where each code point is represented as a single 32-bit word. It has the advantage of simple code point representation, but is wasteful in terms of memory usage. It is used for std::wstring encoding for most POSIX platforms, where sizeof(wchar_t)==4.
    • Case Folding - is a process of converting a text to case independent representation. For example case folding for a word "Grüßen" is "grüssen" - where the letter "ß" is represented in case independent way as "ss".
    • Title Case - Is a text conversion where the words are capitalized. For example "hello world" is converted to "Hello World"
  5. 这里是另一个作证我的问题的例子boost::locale::to_upper也是会抛出bad_cast的异常。究其原因也是由于convert facet没有正确安装有关吧?那么类似的按照oneliner的习惯我有个解决办法

五月一日等待变化等待机会

  1. 我遇到了更多的关于locale初始化不全的问题,这里遇到了as::percent不能正确显示的问题
  2. 这里是关于时间显示的问题,如果没有locale去imbue的话,cout是没有正确显示的,至少我的系统是这样子难道是我的icu没有正确链接?不对啊,这个是std的问题不是boost的问题啊?这个我后来读到boost的解释是cout这个并不会随着global locale的设置而改变所以你要自己设置cout,这个是合理的,否则任何一个程序改变了global locale会影响到系统里其他人的显示输出真是要疯了。 结果还是值的看一下的,其中最后的ftime我模糊记得和命令行的参数类似? 的确这里的解说是说这个ftime参数是ICU的(每次提到这个库的名字我就是胆战心惊啊)
    There is a list of supported strftime flags by ICU backend:

    • %a – Abbreviated weekday (Sun.)
    • %A – Full weekday (Sunday)
    • %b – Abbreviated month (Jan.)
    • %B – Full month (January)
    • %c – Locale date-time format. Note: prefer using as::datetime
    • %d – Day of Month [01,31]
    • %e – Day of Month [1,31]
    • %h – Same as %b
    • %H – 24 clock hour [00,23]
    • %I – 12 clock hour [01,12]
    • %j – Day of year [1,366]
    • %m – Month [01,12]
    • %M – Minute [00,59]
    • %n – New Line
    • %p – AM/PM in locale representation
    • %r – Time with AM/PM, same as %I:%M:%S %p
    • %R – Same as %H:%M
    • %S – Second [00,61]
    • %t – Tab character
    • %T – Same as %H:%M:%S
    • %x – Local date representation. Note: prefer using as::date
    • %X – Local time representation. Note: prefer using as::time
    • %y – Year [00,99]
    • %Y – 4 digits year. (2009)
    • %Z – Time Zone
    • %% – Percent symbol

    Unsupported strftime flags are: %C , %u , %U , %V , %w , %W . Also, the O and E modifiers are not supported.

  3. 关于时区是一个相当的复杂的问题,我看了开头就不想在看下去了,这个是据说boost的内部使用的一个时区的csv的表。我照例的保存了一个拷贝在这里。我觉得我只需要记住我是在America/Los Angeles简称PST就好了。
  4. boost的学习过程是非常困难的因为我目前基本就是把文档的成熟的例子都拷贝粘贴运行看一下结果而已就已经有如此多的问题,如果要看得懂boost的test例子就更难了,那可能就到了boost的开发测试阶段了?总之,密宗三十六,一成天下行来形容boost涵盖的广泛真是不为过,因为基本上几乎所有的开发遇到的问题都在涵盖范围,而且是一个高度浓缩提炼升华的水平,一个远远超过普通人提炼升华的水平,因为考虑到有些进入到新的c++语言特性之中。
  5. 我想跳过翻译这一块,这个实在是有些复杂翻,但是我似乎目前还不想接触,实际上在ubuntu的desktop里看到很多这种多语言翻译的例子,大概能够理解吧。实际上ubuntu里的所有的菜单何尝不是用这个形式实现的呢?我听说这个叫做gettext。
  6. 我记得我在学习gb2312编码的时候曾经尝试过这个boost::locale:关于charset的转换的功能,这个是一个验证程序就是说看看是否真的在gb2312与utf-8的编码输出是否是正确的。原因是我经常被各种编码所迷惑,我也不敢确定我看到的是什么编码。比如uchardet总是报出一些我无法理解的编码。 随后打开gb2312.txt的文件我们看到的是 而打开utf8.txt看到的是 很自然的我为了保险起见使用midnightcommander来查看的。我开始学习hexdump来输出更方便。
  7. 这个是纯粹的实验如何转换编码,最主要的是利用一个locale来告诉传入的字符的编码,我在回忆任何语言转换为utf8是否需要知道原来语言的编码呢?这分明就是废话,每个语言的原来编码方式必须知道才知道几个字节,这个难道不是显而易见的吗?不过我是还在疑惑我直接把究竟charset我要怎么表达我的编码是GB2312,似乎是不行的,这个不是所谓的charset,而是编码方式,总之我必须使用locale来表明我的编码方式:

五月二日等待变化等待机会

  1. 我对于boost::locale::conv::to_utf上加上boost::locale::conv::stop并没有抛出异常感到意外,这么看来对于GB2312的所谓非法字符iconv之类报错了,但是boost的转换函数却没有能力识别?这个也许是可以理解的吧,这个也只有看过源代码才能下结论,但是locale::conv不是使用iconv吗?总之我不确定。
  2. 尝试了一下所谓的boundary,感觉这个东西可能不是那么靠谱,因为这个问题太复杂了,你想替中文断句,这个还是挺难的,因为如果仅仅依靠句号倒是简单,可是这个有什么值的呢?我自己使用句号惊叹号查找不是一样吗?断词?也很难,我感觉文档里的说法更像是在日语中有些应用,也许日语罗马字更规范一些吧? 偶然的重读了以前的随感感触良多,沧海桑田,物是人非,星转斗移,今是昨非,不可言书。
  3. 我发现合并多个视频不能简单的使用多个输入文件而需要这样:

五月三日等待变化等待机会

  1. 我忽略了locale关于各语言的显示问题,跳到了我比较感兴趣的时间日期的显示问题。正如我之前说的,我现在几乎就是nobrainer的把boost 文档里的现成的例子代码拷贝粘贴运行一遍算是学习,可是就是这么简单的过程我还遇到了如此多的问题,需要自己debug添加很多初始化代码,比如这里我需要初始化global locale,这个似乎在很多英文为语言的使用者比较少吧?否则文档作者也不会这样子选择这种hello world代码,让新用户去debug。总之,我想说的不是那样的容易。 原本的例子不需要初始化global locale,但是我的系统有很多的问题,总是抛出bad_cast的exception,并且cout.imbue不使用我的locale也不能显示正确。 结果是有意思的,因为我还是第一次意识到怎样显示某一个日子是星期几的方法。
  2. 这是又一个计算每个月有多少天的办法,boost在这方面确实很强大,这里我等于是把之前的例子重复了一遍,而查看calender_facet才发现它明确要求global locale必须设定否则就抛出bad_cast 结果是这样子的
  3. 这里的关于日期的例子困惑了我好久,首先是关于date_time的constructor的问题这个 究竟调用的是哪一个constructor呢?首先, year(2010) + february() + day(5)是个什么东西呢?我一开始以为是calendar,后来gdb发现是date_time_period的组合叫做date_time_period_set,然后唯一对应的是唯一的解释就是locale被转化为了calendar。怎么做到的?当然是所谓的convert constructor之类的,就是这个。所以,在写c++代码的时候要非常的小心,因为编译器会很“体贴”的帮你转了一些,可是有时候这个不是你的本意就惨了,你很难发现的。所以,很多时候开源代码里面都强制要求constructor必须加上explicit禁止这种转化。
  4. 在这个基础上我做了一个我自认为很有用的东西就是求算出今年农历大年初一是几号?这个不容易吧?这个是让我引以为豪的东西,我为此高兴了好几天啊。不过现在都忘记了。 结果就是这个大瘟疫元年爆发的农历大年初一

五月四日等待变化等待机会

  1. 我把之前遇到的问题再次看了一遍,反复尝试看重复一千次是否同样的代码会有不同的反映(这个是搞笑的说法就是我还是不死心把之前的代码又重复的实验了几遍,主要是我记忆力太差了都忘了我已经实验过了)我的结论似乎有些不同了,还是关于操作符的不同的问题:你认为=和操作符<<是一类操作符吗?我现在看到的是前者是赋值操作符有一个大家族,后者是算术操作符也是一个大家族。所以,他们的行为不一样。 这两个操作符为什么不同呢?比如你认为数组里是常数还是变数? 如果你知道=初始化数组为常数那么这个操作符为什么不是常数 这似乎是风马牛不相及的问题,你可以说 lambda::_1=a++根本就是利用了lambda的初始化语句相当于循环就只执行了初始化,for_each的pred等于lambda::_1=100,void(lambda::_1);那么cout<<lambda::_1为什么就可以执行呢?这个是无法自圆其说的。
  2. 这个例子再次说明了两个operator的不同,因为functor说到底其实和函数调用是很一致的,那么为什么会成为常数呢?当然是说明lambda的解释不认同,一个是初始化另一个才是真正的lambda。说明什么呢?你使用functor,但是这个functor必须使用传入的参数作赋值才行。 这个结果是我们期待的randomized 可是你能够把你的for_each的pred写成lambda::_1=Module()()吗?当然我要把这个Module改动一下 结果却是常数

五月五日等待变化等待机会

  1. 每天我在boost的海洋里扑腾都会被它溅起的浪花所震惊。今天看到这个assign的部分真的是感叹,这个不是正是人们苦思苦想需要的吗?他的起源似乎是为了大师的一句断言:

    There appear to be few practical uses of operator,().
    Bjarne Stroustrup, The Design and Evolution of C++

    boost::assign需要#include <boost/assign/std.hpp>在使用namespace boost::assign之后可以使用大量的重载的操作符来初始化各种各样的的container。比如+=,这两个操作符被重载之后可以呼叫pushi_back或者是insert之类的方法。repeat也是一个assign的方法必须和操作符,一起来用。 结果是有喜有忧,因为有一个类似lambda的问题就是函数repeat把其中的rand()返回值作重复而不是反复调用它,而另一个多次重复的操作符,隔开的rand是被多次调用的。 很快的我看到了我的关于repeat问题的解决方案,使用repeat_fun。 不过我现在还想不出来怎么绑定modulus和rand,因为这个显然是不可以的 这个绑定的结果是rand()的第一次的返回值被当作一个常数使用了。也许我需要一个“复合函数”可是我还是想不出怎么办?
  2. 就在我沉浸在assign的惊奇中时候我忽然又发现了神奇的overloaded_function 当你一步一步发现c++的各种各样的特性可以使用一个一个的对象或者library来表达的时候你是一种怎样激动的心情呢?c++11及其后来的很多feature如果能够通过现有的语言来实现这种伟大你能理解吗?意味着现有编译器可以完全支持新的能力而无需升级! 其中有必要交代的是BOOST_TEST这个可爱的小宏其实很简单,如果你自己定义了就不用include那个boost/detail/lightweight_test.hpp。不过这个都是次要的,重要的是这个是使用模板把众多的函数原型返回,如果你把成员函数也作出来的话你岂不是实现了多态了吗?我读了一会儿还是不太明白,至今我还是不确定成员函数可以???
  3. 我再一次也就是1002次的赞叹boost真是无尽的宝藏等待着我们的发掘。(这个说的太儿童了,boost是一大批高手创造的,为了让我这些凡人来解决现实问题少走弯路的,可是我却感到很困难学习,这真的是情何以可堪?
  4. 终于我在“四月二十三日”提出的初始化数组的不理想的解决有了一个完美的解决,这个真是不容易啊,其中对于“复合函数的概念”我始终理解不了是这个迟来的问题的解决的原因。 这其中的最关键的一步实际上是“复合函数”,因为我原本概念中把它想的很高大上,实际上是我们的参数传入是一个函数而已,似乎用正确的词汇叫做evaluation,因为bind的对于参数的evaluation正是遵循了由里及外的过程,所以,函数的调用也是如此。 结果只是为了证明它的有效,走到这一步我花了半个月!boost实在是太难了!
  5. 这里再一次的强调一个基本的概念,在bind里只需要担心参数没有提供,而不需要担心提供的参数被丢弃!我对于模板错误是感到恐惧的,我想大多数程序员可能都是这样子的吧,因为编译器的错误非常的难以理解,只能出错了就瞎猜,而我一直有这个错误的概念就老是担心我没有使用传递的lambda就导致错误。
  6. 一个list_of在1.65是不能编译的,后来的版本已经fix了,我在1.72版本尝试是没有问题。assign的确很强大但是这个例子给人的感受似乎还需要一点时间才能比较稳定?总之后面的例子我感觉可能在我的1.65版本上还是有问题,那么就此打住因为还有locale的结尾没有扫清呢。 除了list_of之外似乎都没有问题,我稍稍的扩展了一下把我刚刚“发明”的小函数用在了这里来使用repeat_fun,这里想说明的就是我的boost::bind之后和函数指针是没有区别的。 结果是类似的
  7. 这个似乎是boost::locale的最后一部分就是一个新的facet包含了应该有而在std::locale里没有的部分比如language/encoding/country等等的至关重要的信息。这个看起来是如此简单的一个功能,而就在这一刻我又开始犯迷糊。因为当我要获取这个facet的时候编译器报错说它是abstract class,我着实被迷惑了。过了好一会儿我才意识到locale的facet是不能让你复制的,所以,你使用一个const reference是正确的方式。这是多么细小的一个细节啊。还有就是我第一次意识到使用boolalpha来显示true/false,日有所学,必有所成。 结果是值的看一下的,因为没有什么特别的
  8. Let's call it a day!今天其实挺辛苦的!加油!

五月六日等待变化等待机会

  1. 翻看半个月前四月二十七日的日记终于把最近学习的东西串接起来实现了当初的lexicographic_compare里的那个caseinsensive 比较的函数,可是它的复杂程度把我自己的吓坏了!事实上在我使用了中文编码的英文作为例子后我不再感到这个是无用的,相反他是非常必要的,因为生活在英文或者utf-8编码世界的人永远不明白世界上怎么会有那么多种语言,而对应一种语言有时候竟然不止一种编码,看看iconv -l里面支持多少种编码呢?中文就有好几种,当然很多是兼容的,比如GB18030是GB2312的超集,可是还有狠多不兼容的呢?你想自己写一个庞大的函数支持多语言排序吗?locale帮你作啊,这个就是使用locale里的各种函数的意义。 实际上我还是搞错了,这个lexicographic_compare返回值是bool不是int,所以我的结论是错的!因为对于非英语编码的语言你不能使用逐个字节的方法来转换大小写,我已经意识到了这个问题,可是我还是错了,只有使用boost::locale::to_upper这样的函数才行,因为从参数就能明白你必须要把整个字符串作参数一起转换不可能一个个字节的。而且看待吗似乎这个是一个boost自己定义的converter的facet怎么实现的不是std::locale里原有的代码。所以,我又一次出了洋像!我真的找到了答案吗?我的洋像出完了吗?没有我终于在五月十三日发现了问题出在boost::bind和boost::lambda::bind是两个不完全兼容的东西lambda只有lambda::bind才是正确的。 这里用到了多少知识呢?
    • 首先不要觉得多此一举使用locale里的facet的toupper而不是简单的使用std::toupper,后者参数是int这个对于编码是非英语的肯定是不一样的,最主要的是lexicographic_compare传给你的参数是char,你用一个int参数我不知道怎么才能骗过编译器,强制转型我好像没有成功。总之,没有好办法。
    • 其次,接着上面的问题就是locale里的字符转大写是有语言依赖性的,boost里举了一个法语要么是西班牙语的例子,大写连带着要转化什么其他的字母。我搞不清楚,反正不是英文字母固定加一个整数的做法,因为那样你自己写一个就行了。
    • 再次就是locale的创建问题了,普通std::locale或者global的locale很多时候是空的取决于系统的问题,最可靠的是使用boost的locale generator来创建,它的generate方法里初始化了各种各样的facet,还支持cache等等保证线程安全并且效率因为不允许复制,着一些都是原来std::locale里所缺乏的。否则你就会遇到bad_cast的异常,这个也是我这半个月来的苦恼所在。
    • facet类型ctype<char>的获得不准拷贝必须要使用一个const reference否则会有abstract class的错误,这个我昨天已经经历过了。
    • 你要bindctype的成员函数,而它有两个overloaded的形式,必须使用static_cast指明哪一个函数类型否则有ambiguious编译不过,这个是我最近天天在作的。
    • 你bind好了toupper然后要比较这两个结果,使用less那么就用到了“复合函数”(composition)的概念,这个是我费了好大力气才想明白的,等于内外两重的bind,参数先传给最里层的toupper返回值作为外面less的参数
    • 不言自谕的是我们实现的是strictly order也就是使用less的关系,所以是两个结果都是negative才是相等,如果使用less_equal那么就是两个都是positive才是相等,哪一个更好呢?我也不知道。
    • 为了说明有必要使用locale相关的toupper我有意使用一个复杂的例子,其中的输入字符串我故意使用中文编码的英文,因为中文本身没有大小写之说,但是在中文编码里包含了英文字母,这个对于只有utf-8的概念的人来说是不可思议的,但是你只要经历过中文或者其他语言就明白这个是常见现象,比如GB2312/18030就把所有的可打印的英文字符都编码了一遍。那么这个时候你对于字符串使用逐个字节的toupper是不对的,或者你自己写一个英文小写转大写加上'a'-'A'的差值也是不对的。对了中文字串的长度都是三倍。所以,我在我的程序后面加了一个naive的小小循环用天真的toupper来检验一下是否每个字符转大写是否可行。结果当然是不可能了。我这个例子恰恰说明我的这个实现是无法对付非英语语言的大小写转换的!最主要的错误就在于我失误了它的返回值我印象是int,实际上是bool,所以结论是全错了!lexicographical_compare(str1.begin(), str1.end(), str2.begin(), str2.end(), f)它只是说明了我的大写转换失败了!
    结果可以发现的确是中文编码的英文,因为三字节码通常第一个都是一样的吧?
  2. 我今天如果就只是实验了这么一个小函数也应该感到自豪了,这个确实不简单,我单单为了实验输入的中文编码的英文字符就折腾了半天,这需要我的智能拼音配合去编码一个utf8的中文编码的英文字符,听上去很别扭,我的意思是这些英文字符是在中文gb2312编码下的一个个字符应该每个字符是两个字节,然后转化为utf8编码后就是三个字节了。
  3. 我现在才觉得我太不熟悉算法这一块的函数了,因为完全可以使用generate我却使用了transform来初始化数组,实在是多此一举,因为多写了一个iterator 使用generate_n的时候我有担心指针越界的问题,似乎iterator超过end并没有继续前进?当然是危险的!只不过对于integer来说很多时候看不到它的危害程序没有垮掉而已。我在后面的例子里使用vector<string>就出现了莫名其妙的内存错误,这个比直接crash还糟糕因为crash或者更好一点有一个exception被捕捉了都是好事情,但是内存越界的错误很多时候很难察觉因为程序会莫名其妙的错误出现。
  4. 我常常被一些天天都要用的简单的问题所困扰,似乎我的记忆力有问题。我仅仅想产生一系列随机数并把他们存成字符串形式,这看似普通不能的工作,我却被困扰。这是为什么?因为看上去只有c++11才引入to_string,而之前的itoa这种c-style的函数牵扯到内存buffer是一个很不愿意使用的方法。然后我就找到了这个大侠在做了横向比较各种做法及其效率,这个看起来是数字转字符串的总汇了 我最后决定使用boost::lexical_cast,不过我内心深处对于它是充满恐惧的,它的原理是什么我还是不太清楚,遇到cast总是认为是强制内存翻译,是否有重新分配内存呢?再次阅读boost文档感觉lexical_cast实际上就是对于stringstream<<的包装返回string这个不就是我们通常要写的吗?所以这个是可以放心使用的,我对于这个函数的命名感到让人有被误导的嫌疑,cast是一个非常strong word,在程序员眼中是有危险的成分。当然看起来是要抛出异常似乎这个就是命名的由来?看起来当然是,不过这个看似简单的工作我又一次引入了这么复杂 的过程 在这个例子里如果你的generate_n使用超过vector长度的数值是会引发灾难的,因为内存被乱写一通,所以对于generate_n的使用要小心,我宁可使用generate,因为前者实在是有些鸡肋多余。实际上我把generate_n的参数改为vect.size()这个和generate有什么区别呢?再次觉得这个函数就是多余。这个是马上的打脸,因为马上我就意识到使用set插入的时候你必须使用generate_n

五月七日等待变化等待机会

  1. 对于set的几个计算操作是挺有意思,这个想必是每个学习离散数学的基本常识。不过我好像忘了这个symmedtric difference的概念,它实际上是两个集合的difference的union。而同样地,它和intersection的union等于两个集合的union。这里我就做一个小小的实现。这里对于我昨天关于generate_n无用的断言立刻打脸,因为对于set来说你是不能使用两个iterator来插入的,这个是set的特性,你只能使用insert_iterator来重复插入,所以一定要用generate_n。唉,我还是用的太少。 这个是运行结果
  2. 回过头来看我在开始学习lambda的时候遇到了很大的阻力,一方面也是这些introductory example有些不太恰当(这是个借口吗)比如不要使用for_each,而是使用generate那么就好多了。其实不然,使用generate更复杂因为起不到展示lambda的威力作用,最好还是transform 比如这个例子我当初就搞错了自己看错了还以为例子有问题,因为是两个vector,我以为是一个。 结果当然是不错的了
  3. 我花了一个下午做了一个使用lexicographical_compare的multiset并对它进行初始化生成随机数转化的字符串,这是一个多么复杂的一段代码啊,然后我发现这个竟然不是我想要的,我实际上是想要把字符串转回为数字进行比较,这个枉费心机的工作我何必要花那么大力气使用这么复杂的lexicographic_compare?这里用到了多少知识呢?
    1. 第一步是要绑定一个lexicograhic_compare的函数,它传入参数是两个字符串,这个容易吗?不要忘记了lexicographical_compare本身就是一个模板函数,他的模板参数是iterator
    2. 这里困扰我很久很久的是我需要获得传入两个字符串的begin/end的iterator,那么我是否可以直接呼叫lambda::_1.begin()呢?当然不可以,编译器怎么直到你传入的lambda有这个本事?它只知道lambda的类型,它是一个数字也好指针也罢根本不懂得怎么呼叫函数啊?(也许可以,可是我没有找到)所以我必须去绑定string::begin并且因为是有两个重载的begin,你不要忘记static_cast到相应的类型。第一步完!哇赛!
    3. 第二步声明multiset,这个是我从来没有想到的地方,居然我被卡壳非常久,这种意外肯定是一种无知与傲慢,仿佛二战纳粹德军入侵苏联的巴巴罗莎战役中突然遇到不在计划中的苏联重型坦克IV1斯大林I式,以至于整个两个德军装甲师被一辆斯大林I式挡住了整整两天的感觉!你知道multiset需要什么模板参数吗?一个当然是其中的元素类型string,第二个是比较functor或者函数的类型,然后它的constructor需要一个参数就是比较函数的实例。我就是卡在这里。我一开始把实例当作了类型代入模板参数,后来百思不得其解到底我的boost函数的类型是什么?我甚至于开始怀疑是不是一定要functor才行,因为我的是function,于是在想如何把所谓的function转成functor,这个真的是好笑,在使用者眼里functor和function有区别吗?那么问题是我自己定义一个typedef bool(*StringComp)(string,string);行不行,当然可以,可是我的boost的fcomp就压根不是这个类型啊,c++是强类型就是说函数原型类似你自己重定义类型当然就不兼容了啊。可是我实在是不明白我放着我的boost::function定义的类型不用却挖空心思想别的类型是不是有病啊?至此你才刚刚定义了multiset
    4. 后面这些都是之前的东西,我有意识的在随机数取模之后加上10凑够两位数,这个掩盖了单位数的问题。
    5. 当然了使用boost::lexical_cast转字符串是必须再次bind之前的函数就是我之前说的复合函数概念才能把一个函数的返回值作为参数传递给下一个函数。
    6. generate_n使用的insert_iterator这个是multiset/set所必须的,因为不能是push_back这类的back_insert_iterator。
    7. 这里又一个小插曲,我是希望我的代码完全使用c++98而不是依赖于新编译器c++11的功能,于是我倾向于添加-std=c++98,可是这样子模板的<<或者>>就需要加空格,这个我觉得很浪费啊。怎么办呢?
    这个结果并不是我想要的因为我强制产生两位数掩盖了真相
  4. 稍稍改造了一下现在我的比较函数是用数字本身的大小来比较。这个从原则上来说和上一个例子没有本质的区别,不过还是值的学习的。 结果好看多了
  5. 为什么说魔鬼在细节呢?就是说计算机里哪怕有一个标点符号出错就是错,根本没有什么99.9999%正确这种东西。错误就是错误,哪怕就是一个标点。我把我之前的lexicographic_compare里的实现一个string::begin/end的函数拆除来,结果就总是出现Iiterator乱走的错误,什么原因呢?我忘了传递参数是reference,因为你传递string的拷贝那么它的iterator就乱了。很容易的吗? 这里我说的传递参数使用reference是函数原型之前我随手把参数写成了string而不是string&,于是返回值时对时错很难发现错误 结果并不是你想象的,至少我忘了这个是从左到右的比较,之前我改成了数字的比较就不是这样子了。
  6. 我看到boost::algorithm::string.hpp有很多不错的函数是std的非常好的补充。明天应该先尝试这些。
  7. 我把之前的boost::locale::collator的compare找出来,发现它其实和lexicographic_compare是类似的不过就是有caseinsensive以及语言特殊的accent等的部分。 结果和之前的类似

五月八日等待变化等待机会

  1. 首先这个是boost::algorithm的文档。这里是作者的解释,在我看来是比较谦虚的。注意这个和我昨天看到的集中于string algorithm是完全不同的东西

    Boost.Algorithm is a collection of general purpose algorithms. While Boost contains many libraries of data structures, there is no single library for general purpose algorithms. Even though the algorithms are generally useful, many tend to be thought of as "too small" for Boost.

    An implementation of Boyer-Moore searching, for example, might take a developer a week or so to implement, including test cases and documentation. However, scheduling a review to include that code into Boost might take several months, and run into resistance because "it is too small". Nevertheless, a library of tested, reviewed, documented algorithms can make the developer's life much easier, and that is the purpose of this library.

  2. 我又一次被一个小小的问题引向歧途了,在我的eclipse的IDE里编译器使用的代码文件名是eclipse自动产生的makefile传递的是相对路径,而且current_path是一个运行期的directory,和代码文件所在路径也是不同的,所以要获得当前代码文件的绝对路径需要使用dll的program_location 这里需要filesystem.hpp和dll.hpp 结果是这样子的 这个做法远远比直接使用c函数好看的多了。
  3. 大名鼎鼎的boyer_moore搜索算法被boost包装在boost/algorithm/searching/boyer_moore.hpp连带还有它的两个改进优化算法在boost/algorithm/searching/boyer_moore_horspool.hpp和boost/algorithm/searching/knuth_morris_pratt.hpp 这里我用我自己的代码文件来搜索关键字boyer_moore。我一开始想直接在读取文件的时候使用istream_iterator,但是看到算法似乎需要random access的iterator,我就放弃了。因为istream_iterator只有实现了operator++这么一个operator,连operator -都没有实现,那么boyer_moore是不能够的。
  4. 很多时候你觉得你已经很明白了,可是实际开始就又糊涂了,明白back_insert_iterator的作用是什么,可是你觉得似乎没有必要,似乎直接自己绑定push_back似乎更方便,实际上不是这样子的,因为back_inserter可以自己推定模板参数,所以你不必每次都去写模板参数,而且代码是一种generic不需要知道这个类型是否是什么只要他们支持push_back,这个做法正是为了方便使用者,但是当你没有使用经验时候你很难体会。这里是stable_partition的一个例子,我之所以跳回std的这个是因为boost的algorithm::gather,我看不出两者有什么非常大的区别,当然后者对于iterator有了更宽泛的要求,似乎还支持内存极低的极限情况操作

    If there is sufficient memory available, the run time is linear: O(N)

    If there is not any memory available, then the run time is O(N log N).

    这里是结果,可以看到这个是所谓的stable,因为partition之后的相对位置是不变的。
  5. 我看了gahter的代码其实真的有些鸡肋,因为这个就是stable_partition的一个小小的包装变换,那么有必要使用它吗?你完全可以自己调用stable_partition,这里的pivot的应用价值我还不是很明白。感觉gather似乎是一个非常特殊的场景,实在难以堪当算法这个头衔,难怪作者在开头那么谦虚。一个人谦虚是需要理由的,因为不谦虚是不用理由的。
  6. 我发现我对于基本的hex输出居然不理解。我平常似乎没有用到std::cout.setf ( std::ios::hex, std::ios::basefield );我平常似乎使用cout<<std::hex;就可以了,为什么要basefield呢?这个我目前暂时没有时间去理解用就是了。核心的问题是cout只有对于int类型才会进行hex的转换,所以或者你强制转换类型到int或者你使用这个很隐晦的技巧使用所谓的operator+这个unary operator来暗暗的转换类型

    The readers digest version of how this works is that the unary + operator forces a no op type conversion to an int with the correct signedness. So, an unsigned char converts to unsigned int, a signed char converts to int, and a char converts to either unsigned int or int depending on whether char is signed or unsigned on your platform (it comes as a shock to many that char is special and not specified as either signed or unsigned).

    The only negative of this technique is that it may not be obvious what is happening to a someone that is unfamiliar with it. However, I think that it is better to use the technique that is correct and teach others about it rather than doing something that is incorrect but more immediately clear.

    但是这个技巧似乎对于lambda不起作用,也许需要额外的工作? 实际上我是在实验boost::algorithm::hex这个小函数,看上去平淡无奇,不过我是好奇心驱使想检验一下它确实是普通的hex,因为我有些难以置信这个就是所谓的boost的“算法”,难怪作者谦虚,只有boyer_moore的实现可能是需要很多努力,其他部分实在是乏善可陈啊。 结果实在是无聊的很
  7. 不过又一次的细节出魔鬼,我居然不知道stringstream的clear是清除错误状态,怎么清除内容呢?看了诸路大神的各显神通,我想尝试最简单的swap或者是=操作符。结果出现了奇怪的编译错误令我百思不得其解?终于猛醒记起我把-std=c++98加入编译设置现在不支持c++11了,而我翻看代码时候竟然没有注意到swap和operator=都是c++11才有的方法。结果我孤陋寡闻的发现stringstream是这样一个简陋的类,几乎没有什么方法,所以,唯一的办法就是重新设置str的值为空。这个实在是太简陋了。我也太孤陋寡闻了。

五月九日等待变化等待机会

  1. 长征不是难堪日,战锦方为大问题。我觉得产生随机数是一个简单的过程,我在这个小实验后遇到一个很难解决的问题,还摸不着头脑,现在饿得厉害,吃完再说。差点都忘了这个是干什么的,原来是想实验一下inplace_merge的结果,顺便想自己实现一下,应该是不难的吧?不过我现在真的是头昏脑胀中。 结果是这样子的可以看到inplace_merge的结果。
  2. 心情沉重,心绪如麻。究竟我在做什么呢?散步回来我才想明白了一件事,那就是这些天来我所作所为实际上是所谓的“函数编程”,就是类似从前短暂学习的haskell不敢说是,大概有些神似吧?我一直在抵触使用循环,但是似乎这个问题实在是无法不使用循环了。那么使用boost::foreach算不算循环呢?究竟为什么需要这么一个宏呢?它比我手写的iterator循环差别在那里呢?这个口号是真的吗?

    Make simple things easy.
    -- Larry Wall

    这个实在是我很不情愿的做法
  3. 这个core可能是更有意思的一个学习的目标。只是我现在急于寻找什么。
  4. 我对于dll一向是有兴趣。随后再来吧。
  5. 我怀疑我要寻找的是boost::MPL---Meta Programming Language。但是我还是不确定。我看了一会儿mpl我就快吐了,这个是高了一个维度的编程,我是一个生活在低维度的程序员却想去高维度的领域探索,这不是升维挑战是什么?我本来就该被大师们降维打击的。我其实就看懂了一句话:类型如果也可以像数据一样计算的话,那就是元编程(meta programming
  6. 我觉得我还没有准备好尝试mpl,还是从string.hpp开始吧。这里让我感觉我当初是不是有误并不需要使用boost::locale::generator的来初始化?比如boost::algorithm::to_upper实际上是这么一个等价的实现

五月十日等待变化等待机会

  1. 很多时候你就是把问题想的复杂了。比如你要实现一个简单的boost::begin/end要怎么做?看起来是一个很trivial的工作,可是一开始在模板参数选择上我就误入歧途,我在考虑模板参数的参数,结果就陷入了比较复杂的模板语法里了。其实你根本不需要知道container的元素类型,因为只有container的类型就可以了。 不明白我在说什么吗?以下这个是编译不过的,而且我也不知道那里需要加typename重新定义nested template。总之,看了boost代码我才意识到iterator的类型是由container决定的和其中的element无关的。
  2. 关于nth_element是很有意思的,它实际上可能是用于内部实现quicksort之类的用途吧?那么我们可以用min_element和max_element来检验运行的结果。 运行结果如下
  3. 无意中我发现了自己的大错!我用equal实现了一个类似lexicographic_compare的大小写无关的比较发现它并不能正确对待非英语编码,这个导致我意识到我在只有使用boost::locale::to_upper才是唯一的解决方法。这个取决于你是否需要把字符转换为大写然后比较,如果不需要的话你应该可以直接调用boost::collator的compare方法更加直接。我那里有一个oneliner的例子 首先这个是一个同样无法解决非英语转换大小写的问题,而它看上去是如此的吓人! 对于普通例子是没有问题的 结果是部分正确 所以最好的解决方案就是使用boost::locale::collator来compare 运行结果是正确的
  4. 这个是一个非常明显的事情,就是heap是一个“partially sorted”,那么我们可以使用make_heap一步一步来完成排序。 结果是排序升序的 它的效率是怎样的呢?似乎是O(N2)
  5. 我有一个天真的想法就是使用nth_element不使用递归来实现类似quicksort的功能,发现nth_element是一个不“稳定”的算法就是说它会“扰动”已经部分排序的部分,什么意思呢?你之前如果已经把一个区间处理完了,如果你继续对它处理这个区间内部的元素顺序会改变,这个是合法的因为不违背要求,但是对于全排序来说就是前功尽弃,所以从这个结果看到了你对于已经实现了"partial-sort"了部分不要打搅它。 结果就是一些元素的位置不停的变化破坏了排序结果
  6. 类似的你也可以使用min_element来实现一个naive的排序,就是另一个O(n2)的复杂度。名字我记不清了,好像我们打扑克牌的时候就是这么自然的排序的,可能就叫做max-selection之类的吧? 结果是正确的

五月十一日等待变化等待机会

  1. 使用nth_element实现了一个类似quicksort的范例。当然具体nth_element是怎样实现的对于这个所谓的算法的效率是至关重要的,不过呢,表面上这个算法是比通常的quicksort的效率高因为核心就是选取拆分的pivot,quicksort的最大的不平衡因素就在于这个pivot选取的不是正好在分段的中点,使用nth_element解决了这个问题可是怎么解决的呢?世界上没有免费的午餐,得到这个中点是付出了额外的代价因此它不可能比quicksort更好,甚至可以肯定是比quicksort差的多。 关于这个递归函数的参数,我一开始是直接使用iterator但是使用iterator&却有编译问题,不使用reference却无法正确执行。 这个结果并不能告诉我们nth_element究竟做了多少次的swap。看了一下代码nth_element内部应该就是用heap来实现的,那么这个make_heap的复杂度是什么呢?

    Complexity

    Up to linear in three times the distance between first and last: Compares elements and potentially swaps (or moves) them until rearranged as a heap.
  2. boost有很多重叠的部分,比如lambda是一个独立的小功能库,实际上这个设计很好使他更有生命力,而同时phoenix是一个注重Functional Programming的库,至少它自己这么说的。究竟phoenix是怎样一个东西呢?
  3. phoenix看起来是比lambda来的成熟的东西,最起码我的第一个小问题就对比lambda::_1和phoenix::arg_names::arg1后者在for_each里就没有编译的问题 注意这个phoenix的头文件是一个很头疼的东西,对于这个例子里因为有用到了operator<<所以你必须要包含头文件boost/phoenix/operator.hpp当然你一定首先要在基本的头文件boost/phoenix/core.hpp基础上。namespace的话最少的需求是using boost::phoenix::arg_names::arg1; 同样的lambda::_1是编译不过的 而且再次回看for_each的代码无论如何赋值都是不可思议的做法,作为hello world的例子怎么看都不合适只能给人以误导,因为在for_each的传入的函数指针所赋予的参数是一个dereference之后的类型是不能赋值的。__f(*__first);
  4. 我感到非常困惑的是spirit,phoenix,lambda,mpl还有很多很多似乎都是互相纠缠在一起,本来应该有一个更清晰的各自的定位,现在似乎大家都是在彼此竞争,让我茫无头绪。
  5. boost单单文档就是一个迷宫,我一定要小心翼翼记载我的路径否则稍不小心就迷失方向了,更不要提其中大量的版本里的些微的有时候是致命的差别。这就算是走迷宫的面包屑吧。这个是迷宫的入口。
  6. 关于remove_if是一个让我很意外的算法虽然肯定接触过好几次但是每次都是感到突兀。那么它的删除是和set/map这类内部排序的删除是截然不同的,虽然都是小小的细节但是你不去做过一次总是有可能出错的。 结果确实是删除了所有的偶数
  7. 以上的算法其实几乎可以适用于vector/list可是这个是很没有效率的,因为对于vector这种删除一个元素需要平移其他相应的元素是很不经济的。因此才有了remove_if这种算法把元素统统都移到末尾,而vector删除一个区间是非常有效率的,甚至根本不需要释放内存直接修改size就行了。 把之前set的代码稍稍修改就可以适用于vector了,小细节是back_inserter要比inserter有效率的多。 不用怀疑我们肯定有优化的做法来删除vector的数据
  8. 那么对于merge/sort的例子应该是很trivial的,我一直想不起来我之前要作什么? 结果是两个sorted的vector合并了,然后重复的元素被去除了。

五月十二日等待变化等待机会

  1. 关于partial-sort我一开始不是很明白它的用途,只能猜想是否是某一个算法的中间过程?后来想象应该是一个generalized heap。因为heap实际上是一个特殊情况,它只是定义了第一个元素。写到这里我自己都发笑,怎么可能?heap内部维持了相当的结构保证迅速实现pop_heap依然能够保持,而partialsort就是一个局部的sort,其实也是很有用的,因为很多时候你只需要前几名的情况后面都可以忽略了。我使用min_element简单实现了partialsort从结果就看出来两者的算法完全不一样,我的只是一个naive的示范,顺便实践一下mismatch的应用而已。 从解果看到两者的middle之后的分布很不一样,其背后实现机制很不一样的。另外不要困惑middle为什么没有包含在partialsort之内,因为根据定义它就是边界。其实我一开始对于iter_swap很有疑虑,很担心作为循环的iterator经过交换后会指向错误,但是再次阅读就发现不是iterator本身交换,而是指向的元素的交换。
    
    This is original unsorted vector:
    92,99,49,81,28,69,45,38,88,2,38,79,84,57,70,86,71,50,82,76,
    this is my partial sort:(middle[81])
    2,28,38,38,45,49,50,57,69,70,81,79,84,88,92,86,71,99,82,76,
    this is std partial sort:(middle[99])
    2,28,38,38,45,49,50,57,69,70,99,92,88,84,81,86,79,71,82,76,
    my partial sort mismatch: 81,79,84,88,92,86,71,99,82,76,
    std partial sort mismatch: 99,92,88,84,81,86,79,71,82,76,
    -----------------------------------
    my partial sort mismatch: 79,84,88,92,86,71,99,82,76,
    std partial sort mismatch: 92,88,84,81,86,79,71,82,76,
    -----------------------------------
    my partial sort mismatch: 84,88,92,86,71,99,82,76,
    std partial sort mismatch: 88,84,81,86,79,71,82,76,
    -----------------------------------
    my partial sort mismatch: 88,92,86,71,99,82,76,
    std partial sort mismatch: 84,81,86,79,71,82,76,
    -----------------------------------
    my partial sort mismatch: 92,86,71,99,82,76,
    std partial sort mismatch: 81,86,79,71,82,76,
    -----------------------------------
    my partial sort mismatch: 71,99,82,76,
    std partial sort mismatch: 79,71,82,76,
    -----------------------------------
    my partial sort mismatch: 99,82,76,
    std partial sort mismatch: 71,82,76,
    -----------------------------------
    对于min_element实际上是nth_element的一个特例,所以以上部分也可以把这个改成当然这里要使用not2来否定原本的逻辑这个纯粹是我在想用原本的make_heap的逻辑来实现所以才不得已使用std::greater,而这里直接使用std::less因为我的min_element没有使用pred,两者的逻辑是一样的。heap要注意它的逻辑是priority-queue其中的逻辑和排序相反,比如第一个是pred对于所有元素都为false才行。
  2. 我花了差不多一个下午才实现了一个nth_element,这个其实挺不容易的,因为不要试图使用max_element的来作因为这个过于严格,也就意味着不必要的工作。我使用make_heap把我们的[1..nth_element]做成一个heap,然后遍历nth_element之后的元素看到们是否应该属于这个heap,如果是就把这个元素和heap的最大元素swap。原理是什么呢?就是假如nth_element成立的话它的“右边”的所有元素都比他大,如果比它小的话就应该属于这个heap因为我们的nth_element是这个heap最大的元素。
    • 这里有几个地方花了我不少时间debug。首先,我们的heap到底要不要包含nth_element,如果要的话,heap的边界是要加一的。
    • 其次,pop_heap/push_heap要使用和make_heap同样的pred。而默认的是std::greater。这个是很容易搞错的。
    • 再次,比较heap之外的元素和heap的最大值的问题,你要采用原本heap的pred,那么heap的最大值在LHS,那么结果是true还是false呢?根据heap的定义如果这个结果是false就说明它属于heap,这个是我看到的解释:
      The elements are compared using operator< (for the first version), or comp (for the second): The element with the highest value is an element for which this would return false when compared to every other element in the range.
      这里的潜台词是heap的first和其他元素比较。可是如果是其他元素和heap.first比较是true不是更顺手吗?我没有看代码我很怀疑这个是作者的笔误。总之我为此头疼了很久。
    • 最后是一个很无厘头的东西我为了不再记忆pred的true/false,就打算使用一个binary_function来代替,结果not2(pred)编译不过,搞不明白。最后只好作罢。
    这里的运行结果显示我们一个clone的vector直接调用nth_element的结果和我的实现的结果的对比是一致的。
    
    This is original vector:
    24,10,35,89,47,71,49,62,97,79,9,89,73,55,93,85,20,6,56,74,
    this is my nth_element:(pivot[62])
    9,56,49,47,55,35,20,24,10,6,62,97,89,89,93,85,79,73,71,74,
    this is std nth_element:(pivot[62])
    10,6,9,55,47,20,49,35,24,56,62,71,73,74,93,85,89,79,97,89,

五月十三日等待变化等待机会

  1. 其实sort_heap是一个非常trivial的包装,因为整个make_heap的实现似乎都是为了这个sort的终极目标。不过呢,其中的比较的逻辑是确定的就是heap的默认pred是less,产生的是biggest element first,这样子sort_heap产生的是ascending sorted array。 我本来想使用reverse_iterator来尝试创建heap,似乎有问题我也没有仔细看行不行? 结果是比较我的sort_heap和std的sort_heap当然是一致的,虽然我还没有看sort_heap的源代码,可是这个几乎就是确定的。
  2. 实现copy很简单的是可以用transform,这个早就不是什么新闻,我模糊印象到处都看到这种说法说是copy是一个多余。但是这中间让我发现了另一个lambda可能的问题,我说是可能因为实际上使用phoenix的arg也是一样,这不禁让我怀疑也许这个是for_each的问题,或者这个根本就是所有lambda语句的问题因为第一个总是初始化?
    • 首先我做了一个简单用transform来实现的copy结果当然是一样的。你可以看到我一开始还担心lambda::_1不是一个函数指针不能被接受不得不以phoenix的val函数来创造,但是发现其实不需要。这个因为是Integer无论如何它俄返回值都是简单的也就是说int它也就是返回了自身。所以引出了我下一个例子使用string看看有没有问题。
    • 我使用string是因为他是一个非基本类型,结果同样的不需要val来直接使用lambda::_1就可以了。 但是它的结果是有趣的,我为了自己强调这个是string就专门在打印语句加上了引号,可是看结果缺少了半边的引号,只有第一个是正常的
      
      This is original vector in heap:
      18",54",9",78",94",56",29",90",19",23",65",40",16",13",19",50",51",79",92",5",
      This is after transform:
      "18",54",9",78",94",56",29",90",19",23",65",40",16",13",19",50",51",79",92",5",
      
      我故意把第一个打印的前半部分的引号去掉结果和第二句差不多效果,除了第一个数字。这个说明了什么呢?我无法解释,使用phoenix的arg1替代也差不多说明这个原因不像是lambda的问题,难道是cout的问题?
  3. 使用for_each来实现count_if是遇到了一些小困难,主要是我始终没有掌握lambda::if_then这个强大的工具,因为没有例子所以我也就不知道怎么使用。终于找到了这个例子才茅塞顿开。但是使用过程还是遇到了痛苦的过程就是我发现if_then不兼容boost::bind,必须要使用lambda::bind,这个真的是一个痛苦,难道是因为模板类型deduction不成功我要自己static_cast? 另外很痛苦的是我始终没有找到通用的把所有的操作符都包装成functor的库,我知道它可能就在mpl里可是找不到。所以迫不得已的我只好自己定义这么一个简单的increment来包装operator++严格的说++是有返回值的,我的包装需要返回它本身。不过这个在我的count_if里不重要了。 调用过程当然就简单的多了 结果是比较std::count_if和我的counter当然是一样的
  4. 你是否可以使用 transform来实现一个在c++11里实现的copy_if呢?我一开始使用for_each配合lambda::if_then能够实现count_if就想当然的认为这个是可以的,可是始终编译出现的错误:经过反复思考我才意识到这个是不可能的,因为这个也是我始终没有想清楚的地方就是if_then的不成立的条件下要返回什么呢?比如if (condition)return 5;可是在condition==false的时候要返回什么呢?这个是无法解决的因为copy_if和transform的根本不同在于前者并不是一定会执行那么多次,而后者是transform在于把一个range进行改造而不是选择性的挑选。 注意我这里实践了一下lambda::constructor这个就是一个identity的函数体,可以被lambda::bind来绑定,这里不能使用boost::bind因为返回的类型不同,我们需要返回合适的functor给if_then,这个是很重要的。 这里的结果其实就是一个纯粹的copy,因为这个我想要实现的copy_if不成立,编译不过。
  5. 总结一下lambda我要为我的一些粗率的断言向lambda库的作者道歉,它实现的if_then等等就是我一直想要的可是我却没有看到相应的文档。我现在才意识到不是没有文档而是我没有看懂这些文档。我实际上是看到了这些文档,可是压根没有概念,这个就是所有技术文档阅读的通病,当阅读者水平不够或者经验缺乏的时候,根本就是读天书一般。 我想提醒自己的是它需要的头文件是这样子的。if_then需要的是boost/lambda/if.hpp。相应的constructor需要boost/lambda/construct.hpp对于new_ptr和delete_ptr这个也是在construct.hpp定义的,比如这个小小的例子就是很好的应用这两个小functor的示范: 结果是说明cout可以很正确的输出指针类型。这个是挺不容易的。
  6. 对于一些非常普通的algorithm我似乎毫无印象,比如std::reverse_copy这个看似太trivivial的实现,但是很简单的它是否支持inplace reverse copy呢?代码看上去是没问题的,这个测试确实是很trivial的。 结果就是rreverse了结果 那么reverse就是很简单的reverse这里的源代码证实了它的确是使用iter_swap。 我的简单测试 结果如下
  7. 我也终于发现了for_each不能正确执行lambda::_1的问题,原因是必须要使用lambda::bind因为boost::bind是不完全兼容的。 结果当然是我们期待的。
  8. 这里我也发现了我声称我发现了lambda的bug,真的是很令人尴尬。 我把之前的例子做了修改,依旧是两个functor,第一个因为参数一样只能用const来区别重载 第二个functor没有什么ambiguous, 第一个测试例子是使用强制转换static_cast,这里关于functor的operator的地址如何取得是一个小小的考验,其实无他就是地址&RandomModulus1::operator()而已。这时候你怎样cast哪个就是哪个。但是一定要使用lambda::bind因为我之前使用boost::bind就出现了不可解释的问题我还以为是bug。 结果是可以预想到的 第二个例子其实有些多余就是两个operator参数有差别因此没有任何的ambiguious 结果也是平淡无奇的
  9. 怎么用transform来实现replace呢?这里需要用到if_then_else_return 结果应证了我的transform和std::replace是一样的把5替换成了10
    
    3,7,2,4,3,3,9,8,0,0,5,4,3,0,9,4,2,4,5,9,
    s1 == s2 
    3,7,2,4,3,3,9,8,0,0,10,4,3,0,9,4,2,4,10,9,
    3,7,2,4,3,3,9,8,0,0,10,4,3,0,9,4,2,4,10,9,
    

五月十四日等待变化等待机会

  1. transform基本上可以使用for_each来替代,前提是必须要使用lambda::bind 这个是replace_if的一个使用for_each的翻版,之前是tranform。 结果是5都被换成了10这样子的
    
    7,5,2,0,6,5,5,0,9,7,1,6,2,4,1,2,9,6,9,9,
    7,10,2,0,6,10,10,0,9,7,1,6,2,4,1,2,9,6,9,9,
    
  2. 实现partition是容易的,但是实现stable_partition就有难度了。我目前想到了一个类似stable_partition_copy的方法,这个名字是我杜撰的,效率也是及其低的因为是vector,如果list应该还好吧。 结果显示这个是stable_partition的结果,就是说所有小于5的成员按照原来的顺序排在了左面,大于或等于5的排在了右面,分割点在5的位置,注意这里5是一个碰巧的结果,就是说如果从左面第一个大于或等于5的数都会成为分割点。
    
    5,0,1,3,7,9,0,3,5,1,0,1,3,9,9,4,0,8,8,0,
    0,0,4,3,1,0,1,3,0,3,1,0,5,7,9,5,9,9,8,8,
    
  3. 虽然说partition的非stable版本很容易可是我也没有能够一次写对,还是出了一个小错误,就是想把代码写的紧凑想省掉一个判断似乎做不到。不过算法还是简单的,就是从左右往中间逼近凡是不符合条件的就交换。 我曾经试图把pred的not1单独声明为一个变量,后来才意识到pred的类型是一个很麻烦的事情,也才明白交给not1去deduce pred的类型是大家通常的做法。while(std::not1(f)(*right)) right--;另一个错误就在于作交换的时候一定要判断是否左右iterator已经过界了,就是if (left<right)否则最后一次有可能是错误的swap。 结果当然是不“stable”就是说有些元素的相对位置被改变了,虽然所有小于5的在左边,大于等于5的在右边。
    
    4,6,2,1,3,7,0,9,9,2,7,8,0,3,5,0,6,5,9,7,
    4,0,2,1,3,3,0,0,2,9,7,8,9,7,5,6,6,5,9,7,
    
    看了源代码我意识到我的实现是简单的在于我没有顾及返回值因为原来的函数要求返回指向第二组的开头元素,没有这个要求当然容易了。因为我的算法无法解决一个极端的情况就是当整个数组没有一个元素符合条件的时候我必须指向end,而我的last是从end-1开始于是这个就有麻烦。源代码当然也很巧妙因为我实际上就是要把刚才的极端情况单独解决了就可以了。
  4. 关于rotate_copy是比rotate容易的多了,因为任何inplace操作都是困难的。这个rotate_copy使用两次copy就能实现。 这里又是一个我忽略的问题就是vector的reserve和resize的问题,似乎对于之前的使用lambda的时候我观察到insert对于没有reserve的失败,但是这个却无法在普通的insert出现,我不能使用resize是因为我的目的地vector必须和源头一样而我又需要使用push_back。但是现在如果使用copy的话目的地必须先resize,因为它要直接赋值。 结果就是middle是新的begin。我的结果和直接使用rotate_copy是一致的。
    
    4,1,8,2,4,2,6,3,5,3,3,0,3,0,7,2,9,1,0,5,
    middle[6]=6
    s2 == s3
    6,3,5,3,3,0,3,0,7,2,9,1,0,5,4,1,8,2,4,2,
    6,3,5,3,3,0,3,0,7,2,9,1,0,5,4,1,8,2,4,2,
    
  5. 对于partition实际上我的代码可以写成使用find_if,这个是代替while循环的,代码更好读一些,但是我使用了reverse_iterator这个比较iterator是不能的,必须转换成reverse_iterator才能比较,而它的方向又是反的,伤脑筋的。 结果是把数组分成两部分,分割点在9同样的std::partition要求返回分割点的iterator这个需要额外的代码对付额外的极端情况。
    
    7,3,1,3,5,7,9,6,8,5,9,4,6,8,4,8,7,4,1,2,
    2,3,1,3,1,4,4,4,8,5,9,6,6,8,9,8,7,7,5,7,
    
  6. 对于lower_bound/upper_bound其实也是一个鸡肋,因为要求sorted,这个条件太苛刻了,所以,只能解释就是这个是set/map的内部函数开放出来而已。因为这个可以很容易使用find_if来实现,当然其中的逻辑比较费神,你无法把less通过其他的not1之类的导出greater,你只可以得到greater_equal。 我对于文档上说什么lower_bound包含,upper_bound不包含很头疼,于是我就输出区间,如果一致那就说明我的结果是对的,至于iterator本身是否包含在lower_bound内与否就是定义问题,与算法无关了。
  7. 这是一个失败的尝试,因为我本以为我可以用remove_if来替代partition,结果发现remove_if是一个破坏性的算法就是说在返回的iterator之后部分的数组完全是垃圾元素。不过呢?在debug这个“奇怪”现象过程中我才发现了怎样实现cout<<lambda::_1<<','这个很难吗? 这个意味着你需要两次lambda::bind因为这是两个函数,而糟糕的是operator<<(char)你是不能绑定的,因为我之前花了好长时间才看明白代码他是定义在ostream的基础类的protected成员函数,导致我只能去绑定put函数,不过这个不是更简单吗?我连static_cast都不需要了,不是吗?put是没有重载的。可是为什么继续绑定同样的operator<<不行呢?因为cout会直接把你的char当作int输出啊! 这里最复杂的部分是实现了cout<<lambda::_1<<','因为高亮部分就是第二次绑定put的部分。其中的插曲是我又一次忘记了成员operator函数的地址operator<<是不带挂号的。这个函数签名是比较头疼的,因为编译器要你完全一致不做任何的类型转换,比如int和const int&是截然不同的参数类型。同时另一个让人印象深刻的是第二次绑定的时候我原本打算按照惯例传递ostream*指针因为我第一次绑定传递的是cout的地址,而cout<<lambda::_1返回的是ostream&是reference,可是我在lambda::bind前面加上&取地址后看样子是不能正确执行的,编译没有问题,运行结果等同于NULL 指针,这个当然是我猜的因为后面的','压根儿没有执行。所以现在看来编译器对于类的实例到底传递的是指针和reference的要求是一个谜团。这个正如函数指针到底要取地址还是不要取地址,编译器似乎有一套逻辑。 这里的结果是比较有趣的,最后一行的高亮部分才是remove_if剩余的有效部分,其后的都是“垃圾”所谓垃圾只是我自己的一厢情愿,它只是保持了原来的结果,但是我期待着不切实际的实现partition,这才是异想天开。,这个害得我debug了好久,因为正好碰上我的笔记本开的时间太长了以至于eclipse报错说内存不足。我一度还以为是编译器的问题,其实remove_if文档里的代码写的一清二楚,为什么我会如此健忘?
    
    7,4,1,5,9,3,8,3,1,3,5,6,3,5,9,9,6,5,2,3,
    4,1,3,3,1,3,3,2,3,
    7,5,9,8,5,6,5,9,9,6,5,
    7,5,9,8,5,6,5,9,9,6,5,6,3,5,9,9,6,5,2,3,
    
  8. 其实刚才的失败的例子就是为了这个,也就是使用for_each来实现copy_if因为这个居然是定义在c++11的,当然这个要借助于if_then,之前我曾经尝试使用transform,那个是不可能的,但是很奇怪的我居然忘了使用for_each,也许是我那天头脑都发昏了似乎要吐了。不过现在看起来是比较容易的。 结果当然是正确的了所有小于5的元素都被按照原来的相对顺序拷贝了。

五月十五日等待变化等待机会

  1. 我做了一个自己的实验实现了一个看似无用的功能,就是把一个数组中连续相等的部分拷贝出来。它使用adjacent_find的功能,并且使用not2来逆反pred以便找到连续相等的结束点。 结果显示它把连续的相等的部分都拷贝出来了。
  2. 对于equal_range其实可以使用find_if来实现的,虽然要调用两次,但是第二次起始点是基于第一次的返回点,所以,效率是类似的。当然实际上这些算法都是比较简单的。 结果证明是正确的区间
  3. is_partition是c++11才有的新函数,其实这个也同样可以简单的使用find_if来实现。 结果可以看到我的返回的结果当然是true因为我之前已经把它做成了partition,返回的partition后半段的区间证明了这一点。
  4. 同样的新函数is_permutable让我困惑了好一会儿,究竟什么样子的数列才是permutable的呢?原本在我看来任何数列都可以next_permutation啊?后来才意识到不是的,因为要产生distinct permutation就不能有重复的数字,这个是常识。同时两个数列要彼此permutable(这个是我创造的词!)意味着他们属于两个全相等的集合,但是std的所有的set的函数都要求数列是已经排序的,因此没有办法使用,所以为了检验两个数列相等就只有循环用find来确保他们相互包容。总而言之,这个更主要是一个概念而不是什么算法。 我一开始脑子短路想着用adjacent_find去寻找重复的元素,后来才意识到这个问题,只好做了一个functor作两次find保证有且只有唯一的存在。就是要作两个循环去保证每个元素都在对方集合里有且只有唯一的存在这样子实际上就保证了自己的集合里每个元素都是唯一的存在的。所以文档说最坏就是O(N2)。 其实这个根本是一个positive的test,首先,我产生的数组必须是有next_permutation的,这一点我应该事先保证,当然我的取模到100碰撞的可能性是很小了。用adjacent实际上是不对的。这个算法压根有问题。所以我已经改成了自己的functor来检验唯一性的存在。
  5. 所以这是一个很简单但是又经常要用到的问题,如何判断一个数组里的元素是否有重复?这个当然是很简单如果排序的话。也许从算法的角度总的来说是排序更快但是这个需要额外的空间如果你不想破坏数组的话,同时拍完序你还是要调用adjacent_find之类一个个找出来,所以,我做了一个类似的functor来用find查找似乎更加的实用。 结果显示了那些元素有重复
  6. 关于上一个is_permutation的定义实际上是不准确的,因为对于有pred的版本来说是可以允许重复的,比如std::less_equal那么它的条件是否要放宽到两个集合相等呢?也就是说我需要检验每一个元素与另一个集合中一个固定元素的一一映射使得lambda::bind(std::logical_and(), lambda::bind(pred),lambda::bind(std::not2(pred)))为true?这个检验起来有些麻烦,还不如去直接看代码呢?看了一下代码感觉不是很肯定,但是最起码我发现我理解错了这里的pred实际上是元素相等的而不是顺序,我以前一直有一个误会就是permutation是lexicographic order所以我始终认为要建立元素之间的order所以pred肯定是顺序的比较,现在才明白其实permutation是集合的概念只要元素不一样就可以了。这个的确是混淆概念的地方,因为next/prev_permutation使用的是comp的pred,而is_permutation使用的是
    The elements are compared using operator== (or pred, in version (2)).
    难怪我被搞糊涂了。
    其实从这里也可以看出来如果Is_permutation也像next/prev_permutation接受用户的比较的pred,那么实现起来是很麻烦的,因为strict-order的pred可以用!pred and !not2(pred)来判断相等,可是对于semi-strict-order比如less_equal怎么办呢?你要pred and not2(pred)来判断相等。这个实在是太麻烦了,简直没有办法。
    关于is_permutation的代码我看了好几遍虽然简单就是一个地方始终不理解,最后自己重新照着写了一遍才在debug中明白了其中的真谛不禁绝口承赞其中之奥妙,这是一个优化,虽然现在运行了一个例子就明白了,可是当时读代码的时候始终不理解。纸上得来终非浅啊!只有实践才出真知! 我对于这个continue 一开始始终不理解,后来看了这个运行结果才意识到巧妙的优化。在下面这个数组里第一个3肯定是已经做过count_if的检验了,所以第二次遇到就不用再做了!这个是我没有想到的。当然直接使用count_if的确是简化了问题,它直接就囊括了是否允许重复或者不允许重复的两个情况。代码简洁多了。
    
    3,6,7,5,3,5,6,2,9,1,
    3,6,7,5,3,6,1,2,9,5,
    
  7. find_first_of也是比较简单的,只是他的名字会误导,因为它并不是和其他的find一族相似,是比较另类的。 结果是返回第一个被找到的元素
    
    6,8,7,9,5,7,2,7,3,2,0,1,1,8,4,3,4,3,9,4,
    9,1,9,9,5,
    found it:[3]:9
    
  8. c++11的新函数partition_point实际上也是很容易实现的,你可以使用find_if配合negate原来的pred,而这里唯一的trick是std::not1期待着result_type的定义,但是lambda::bind没有这么定义,这里告诉你可以用boost::function包装一下 结果显示了partition point和直接调用partition_point的结果是一致的。
    
    4,2,0,3,2,2,8,6,9,6,
    partition point:[6]:8
    
  9. 集合里的includes的实现其实很简单的,一个循环就可以了。 结果如下
  10. 实现set_difference还是有些难度的,就在于这个过程需要绑定好几层的绑定。其中for_each嵌套if_then等 结果显示我的结果和std::set_difference是一致的。
  11. set_union其实是比较简单的因为已经sorted了,所以可以直接调用merge。 结果也是比较std::set_union的结果是一致的。
  12. set_intersection几乎和set_difference一样我仅仅把std::equal_to改成了std::not_equal_to而已,当然set_difference要改成set_interesection来测试结果了。 结果同样显示我的实现和std::set_intersection是一致的
  13. set_symmetric_difference其实很简单的就是两个set_difference的union,所以,我仅仅再原来set_differemce的基础上再次调用了一遍而已。 结果也是一样显示我的实现和set_symmetric_difference一致的。

五月十六日等待变化等待机会

  1. 昨天太辛苦了总算把绝大部分的std::algorithm部分重温了一遍,早上起来本来想实现一下next/prev_permutation因为我的概念里认为不应该要求permutation有要求比较大小的额外条件,后来一想就明白自己的想法是错误的因为permutation要有顺序才能有所谓next/prev在没有上下文的情况下就能产生。我的想法是要有stateful的一个functor才能实现,比如在functor的constructor里记录下每一个最初始的元素的相对位置来作为大小比较的因素,原因就在于permutation不是集合的combination是有顺序的。我依稀记得上combinatary algorithm这门课的时候提过其实这个按一定顺序产生所有排列与组合也是一个算法,也有按照需要最小变化代价来产生下一个的排列的算法那么我们std的是产生人类最直观的按照lexicographical_compare的原则。想明白了这一点我决定参考看看bitset的算法,发现我的记忆有误,它没有产生组合的算法。
  2. 我转向exception感到我不太熟悉dynamic exception specification的机制,编译器警告说这个已经被废弃了。warning: dynamic exception specifications are deprecated in C++11 [-Wdeprecated]这个我原来就不怎么用没想到已经被废止了?我只是依稀记得工作上总是看到吸血鬼的代码总是这么声明我统统选择无视。究竟这个机制有多大的实用性现在看来确实有些鸡肋,扔出来的异常不是我声明的你能把我怎样?你就拒绝处理吗?我在实验中错误的把throw在设定std::set_unexpected之前结果呢?当然就是跳过了它。在这里面你要处理什么呢?你要发出最强烈的谴责吗?异常机制只能帮助你梳理错误控制流程。

五月十七日等待变化等待机会

  1. 现在我开始跋涉在uncharted area,对我来说dll.hpp和enable_if都是不太熟悉的,前者我曾经用过结果简单一实验就意识到有问题,首先sections是必定存在的,可是使用symbols立刻报错,我怀疑是这个需要section的名字作为参数,那么没有参数的形式是给谁用呢?不用参数的话就返回所有的symbol在一个vector里啊,我的实验的错误应该是别的原因。 这中间是一些及其低级的错误,因为我正在探索一个复杂的问题,始终摆脱不了这个低级的错误,最后注意力集中才看到这个小问题。 这样的随手一写的代码有问题吗?从functional programming的角度还是推崇的,因为我避免了任何的变量的声明,可是我始终遇到莫名的错误,后来才意识到sections返回的是一个vector<string>的对象,在for_each之类的回调函数是危险的,具体怎样子我只能猜测是做了一系列的拷贝,所以,都是temporary的如果我声明一个变量来承接,甚至引用都会没事。这个在普通循环里看不到但是在for_each这样子的调用你的functor的情况下就暴露了。因为secttions应该不是一个trivial的retrieve内部变量这样子的简单函数,所以我们随手这么写是一种犯罪,我不明白我现在是怎么这样子退步了?难道是因为我追求写functor习惯了FP的style所以忌讳写普通的变量循环的老套子?
  2. 其实困扰我一早上的是一个更加复杂的问题,我还找不到解决办法都快吐了先记下来吧。就是我之前的模板参数也有模板参数的一个简单的打印函数会导致string的ambiguious的问题。对于其他container没有问题,这个已经检验过了,上一次恰好我没有实验string所以没有发现,问题就是string 是一个特殊的容器,它本身又被shortcut实现了一下operator<<于是到底我是否要继续container的模板方式打印一个一个char呢?明显的编译器搞糊涂了。 这个关于容器的模板除了string都能工作比如 但是对于这些含有string的容器,包括string本身都有编译错误 问题的原因是清晰的,就是string本身就是一个容器,当他是另一个容器的元素的时候在调用cout<<str的时候编译器糊涂不知道应该调用我定义的模板函数还是调用std::ostream的operator<<。我尝试使用enable_if之类的发现问题不是一类问题,因为我并不是要增减模板参数。我也尝试使用template specialization但是无法屏蔽它,似乎因为函数std的那个是inline吧?最后我链接也会找不到吧?总之我是处于非常混乱的地步。
  3. 费尽心思后来很偶然的部分解决了这个模板函数的问题,原因是我只能定义我的容器类型为非const这样子就和系统的string的operator<<有所区别,但是后果是我破坏了string作为一个完整字串的输出,结果它和普通的vector一样输出一个个字符,这个让我烦恼不已。 注意我这里把函数原型的参数改成了拿去了const 这是测试程序,我现在不能再使用rvalue的临时变量了 结果是让人有些沮丧的
    
    std::ostream& operator<<(std::ostream&, C&) [with T = char; C = std::__cxx11::basic_string; Args = {std::char_traits, std::allocator}; std::ostream = std::basic_ostream]
    h,e,l,l,o, ,w,o,r,l,d,d,
    std::ostream& operator<<(std::ostream&, C&) [with T = std::__cxx11::basic_string; C = std::vector; Args = {std::allocator, std::allocator > >}; std::ostream = std::basic_ostream]
    first,second,third,
    std::ostream& operator<<(std::ostream&, C&) [with T = std::__cxx11::basic_string; C = std::set; Args = {std::less, std::allocator > >, std::allocator, std::allocator > >}; std::ostream = std::basic_ostream]
    set1,set2,set3,
    std::ostream& operator<<(std::ostream&, C&) [with T = int; C = std::__cxx11::list; Args = {std::allocator}; std::ostream = std::basic_ostream]
    1,2,3,4,56,78,
    std::ostream& operator<<(std::ostream&, C&) [with T = char; C = std::vector; Args = {std::allocator}; std::ostream = std::basic_ostream]
    c,5,o,8,p,
    
  4. 关于为什么不要partial specialization我不是很理解,只是模糊觉得类型不对,比如class T如果在代码中变成了T*究竟这个算不算specilization?我可能理解的不对。
  5. 整整一个下午都在被操作符重载的partial specialization的问题折磨。吃完饭做了一个实验证实了一件事那就是我所作的partial specialization应该是对的,只不过为什么operator<<不行我还不清楚。总之模板中带有模板参数是一个很复杂的问题。 这个是主函数,它唯一的问题是string也要当作vector来把一个个的char打印出来,这个显然不是我想要的。我觉得最大的问题是如果我for_each里使用了operator<<的话就导致递归定义,这个几乎是无解,我尝试着把for_each里也改为print来一个递归定义然后就始终无法编译。 于是我做了一个string的specialization 那么如何调用它呢?对于任何支持begin/end的容器都可以打印其中一个个元素。 结果是比较有意思的
  6. 我还是想错了因为没有办法实现递归定义导致我做的没有什么意义,比如说我要输出vector<vector<string>>就不行了。这个作罢吧。所以这个纯粹是一个失败的做法我学习使用dll::library_info尝试把每个section的symbols都装入这个vector的vector。结果为了打印还要再来一遍循环,这个将值无意义。 这里唯一值的地方是那个for_each我为了把vector<string>装入我的vector的vector

五月十八日等待变化等待机会

  1. 这个入门的关于dll的教程还是有必要温故而知新的。
    • 首先是这个plugin的概念,虽然我在工作中天天使用但是似乎没有这么清晰的认识,就是你定义接口,这个是一个虚函数,在以后的dll::import中它就直到这个虚函数表的大小。只不过我们一般都是通过factory来获取实现类的指针。这里dll的Import方法让你使用模板方法来确定获取的symbol的大小,当然函数还是要求你提供symbol name来搜索它。
    • 那么dll的制作过程你要使用一个宏extern "C" BOOST_SYMBOL_EXPORT它就是__attribute__((__visibility__("default")))这个是不是绝对必要的,因为动态库你不做处理这个就是default的。注意到这个做法要求你一定要使用"C"的函数装饰,所以,这个做法是有限制的。值的注意的是这个例子是把函数类声明在namespace中,而使用那个宏创建了一个alias应该是简化了import的难度。也就是说我使用dll::library_info可以看到这个alias是不带namespace的,这个是当然的因为是extern "C"嘛,不论是不是在namespace这个都是一个简单的名字。这个就是关键。
    • 关于编译动态库boost并没有什么特别的,g++ -fPIC -shared。而从import的角度来看,dll是依赖于boost_filesystem库的,同时需要动态调用库所以,-ldl是必须的。
    • 关于boost dll所谓的工厂模式是这样子的。说白了一个类的static 成员函数,问题是你返回什么?我们原来是使用指针的指针这个绕过了接口不断扩展不兼容的问题,当然是表面的绕过,实际上现在看来这个兼容是有限的,对于不兼容把它掩盖起来有时候更危险,这个是另外的话了。
    • 那么返回shared_ptr这个做法好不好呢?当然是好的,因为有些操作系统如windows的不同版本动态分配内存是变化的,谁分配谁释放必须在跨越动态库之后也要保证原因是运行期库有可能是不兼容的,Linux的情况要好一些只有在非常古老的gcc有这个问题,但是对于不同的Linux的distribution这个实在是难以预料,所以返回shared_ptr是非常必要的。那么指针的类型是否是实现类呢?当然不是,对于使用者只有被暴露了接口,甚至于这个工厂类的定义都是不暴露的。你所知道的就是一个接口的虚类的定义,甚至于这个接口都不提供任何实现类的大小,工厂类的创建函数接口也不暴露你能够用动态调用。这个不对,你不知道工厂接口怎么调用,所以这个是动态调用不需要头文件这里我们以前在C程序也是定义函数类型,不过接下去使用boost::function的模板函数变量这里使用import_alias获得工厂接口 所以返回的是什么?是函数指针,是能够创建接口指针的函数指针。
    • 从这里我们学到什么呢?在动态库那一端,工厂接口使用的是一个所谓的alias,他是什么呢?我们通过查询所有section来寻找这个alias "create_plugin" 注意看alias create_plugin存在于一个boost专用的section boostdll,这个就是那个复杂的宏BOOST_DLL_ALIAS创建的,它太复杂了我没有看懂,现在知道它在做什么了。
      
      	...
      ##############26:	boostdll:[1]#############
      create_plugin
      ##############27:	.bss:[0]#############
      ...
      
      那么如果不使用boost alias的普通的extern "C"是在那里呢?
      
      	...
      ##############26:	.bss:[1]#############
      plugin
      ##############27:	.comment:[0]#############
      ...
      
      天哪,为什么是在bss而不是text?你是否和我一样的吃惊呢?为什么?其实吃惊是完全无理由的因为plugin也是一个所谓的alias,就是说它并不是真的函数,而是函数的别名,所以是symbol。我都糊涂了这个是类的别名,类的symbol本来也是放在bss的。这个纯粹无聊。
  2. 不过这里有一点是很重要的提醒,可能是很多人都如同我一样没有想到的(这里的潜台词是什么呢?boost::dll::import_alias或者dll::import实际上是类似于dlopen之类的,那么当他的返回值被释放的时候,比如shared_ptr的指针出边界,它是要释放动态库类似于dlclose,而其他的symbol之类的都会失效,这个是让人有些意外容易被忽略的):
    [Caution] Caution

    Be careful: creator variable holds a reference to the loaded shared library. If this variable goes out of scope or will be reset, then the DLL/DSO will be unloaded and any attempt to dereference the plugin variable will lead to undefined behavior.

  3. 这个关于SFINAE的概念我还是不理解,这个例子似乎有些意思,吃饭后在说吧。这里是enable_if的文档。要理解enable_if一定要明白什么叫做SFINAE。

五月十九日等待变化等待机会

  1. 关于这个dll的linking plugin into executable例子折腾了我差不多一天时间,不知道是不是我的思路迟钝还是另有玄机,为何没有人遇到我的类似的问题呢?首先它的概念的理解就让我费了不少心思,究竟这个是什么样子的plugin呢?我花了一天时间就学习了这么一个例子,古人长夏唯消一局棋,而我折腾了一整天就看懂了这么一个例子!
  2. 首先什么是plugin?标准的定义是动态库因为主程序的编译不需要依赖于动态库的更新,所以,我们定义了一个不变的接口类,所有的实现类从这个抽象类继承来的,当然在动态库的实现与动态调用的限制你还不能完全做到对于实现类的完全无知,你当然做到了对于实现类的完全无知,甚至实现类存在的形式都是透明的,因为不知道他是以什么形式存在的,是动态库吗?也许吧,总之你只有一个固定的工厂接口而且是固定的。。这一切都在这些例子里得到了演示,包括使用最最简单的动态调用工厂接口调用,在众多plugin里搜索接口然后再作调用。那么这个例子linking plugin into executable究竟是什么呢?
  3. 作者首先给出了它的优点
    • reducing common size of distribution
    • simplification of installation of distribution
    • faster plugin load
    真的有这么好的事情吗?它存在于哪里呢?首先我们看看怎么编译吧?这里是三个文件:
  4. 在刚开始学习lambda的时候我对于它的能力是持怀疑态度的,我认为很多例子的代码不能执行,现在看来是很多我自己的错误。比如我要把每个数组的元素都加一,你可以不用使用transform,而使用for_each的时候甚至于完全不用绑定任何函数,比如我一直在想我是否需要把integer的operator++进行绑定呢?完全不需要,简单的++就可以。同样的我在苦思冥想怎样实现operator+=的绑定,但是最最本能的居然就是对的,因为lambda的作者花了很多的努力实现了操作符重载了。我为什么要再去绑定呢? 结果就是如此的简单
  5. 就在几个星期前我犯的错误依然记忆犹新,我那个时候苦恼于lambda不能做一个最基本的随机数的赋值动作,而范例却当作这个是必然的。一切的一切就在于没有真正理解lambda是依靠于操作符重载来实现lambda functor的,这个我目前也只有模糊的概念,因为operator.hpp很多使用宏来定义大量的操作符重载导致阅读很困难。比如这个是我一直想要实现却总是失败的,我现在开始意识到如果使用了lambda::bind的话返回的是一个lambda::functor那么对于操作符=已经实现了各种各样的操作符重载于是就能自动绑定吧?我只是模模糊糊觉得原理是这样子的。总之几个星期的学习我还是有很大进步的。 这个当然就是产生很多随机数,而我之前不懂得把rand函数用lambda::bind使得它成为一个lambda::functor,而是直接使用导致它是一个常量,而错误的使用boost::bind因为返回的functor不是lambda::functor导致实现的操作符又不对了。

五月二十日等待变化等待机会

  1. 关于visibility的问题我不是很理解。gcc的文档说的是这个可以被外界的动态库链接?还是相反?我印象中我已经下载了这篇关于编写DSO的著名的文章 这里是gcc的说明,我摘抄的一个原因是它的内容很多需要深入的理解,其中很多是sdk的开发者需要关注的。
    -fvisibility=[default|internal|hidden|protected]

    Set the default ELF image symbol visibility to the specified option—all symbols are marked with this unless overridden within the code. Using this feature can very substantially improve linking and load times of shared object libraries, produce more optimized code, provide near-perfect API export and prevent symbol clashes. It is strongly recommended that you use this in any shared objects you distribute.

    Despite the nomenclature, ‘default’ always means public; i.e., available to be linked against from outside the shared object. ‘protected’ and ‘internal’ are pretty useless in real-world usage so the only other commonly used option is ‘hidden’. The default if -fvisibility isn’t specified is ‘default’, i.e., make every symbol public.

    A good explanation of the benefits offered by ensuring ELF symbols have the correct visibility is given by “How To Write Shared Libraries” by Ulrich Drepper (which can be found at https://www.akkadia.org/drepper/)—however a superior solution made possible by this option to marking things hidden when the default is public is to make the default hidden and mark things public. This is the norm with DLLs on Windows and with -fvisibility=hidden and __attribute__ ((visibility("default"))) instead of __declspec(dllexport) you get almost identical semantics with identical syntax. This is a great boon to those working with cross-platform projects.

    For those adding visibility support to existing code, you may find #pragma GCC visibility of use. This works by you enclosing the declarations you wish to set visibility for with (for example) #pragma GCC visibility push(hidden) and #pragma GCC visibility pop. Bear in mind that symbol visibility should be viewed as part of the API interface contract and thus all new code should always specify visibility when it is not the default; i.e., declarations only for use within the local DSO should always be marked explicitly as hidden as so to avoid PLT indirection overheads—making this abundantly clear also aids readability and self-documentation of the code. Note that due to ISO C++ specification requirements, operator new and operator delete must always be of default visibility.

    Be aware that headers from outside your project, in particular system headers and headers from any other library you use, may not be expecting to be compiled with visibility other than the default. You may need to explicitly say #pragma GCC visibility push(default) before including any such headers.

    extern declarations are not affected by -fvisibility, so a lot of code can be recompiled with -fvisibility=hidden with no modifications. However, this means that calls to extern functions with no explicit visibility use the PLT, so it is more effective to use __attribute ((visibility)) and/or #pragma GCC visibility to tell the compiler which extern declarations should be treated as hidden.

    Note that -fvisibility does affect C++ vague linkage entities. This means that, for instance, an exception class that is be thrown between DSOs must be explicitly marked with default visibility so that the ‘type_info’ nodes are unified between the DSOs.

    An overview of these techniques, their benefits and how to use them is at http://gcc.gnu.org/wiki/Visibility.


五月二十一日等待变化等待机会

  1. 整个下午我都在试图理解作者关于visibility=hidden的观点,他的结论是没有错的,可是他的例子说明了这一点吗?是否它自己有真正的得到了他的结果呢?我非常痛苦的来检验他的结论,因为就算不去重复他的实验我们大家也知道结论,问题是他的例子非常的令人费解。而且让人抓狂的是作者不给makefile,那么有的tutorial它实际上是编译多个tutorial的文件这不是出谜语让人猜吗?虽然都很简单的例子可是动态库和编译有着巨大的关系,你不给编译方法你想说明什么呢?当然同时写windows/Linux的编译指示要把人累死了。于是我就把我自己累死吧。 我没有把法只好按照作者的目录结构制作一个repo来记录所有的tutorials。 我不得已对作者的代码来修改,因为实在是令人有些痛苦,我有时候真的是暗地里慨叹究竟是我的理解不对还是boost文档帮助的水平有问题,我每次想到这一点就想自己大自己的嘴巴这种想法就是大逆不道,肯定是我自己理解有误,坚持下去你就会找到你的误解,不要再出洋相了!比如之前关于lambda的理解。所以,我只能继续猜想实验作者的代码。这个是我的运行结果,我完全不理解为什么没有作者声称的问题,我完全可以看到所有的三个plugin,作者的逻辑似乎不太可能,因为是动态加载,并不是把加载交给ld去自动运行因为ld是会有global/local还有lazy/now的各种解决方案,我在工作中遇到过这个问题也是在ld在解决dependent的symbols的时候才有这个问题,你现在自己一个个的加载library你实际上是用来说明如果ld想这样做的话就用可能不再去解决symbols了,因为在你的map上有了就如同ld的sym表里有了就不再寻找新的library。所以作者是在做一个示范的例子为什么需要visibility,但是他的例子完全是在说明相反的做法。总之我实在是又一个非常的hardtime to understand。只能这么猜测了。长夏唯消一局棋,我又是一个下午在折腾一个简单的例子。我都要饿晕了。。。
    
    Plugins path: /home/nick/eclipse-workspace/examples:
    Loaded (0x5626c28f9900):"/home/nick/eclipse-workspace/examples/tutorial2/libmy_plugin_aggregator.so"
    Constructing my_plugin_sum
    Loaded (0x5626c28fa090):"/home/nick/eclipse-workspace/examples/tutorial1/libmy_plugin_sum.so"
    Loaded self
    
    
    Unique plugins 3:
    (0x5626c28f9900): aggregator
    (0x5626c28e7470): static
    (0x5626c28fa090): sum
    Destructing my_plugin_sum ;o)
    
    同样的我在destructor里把之前的cout输出改成了文件读写 这里的ostream::sentry同样会抛出异常,我debug了好久才意识到和constructor类似的问题。

五月二十二日等待变化等待机会

  1. 对于接口的回调函数这个例子其实是很有用的,尤其他的singleton具体实现是有参考性的。 这里的代码是很实用的: static函数里的一个static instance,但是我们需要使用private定义的constructor防止用户创建。
  2. 这个查询接口的例子其实很有实际意义。 对于调用接口我故意展示错误使用library对象导致不可预见的错误。 这样子的结果是可以预见的,就是类似于segment fault之类的, 因为shared_library出界就被释放了通过它获得的函数指针就是无效的。皮之不存,毛之焉附?
  3. 也许这个例子本身也不算复杂,就是增加一个计数器,可是说起来都是容易,作起来就是细节。我真的理解了吗?其实我没有。要好好想一想。
    • 一个小的不能再小的细节就是编译的时候对于tutorial8_static一定要在编译指令上加上-rdynamic否则接口声明会被优化掉,作者已经反复强调过了。
    • 还有就是关于接口定义因为会用到一些dll的宏,一定要在定义任何接口之前include至少一个什么dll的头文件比如:#include <boost/dll/shared_library.hpp>
    • 究竟接口的使用的核心问题是什么呢?我其实已经老早就指出了那个让多数程序员头疼的问题。你的接口绝对要保证动态库加载不能提前释放。那么你要怎样做到这一点呢?首先你需要这样子的一个deleter,它负责删除你的shared_ptr的对象,但是它维护了一个我们心心想念的那个shared_library的对象,把它作为成员函数包装在shared_ptr就保证了在它的删除接口之后才去释放动态库。这个次序至关重要,而使用这样的简单的设计值的学习
    • shared_ptr的机制是什么呢?看到下面的shared_ptr的一个实现细节,你是否想起了SFINAE里谈到的这个著名的例子究竟为什么让实现者使用如此出人意表的方式来判断一个数据类型?难道直接使用sizeof不能获得同样的效果吗?换句话说它究竟在做什么?你使用两个函数原型来定义了分别返回yes/no然后你试探编译器看看究竟Y是否可以被当作指针变量来使用?因为我们判断的大小不是函数指针而是函数的返回值yes/no,这一点我花了好久才理解,真的是鲁钝啊。难道判断一个类型是否为指针类型需要这么七巧的途径吗?你有更好的办法吗?你怎么知道经过多次typedef的类型是一个指针还是什么呢?问题是为什么要判断呢?你压根儿要判断的不是数据类型而是函数指针是否是一个合法的deleter!所以你定义了你预计的deleter的样子返回yes,而另一个模板则是所有错误的情况,根据SFINAE如果第一个匹配成功了sizeof检验返回值的大小就知道是否这个deleter是一个合法的函数指针
    • 对于make_shared这个好用的工具我还不熟悉,这里要记得参数是类型的constructor的参数 比如new dll:shared_library(location);这个和直接调用shared_ptr的constructor的优点据说很多比较一下这个这里的解说我还不能很好的理解:

      The advantage of boost::make_shared() is that the memory for the object that has to be allocated dynamically and the memory for the reference counter used by the smart pointer internally can be reserved in one chunk. Using boost::make_shared() is more efficient than calling new to create a dynamically allocated object and calling new again in the constructor of boost::shared_ptr to allocate memory for the reference counter.

      从字面上的意思就是你普通都是用new去先创建对象,那么shared_ptr的constructor也会内部再次作动态分配他的成员。大概就是这样子吧。
  4. 至此作者的8个tutorial我就完成了测试学习的过程,最后一个因为是windows dll的内容我不感兴趣了,也没有环境了。收获还是不小的。至少很多东西都是要亲自作一遍才能有收益,也才能发现魔鬼的细节。
  5. 我觉得有意无意把代码写的花里胡哨的都是在卖弄与耍流氓,代码的目的是被人读然后纠错。为什么有人要这么干呢?一定是吃饱了没事干可是我目前肚子饿得受不了了,我怎么可能是吃饱了没事干呢?那一定是饿得没事干! 我对于lambda::algorithm里的对于std::algorithm的包装感到迷惑,究竟要做什么用呢?而我的实验也是无比的无聊,因为我想用find_end找到之前用两次find_if找到的区间,可是我要应对区间为空或者区间只有一个元素的特殊情况,所以,写了一堆的条件,之前我希望使用lambda::if_then_else_return,可是它返回的是lambda::functor我不知道怎么使用它,而且functor之间无法实现operator!这个是for_each的判断条件,所以,真的不知道在干什么! 浪费了无数的脑细胞就在解决这一个极端无聊的情况:

五月二十三日等待变化等待机会

  1. puppy linux和几个主要linux兼容,但是编译module依旧对我是一个模糊概念。这里我可以大概的理解,因为已经看到过很多次了。
    
    git clone --branch v3.16.3 /BigDisk/linux kernel-3.16.3
    make -C ~/Downloads/kernel-3.16.3/ M=~/Downloads/kernel-3.16.3/drivers/media/radio modules
     
    我把Makefile改成了这样子:obj-m += dsbr100 但是我遇到了这样子的错误: 这里的人这么说有人说用这patch。 但是我又遇到了其他的错误,我决定做一个ubuntu老版本的USB来实验一下。
  2. puppy制作的u盘不能启动,我想实验一下ubuntu,这里是ubuntu的kernel的version对照表
  3. How to disable localversion when building kernel module? 最后有用的似乎还是设立一个隐形文件才有用:touch .scmversion
  4. 总结一下编译驱动的简单流程,如果你有内核代码树,那么就把编译的路径指向内核的根目录,而M=你要编译的模块的路径,你的Makefile唯一需要的就是定义obj-m+=source.o所以,明白这个原理的话就明白当你没有下载完全的内核源代码的话,其实你需要的不过是一些必要的头文件,那么你的系统如果有安装了必要的头文件你就只需要把make的路径指向它。比如我的ubuntu的内核头文件是在/usr/src/linux,他是一个软链接指向了当前内核的头文件的路径。
  5. 如何使用for_each来实现copy_if呢?我的记忆力太差了把这个又实现了一遍,不过可以改进一点点就是直接使用lambda::_1<50而不需要使用std::less 我现在越来越明白了lambda::_1因为重载了各种操作符所以能返回lambda的functor。
  6. 找到一本在线版的boost library的书据说有430个例子,不知道是不是就是文档的例子的集锦?
  7. 再次强调一下boost::algorithm里的函数几乎都是一些鸡肋,比如这个to_upper_copy就是一个没什么大用的包装了一下std::toupper的函数,对于多字节的语言就无能为力,只有boost::locale::converter才是你需要的转换大小写的函数,而相应的boost::locale::collator::compare也是能够正确应付多字节语言的大小写不敏感的比较方法 这个结果就能说明问题:
    
    boost::algorithm::to_upper_copy cannot convert [i am lower] to upper case correctly:i am lower
    boost::locale::converter can convert [i am lower]to upper case correctly:I AM LOWER
    boost::locale::collator can correctly do caseinsensitive compare between:i am lower == I AM LOWER
    

五月二十四日等待变化等待机会

  1. 终于转战回到正轨继续string algorithm library这个不同于所谓的algorithm那个除了一个boyer_moore搜索算法乏善可陈。我真的有些信口开河这个string就是那个algorithm项下的一个部分,而搜索是另一个大的部分。所以,让我们看看是否sttring的algorithm有什么亮点。其中需要用到一个range,这个需要专门花时间来学习。
  2. 我感觉std的algorithm的实现者想法常常难以捉摸,比如这个find_if_not真的有必要实现吗?明明已经有了find_if,那么unary的negate不能使用not1吗?而让人气愤的是find_end实现了,为什么不实现find_first?要知道find_first_of是一个完全不搭界的range的search,有什么理由不实现find_first呢?find_first等价于search,这个就是为什么没有find_first的原因!他的使用频率还不多吗? 即使没有boost::lambda你也足可以轻松实现find_if_not 而你看使用lambda似乎代码更加的intuitive,总之find_if_not实在是太trivial了,有这个时间为什么不去实现find_first?
  3. 怎样实现unique_copy呢?我怎么记忆中我实现过了?可是笔记和实验代码都没有,那就是真的有患了妄想症了?这个容易吗?我不确定的是unique_copy是否需要排序?文档没有说,可是我觉得这个是隐含条件啊?unique_copy并不是保证每个元素在copy完后是唯一的,只要报纸相邻不重复就行了。相当于不停的检验adjacent_find的否命题,这个就是我的算法的依据。 这里有两点,就是不排序也是成立的,另外关于否命题,我的示范是使用not2对于自定义的命题。
  4. 如果我们不使用adjacent_find的逻辑来实现unique_copy那么我们需要实现一个有状态的functor,它要记录之前的数值等于是有一个上下文的context,当他发现当前数字与之前的不同的时候就返回真提醒我们作一个拷贝,也就是说它遵循了unique_copy的定义把相邻的不同的元素都做了拷贝,这个类似一个自动机的小算法必须解决第一个元素的拷贝的问题,因为比较当前与之前在第一个元素是不成立的,必须无条件拷贝,所以,我必须增加了一个特别的flag来提示这个是第一次。把逻辑写在一个functor里面然后使用for_each再配合lambda::if_then来实现了这么一个unique_copy,比上面adjacent_find来的复杂一些,代码也不好看。 这里又一个小细节如果不注意要卡壳的,对于if_then来说其中的functor默认是一个类似rvalue的copy,那么我们的functor实际上是不符合的,因为作为一个copy的话,它的operator()必须是一个const的函数,而const函数不允许我们修改成员变量,这个是我们制作这个functor的核心目的就是记录之前的数值来与当前值比较,那么怎么办呢?幸亏boost::ref可以允许我们把NotEqual这个functor当作reference来传递启用了if_then的另外一个非const copy的overloaded的变型。
  5. 我散步的时候自以为发现了一个“新大陆”想着使用recursiion来定义boost::function,可是结果发现似乎boost::function不支持递归定义。后来我又发现boost::local_function这个“新大陆”,boost实在是太复杂了,似乎是一个新主意的试验场啊。 我发现有人有相似的想法可是这条路不通啊!以下虽然编译可以但是运行的时候出错 递归定义不支持啊这个是错误
  6. rotation array如何实现呢?或者vector shift如何实现呢?我用for_each来实现依靠一个有状态的functor,也许有更好的办法吧? 结果显示数组整个想右平移了一位。
    
    6,18,0,11,6,18,1,16,6,4,4,10,15,17,8,5,19,0,15,14,
    14,6,18,0,11,6,18,1,16,6,4,4,10,15,17,8,5,19,0,15,
    
    那么左移怎么实现呢? 结果是左移
    
    7,15,11,2,1,3,17,15,17,7,13,9,7,12,2,14,15,10,13,3,
    15,11,2,1,3,17,15,17,7,13,9,7,12,2,14,15,10,13,3,7,
    
  7. 不能使用递归定义fibonacci数列,那么就用一个functor配合for_each来实现吧,我把fibonacci数列输出再数组里。 根据finonacci的标准定义,这个小函数产生数列。 结果必须输出完整的fibonacci数列必须从第一个输出。

五月二十五日等待变化等待机会

  1. 对于copy_backward的实现其实是比较不值一提的,完全可以使用std::copy配合reverse_iterator来完全实现,所以这个函数实在是鸡肋。 结果印证了copy+reverse_tierator等价于copy_backward
  2. in-place的操作是比copy级别的算法难度高上几个数量级的,我看了很久rotate都还是不太清楚它的原理,但是它的巧妙我是领教了,这个看似简单的问题实际上上相当的复杂,你可以把它想像成一系列的递归算法,因为每次swap一个段落之后你又面临一个较小的rotate的问题,不过边界条件需要相应改变,巧妙的就是这个递归的结束条件的不断改变,这样的算法才是真正的算法,因为这个写法只可天上有人间妙手偶得之,估计想我这种凡夫俗子连看懂都难,更不要想写出来了。我以前匆忙浏览以为挺简单其实真正看下去才知道in-place的难度,这个就是螺蛳壳里做道场,刀尖上跳芭蕾,在内存极低的情况下的算法就是这种精髓,只用swap的一个变量的存储空间来腾挪,这就是难度之所在。 为了更好的帮助我理解算法的复杂性,直观的演示是最有效的办法,首先凝视这段代码,看出了什么没有呢?我是没有只是觉得最后的部分无别复杂,事实证明我是对的,最后一段无比的复杂。
    template <class ForwardIterator>
      void rotate (ForwardIterator first, ForwardIterator middle,
                   ForwardIterator last)
    {
      ForwardIterator next = middle;
      while (first!=next)
      {
        swap (*first++,*next++);
        if (next==last) next=middle;
        else if (first==middle) middle=next;
      }
    }
    这是我的测试代码来演示一下把一段三个元素的段平移到末尾,正如你所预料的所有故弄玄虚的人都要把一些杂七杂八的东西混合成障眼法来迷惑最核心的东西我这里也故弄玄虚的创建了一个最无聊的increment的functor,它就为了产生自然数1,2,3。我为什么不能用一个简单的for_loop循环呢?我忘了。 运行的结果才是你关心的:
    
    1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,
    4,5,6,1,2,3,7,8,9,10,11,12,13,14,15,16,17,18,19,20,
    4,5,6,7,8,9,1,2,3,10,11,12,13,14,15,16,17,18,19,20,
    4,5,6,7,8,9,10,11,12,1,2,3,13,14,15,16,17,18,19,20,
    4,5,6,7,8,9,10,11,12,13,14,15,1,2,3,16,17,18,19,20,
    4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,1,2,3,19,20,
    4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,3,1,2,
    4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,1,3,2,
    4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,1,2,3,
    4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,1,2,3,
    
  3. 我常常觉得minmax这种玩意和如rotate之类的高难度算法放在一起就是一种对后者的亵渎,凭什么写这个无聊东西的作者可以名垂青史在自己的履历上说是stl的作者之一?当然对于initialize_list我完全不了解,这个是新编译器的东西可能是有很高深的东西,可是我的观点依然是为什么在早期的版本里面没有人敢写这个呢?究竟这个代码 浪费了几个指令,就是多了一次比较吧,可能对于数组的minmax有更多的意义毕竟比较一轮就能同时找出最大最小确实不需要两次调用min_element/max_element的,但是这个所谓的initializer_list是个什么鬼?怎么用啊?如果不是实际应用的容器那么有什么用呢?只是这个我感到愤愤不平而已,凭什么大家都能做的让某个人得到了荣誉? 我觉得如果minmax可以入选凭什么我做一个increment的functor不能入选?
  4. 反反复复的理解所谓的SFINAE的意义就在于你在enable_if得不到type的typedef,那么未定义的空会导致编译器在匹配失败就悄无声息的避开了这个模板的匹配,而返回值的空或者其他类型的空是其中的必要。我只有自己亲自尝试才有可能真正的掌握。那么我究竟应该使用c++11的还是boost的呢?这个是定义在std的type_traits里。
  5. 实现一个字符串的trim,当然我是想看看find_if是不是也可以使用reverse_iterator,因为可以的话,就没有find_end什么事情了,不是吗?我又闹笑话,find_end是模式匹配,比find_if复杂多了,实现起来是很难的,find_if的确是不限于iterator的类型,不过呢,string::erase是不能使用reverse_iterator的,这个有些出乎意料。我只好使用resize这个效率更高。 结果是正确的吧
  6. 经过几个星期的学习我觉得我现在对于lambda的理解的就比以前多多了。所以我现在就理解我可以这么写因为所有的诀窍都在于lambda::placeholder对于各种操作符的重载上,即便那种非常过分fancy的使用[]的形式我非常的抵触因为这个是典型的滥用操作符重载,我们说好的大挂号{}呢? 那么在你理解了需要lambda::_1的functor,对于常数赋值这两个方式哪一个更好呢?
  7. 我对于boost的文档深感头疼,因为又很多的library相关但是很难找到,比如我想要利用string algorithm的一个小小的is_space,可是我需要他的否命题,结果搞得无别罗索,因为std::not1不能用,结果只能参考这里的说法使用bind来绑定std::logical_not<bool>()真的好麻烦啊。问题是为什么不能直接用std::isspace?原因是参数类型是int不匹配,总之编译不过。

五月二十六日等待变化等待机会

  1. 我对于boost::algorithm的质量是很担忧的比如find_last我觉得它根本就是打住!我还没有看懂代码就妄加评论,怎么可能犯连我都能避免的错误呢?不应该有这种想法perish the thought!关于iterator_range的确是一个熟知程序员痛苦的人的发明天天写std的人就嫌手疼啊,写不忘的begin/end。为什么不发明这样的range早一点呢?但是关于algorithm里的那些个predicate确实是有缺陷,比如is_alnum/is_space等等的定义都缺少一个argument_type的typedef导致不论是std::not1还是boost::unary_negate都无法应用,这个有什么解决办法吗?除了使用logical_not来作?当algorithm::is_space没有定义argument_type之后编译器报出的错误是 一开始我对于这个错误很不理解,后来看到std::not1的祖先定义他的operator()是这样子的 它是依赖于predicate的argument_type来决定他的参数类型的,而如果_Predicate::argument_type没有定义的话根据SFINAE的原则它就不产生operator了,于是编译器就抱怨没有这个期待中的函数了。这个就是错误的根源。对模板错误的debug超级的困难的原因就在于它基本就是编译期的错误,而编译器报出的错误往往不着四六,看得一头雾水,不像运行期的bug你可以使用gdb分析逻辑,这里纯粹就是编程者代码知识功力的高深,包括对于库函数实现的熟悉程度,没有什么捷径可循。而库函数往往都让初学者望而却步,尤其是在某个看似无厘头的步骤被卡住以后束手无策往往导致所有后续探索也被放弃了,这个就是失去了大好的进步的机会,因为没有高人的指点纯粹靠摸索往往事倍功半。
  2. remove_if可以使用copy_if来实现,同样的boost::algorithm::erase_all可以使用copy_if来作in-place的操作,当然最后要缩减字串长度。 结果去除了所有的空格: 所以从这点来看lambda::_1==' '是要简洁的多了。
  3. 对于copy_range怎么理解呢?我发现range是一个相当丰富的湖泊,如果boost整个是海洋的话,所以,需要现在就开始探索这个range,因为algorithm很紧密的依赖结合它。读到这个range algorithm我才意识到我抱怨写的手疼的begin/end的std::iterator的解决方案是在这里,看看这个boost/range/algorithm.hpp头文件里所有的std::algorithm的函数几乎都被它重新包装了一遍,我有一种豁然开朗的感觉! 这个是copy_range的代码你发现有"copy"的痕迹吗?凝视良久无言可对,这那里有我期待的copy呢?本身你需要拷贝(动词)吗?你需要的是拷贝(名词)不是动词的拷贝。比如我们调用的时候copy_range<std::string>(*aRangeIt);就直接利用了string的constructor来创建了一个range的string,你要问为什么模板参数的Range没有传入呢?我没有找到overloaded copy_range,所以只能说能够推理出来因为在参数出现了range,那么反问的问题就是既然不需要何必要定义?这个补全的动作是编译器的普遍行为吗?我并没有看到推理类型的动作啊。
  4. 这个是一个Hello world式的range algorithm的例子,就是说在boost/range/algorithm.hpp里定义了所有的std::algorithm里的算法终于可以摆脱反复写begin/end的手疼了。 这个写法是不是够玄了呢?原文作者是要实现一种类似于shell里的pipeline的效果,也就是FP里输出变成下一级的输入这样子的流水线的操作,可是中途的unique不是完整的输出而最后输出到std::cout不能添加一个分隔符看不清楚,我不得已才输出到另一个vector然后添加分隔符再输出。其中std::ostream_iterator<string>让人耳目一新!。

五月二十七日等待变化等待机会

  1. 俗事缠身无法豁免,今天在静下心来看代码。这个是一个很好的问题,对于这样子的函数的overloading怎样区分呢?一个指针T*一个是数组T[sz],对于编译器来说有差别吗?对于函数我们要区分怎么办呢?这个是我从boost代码摘抄的,作者解释是
     //
    // Remark: the compiler cannot choose between T* and T[sz]
    // overloads, so we must put the T* internal to the
    // unconstrained version.
    //
    这个究竟是什么意思呢?我们定义类型的时候数组变量和指针变量可以当作是不同的类型,可是在编译器眼里是不存在的比如以下就是我做的一个验证的例子我们使用模板定义宽泛的打印函数 那么我们先定义一下指针变量的形式 可是当我们定义数组类型的时候编译器不承认 编译器抱怨说这个函数原型已经有定义了: 这个原因是数组作参数的时候总是被当作指针来传递的。因而编译器会警告你 导致的结果就是sizeof(ary)/sizeof(ary[0]几乎确定在64位机器就是2,而不是数组的真正长度。这个就是数组作为函数参数的限制。总而言之我们没有办法区分数组和指针这两个参数类型。可是作者究竟在说什么意思呢?我还是看不懂它的代码。比如一个相似的例子我使用函数指针定义functor 看上去函数指针似乎是接受了类型,可是调用的时候其实没有区别: 在编译器眼里是没有类型区别的,那么怎么能够区分两种参数呢?我搜到了这个模板的做法其实很好: 于是我们可以把数组长度作为模板参数来传递,当然这个和额外一个数组长度参数没有什么两样,都有人为错误的可能性。 结果显示不同模板参数是不同的函数,这个也许是唯一的优点,可是有什么实际意义呢?
  2. 让人泪目的小说----《乡村教师》(作者:刘慈欣)当我读《三体》第一部中我理解了叶文洁对于人类的仇恨强烈到以至于呼唤外星文明来毁灭这个罪恶的星球,在《乡村教师》里我感到了的悲愤以至于明白这个丑陋的星球之所以还存在是因为一小部分人的无私奉献与牺牲。
  3. 我在以前阅读的时候对于lambda的operator overload其实没有真正理解,所以,我才会反反复复疑问lambda placeholder能不能呼叫类的成员函数的问题,其实理解了这些operator是不能overloading的原因就理解了这个是不可能的,所以,这就是为什么你需要使用lambda::bind的原因。主要就是为了类的成员函数,否则直接使用lambda placeholder的operator就可以了,何必要费时费力去bind呢?另一方面lambda placeholder必须是lvalue因为这些operator的重载要求它是成员函数,这些operator包含了assignment,subscript operator

五月二十九日等待变化等待机会

  1. range的困难之一在于它的类型,包括各种各样的iterator的概念,我昨天遇到了tuple的一些概念和使用细节也是如此,有些概念看似简单,但是实际使用就卡壳了。只有反复的练习才能掌握。从贡献大小的角度来看tuple是要大于range的,因为它更有实现的难度,也是补充std的巨大的欠缺,是很多人心心想念的东西,是比vector更加接近现实世界的东西,我应该认真学习。
  2. 昨天遇到这个例子这个tuple的combine是一个非常强大的方法,尽管我现在还不能理解它的具体使用场景,可是我已经意识到了我一向不喜欢的BOOST_FOREACH的无可替代的优越性,之前我一直不喜欢这个看似macro的小东西,觉得它可有可无,从函数编程的角度来看它是不够格的,因为更像是一个循环,从异常抛出的痛苦经历我依然记忆又新,就因为工作中我遇到过一次debug它的异常我对它产生了成见始终很抵触。但是这个例子让我感觉它有一些优越的地方。 在这里boost::combine返回的类型其实很麻烦,要是不使用偷懒的auto方法真的不知道怎么办,可是我们的目的并不是要使用返回的tuple,我们只需要其中的元素,所以使用了tie的方法,就无形中跳过了定义的部分,这一点让人印象深刻,因为同样类似的例子是每次写iterator循环也要定义iterator的类型,这个也是很烦人的而我们关键的代码不是循环本身的循环变量如何增减,而是循环变量指向的元素,这个就是for_each这样的算法的好用之处,但是我们依然逃不出指定for_each起始的部分,现在有了range是摆脱繁琐的begin/end的时候了,而类似的tuple类型千千万万,我们只关心其中的元素,跳过tuple的类型使用BOOST_FOREACH/tie的组合也是其中的妙处。 偷懒的auto做法是我非万不得已不愿使用的,这个会培养人的惰性: 这里的t到底类型怎样呢?我是写不出来的。

五月三十日等待变化等待机会

  1. 俗事缠身,接续前节。这个例子究竟要怎么理解combine的返回值呢?这里是我的答案。终于我找到了我的方法可以避免使用我所厌恶的BOOST_FOREACH,至少打印实现了。 返回值是这样子的: 可是我对于他的类型依旧十分迷惑,于是我查询combined_range的定义,它也就是一个特殊的iterator_range,那么它应该支持所有的方法,比如我们可以使用boost::size来对这个range取得它的大小,获得它的第一个元素的front返回的是什么呢?原来是一个tuple,这样子我们就理解了这个combined_range。这里我使用tuple的get方法来获取其中的元素。这里给人印象深刻的是这个get<N>模板参数是很智慧的因为它是编译期就知道你的tuple的元素类型与个数,比如我尝试不存在的it.get<2>就会导致编译错误。 那么对于tuple我们看到它有重载operator<<怎样利用呢?首先这个是定义在boost/tuple/tuple_io.hpp里的, 于是我们可以使用range的for_each版本来使用combine返回的这个combined_range这样子我们可以打印整个的range:
  2. 这个是我认为很高难度的探索,折腾了我一个早上,我打算在前面的例子基础上探索怎样获取tuple的元素作处理,比如我们创建了一个combined_range的combined tuple这时候我想把其中的元素剥离出来比如存储到另一个range里,那么应该怎么作呢?
    • 首先创建两个range一个是存在vector里的int,另一个是存在list里的char,我们使用随机数来初始化 结果是这样子的
    • 然后我们把他们combine起来再显示看看 结果是一个combined_range
    • 以上都是之前做过的没有什么难度,接下来这个bind花了我快一个早上,因为我想把tuple里的整数抽出来存到另一个vector里 首先选择获得tuple的成员函数就费了很多功夫,我一开始想使用tuples::get(t)这样子的tuples名字空间下的全局函数结果遇到了困难,主要是函数的类型有些歧义,也许是我的某些错误,总之我后来放弃了因为函数的参数是一个tuple,它的类型我感觉困难。然后我尝试tuple的成员函数get(),它没有参数并且只有一个const和非const的变形容易bind。结果我卡在了到底tuple的数据类型是什么呢?不要告诉我你用constructor怎样获得tuple。我一开始对于tuple为什么是tuple::detail::cons还感到奇怪,后来才意识到这个是tuple的实现类,tuple继承了这个get方法,于是问题开始明朗了,通过编译器错误的提示我开始意识到tuple的模板参数原来居然是这样子的我接下去试试看三个模板变量这个类型是怎样的。可是这个真的是我的困惑,我在实验三个元素的tuple时候发现根本不需要这样子直接使用boost::tuple<int,char>而最最让我吃惊气氛的还在于static_cast区分两个get的const和非const的办法居然诀窍在于返回值必须使用const int&看来对于简单类型我们认为int不可能与非const类型混淆,可是编译器需要严格的const匹配,于是不但你传入参数使用了boost::cref导致int类型也变成了const int&的返回类型。这个是最让我吃惊和惊讶的。这里是抽出来的数字:
  3. 但是实验tuple带三个元素的结果出乎我的预料,它的类型居然就是简单的tuple<int,intint>这个是为什么? 结果还需要吗? 我随后使用复杂类型string重复了以上实验
  4. 你能想像我为了这么一个简单的产生随即字符串作为名字的函数居然耗费了一个下午吗?这其中的debug太困难了,因为是模板的编译错误完全只能依赖于逐步的化简步骤观察编译错误来推敲,这个过程实在是太痛苦了。 我要的随机字符串
  5. 对于range_combine的巨大威力我想用这个例子来体现,就是说你有来自于三个不同来源的数据集,比如firstname是一个vector,lastname是一个list,还有一个年龄的vector,现在你可以很容易的把三个数据集进行一种mapping成一个统一的数据集。 这个结果显示了把三组数据集合在一起的效果:

五月三十一日等待变化等待机会

  1. 这个是从今日头条看来的算法题目:给定一个整数数组,从中找出最小没有出现在数组里的整数。时间复杂度要求是O(n),空间复杂度是整数大小的O(1)。所以,这个题目是有相当难度的,因为时间复杂度不允许排序,空间复杂度不允许使用现成的hash算法工具。但是任何算法都逃不过空间换时间,时间换空间的自然规律,作者给出了所谓的Iin-place的hash算法。我就顺手实现了一下。原理其实说白了也不复杂就是利用了正整数天然的自然数位置的hash算法,只不过不允许另外分配空间的inplace算法必然要把原来的数据加以摧毁了。首先最重要的一点也是问题的核心就是我所没有想到的最终答案出现的范围的问题,这一点想不到这个题目就无解了。那就是最坏的情况,数组是一个从一开始的连续自然数,那么答案是什么呢?正好是数组长度加一。所以,作hash表的时候对于小于0与大于数组长度的都可以剔除了。这个就是最核心的部分。当然在算法实现上我意识到了出现重复数字我一开始没有判断导致无限循环。这个也是一个小小的隐含条件,原题没有说明数组是否允许重复数字,我按照不允许去实现而现实的随机数是允许的结果导致无限循环。 结果多显示几组: 为了验证最极端的从1开始的连续自然数的情况,我这样子产生数组 然后结果就是数组长度,就在这个时候我发现了我的边界条件的错误就是我从0开始的hash表实际上是毫无意义的浪费了宝贵的第一个位置导致我的表比实际少了一个,所以这个算法有问题无法应对极端情况。只能重来必须利用第一个不能分配无意义的0。以下是修正的版本 这里是一些测试结果
  2. 很多时候你在c++xx里看到的新的功能或者库往往都可以在boost的各种不同版本里找到,因为他们正像武侠小说里的很多武功门派是析出同门,甚至就是先在boost里实践然后被c++委员会认可。不过其中当然也许有些许的区别,我宁可不依赖于编译器的新功能而通过库的实现来得到这些新的功能。所以,这里的例子就是random,我想探究一下究竟两者有什么区别。这里是boost的文档。
    • 首先这里有概念需要理解。
    • 理解它的应用领域
      • numerics (simulation, Monte-Carlo integration)
      • games (non-deterministic enemy behavior)
      • security (key generation)
      • testing (random coverage in white-box tests)
    • 这是非常重要的步骤,让我们理解它的类型
      • non-deterministic random number generator
      • pseudo-random number generator
      • quasi-random number generator
      。 这里很重要的是所谓的quasi-random number generator。我读了它的介绍才恍然大悟,这个其实在测试中至关重要,我们常说的coverage对这个需求太大了,它并不是所谓的真正的“独立”随机数,在于它的分布依赖于之前的分布,这个更加接近很多赌徒的想法,比如古龙的小说里经常出现的情节:已经连开了十三个大了,下一个必定是小。因为分布是非“独立”的,而不是真随机的完全独立。
    • 这些都是Uniform Random Number Generator 对于第一个helloworld例子我都有些理解的困难,这里的代码只有这么几行,可是它背后的原理却很深奥,比如你查看boost::random::mt19937它的发生器的算法的基础是什么呢?我们平常熟知的随机数是一个伪随机依靠无限不循环小数的这样子的数列,而它是依靠Knuth的这篇论文据代码注释是在Vol. 2, 3rd ed., page 106我根本就看不懂,只知道是一个连续的余数,大师解释了为什么它的随机性。这个完全超过了我的理解。那么从使用的角度来看所谓的boost::random::uniform_int_distribution是一个模板类不管什么样子的引擎都遵循上界下界的约束(复杂的地方就是非整数是半开区间,整数是全闭区间,这个细节实在是太多了)。而都是依靠operator()()返回随机数。这个从使用的角度是很简单。
  3. 于是我决定使用random::mt19937发生器来产生随机数。这个要绑定它的operator()虽然不是很难,可是我也要尝试好几次才能作对。绑定的确不容易,我已经练得有些手熟了。有一两个小细节,就是uniform_int_distribution是一个模板类哪怕是有默认整数作参数也要象征性的把<>加上表示不能忘本。同时engine这个函数参数是传递的引用,所以不得不加上boost::ref这个如果不知道就卡壳了。其实要不是这个reference的参数限制我本来可以写成一个oneliner的使用临时变量的形式,当然啊我根本不考虑效率仅仅想把代码写的fancy让别人看不懂而已。当然对于取得operator()的地址怎么作我已经很有经验了。 结果并没有意外,我很期待看看quasi-random的效果。
  4. 我在散步的时候对于generate_n没有放在range::algorithm里耿耿于怀还想着自己实现一下让他返回新产生的range因为std::generate_n返回的是新产生的最后iterator,bengin应该不会变得。可是再看看概念我才意识到这个是一个模式的问题,对于generate和其他比如for_each之类的range都是现存的不会改变所以才有流水线的连续处理的可能,可是generate_n是完全新创造的,并不是generate那样子只是对于现有的进行加工。所以,只能作罢。
  5. Knuth大师的《计算机的艺术》我是买了原版的可惜只翻过几页纸,开始还有些忐忑,现在都麻木不仁了。现在看到这个关于随机数的部分还真有意思。我也没有指望能读多少,可是就像当年小昭劝慰张无忌习练乾坤大挪移九阳神功时候一样,读一个字也是好也是一个字的增益。

六月一日等待变化等待机会

  1. 昨晚看大师的著作没看几页就睡着了。
  2. 对于uniform_int_distribution它的头文件自然是boost/random/uniform_int_distribution.hpp,那么对于discrete_distribution那么自然也就是boost/random/discrete_distribution.hpp前者的constructor就是设定min/max,因为它是最简单的,也差不多只有这个属性需要设置了,对于discrete_distribution至少你要设置它的概率分布,这个自然是整数从0开始到分布区间的长度减少1,因为是discrete所以自然我们知道是整数的输出了。其中的param_type默认是double,但是应该也是可以使用别的类型作为模板参数,不过我目前还没有打算尝试。这两个模型如果使用的是mt19937它都是头文件实现的,可是对于boost::random::ramdom_device它是有实现代码的,需要链接libboost_random.so
  3. 这里就是对于random_device的例子的实践,注意这个random_device是一个真正的随机发生器,因为我不用自己设定种子。在linux它依赖的是/dev/urandom这个虚拟设备来不断的获得随机数。我使用hexdump可以源源不断的获得。其中绑定还是花了我不少时间,我对于lambda::var的使用才真正有了理解!对于这么一个简单的问题我以前百思不得其解,现在才豁然开朗。 首先我们定义了所有password的可能的字符集,并且使用random_device作为引擎生成了一个uniform_int_distribution的发生器。 接下来我们要把它绑定为一个functor,这一步其实基本上都是大同小异因为使用的都是各个发生器的operator()方法,只不过有重载我们必须static_cast类型指定。 这里才是我今天学习的进步的一点点,因为我本来想使用generate_n可是它只是被动的要返回一个值我没有地方使用lambda::_1这个placeholder就卡壳了,只好先resize这个str然后再使用for_each因为它允许我使用lambda所以很尴尬的这么作 可是这个太笨了我既然可以使用lambda::var为什么不能直接使用generate_n呢? 所以这个才是最好的解决方案。每天对于lambda的理解都深入一点点,其中的很多段落我反复看了很多遍才开始理解。其中对于operator是lambda的精微奥妙的地方因为使用lambda就是为了少写代码,尽量使用操作符,因此这个理解是最关键的,可是操作符也是最最灵活甚至容易被人们滥用的地方,而且最容易产生歧义的地方。
  4. 所以这个两种截然不同的机制,一种是所谓的pseudo-random number generator有这些classes rand48, minstd_rand, and mt19937 而另一个机制是依赖于操作系统实现的所谓的random_device就是内部模拟一些随机过程自动产生永不停写的随机数,它需要libboost_random.so的链接的,因为是操作系统依赖的实现,不一定都支持。 这个关于generator的表我很喜欢并且加上了我最最关心的头文件部分
  5. 似乎uniform_smallint这个distribution更加适合小的数字范围。这里更加重要的思想是随机数的产生器可以被mapping作各种各样的概率分布,这个思想是很重要的。这里我采用最复杂的random_device来作引擎。需要include头文件boost/random/uniform_smallint.hpp 结果是看不出来有什么区别的,因为概率分布是一个高层次的东西?
  6. 对于所谓的uniform_01来说其实可以看作是一个浮点数版的整数uniform_int_distribution的映射到0到1之间。我感觉它就是一个uniform_real_distribution 的一个特例而已吧? 唯一不同的是operator()居然不是const的函数。这一点实在是有些让人无语。 结果是可以立刻使用的
  7. 绰号“白努力”的bernoulli_distribution实际上说的是一个事件是否发生的特殊分布。这个其实挺有用的比如扔硬币。 这里是结果
  8. 我在实验这个binomial_distribution的时候也开始回忆起了以前学概率论的记忆。其实都不是很复杂的分布概念。 你输入的参数是100和0.2那么我的成功的数学期望值就是20,所以重复了10次,大部分都是在20附近徘徊
  9. geometric_distribution就是我们熟知的古龙小说的赌场里嫩沟连续开大的话能维持多久?比如我有个灌铅的色子,开大的机会是0.65,那么它连续开大要多少次才能开小? 那么我们看看能够连续开大维持最长的是多少次?我们看到有5次。
  10. 关于negative_binomial_distribution实际上是在问如果赌场老板的色子灌了铅导致开大的概率是65%,那么总共开大13次的话,开小的次数是多少? 我看到的开小的次数有
  11. 编译内核模块的Makefile只需要添加obj-m +=dsbr100.o 而编译指令就是指向内核头文件的根目录make -C /usr/src/linux-headers-5.3.0-40-generic/ M=$PWD 但是我目前还不知道要怎样签名这里很长我还没有来得及看就困了。

六月二日等待变化等待机会

  1. 我曾经为了这个简单的功能寻找了一个月,其实它老早就写的很清楚,只是那份文档我始终没有看懂。当时甚至我试图专门实现了一个通用变量的self-increment的functor,现在看来真的是无语,我始终坚信这个功能一定存在于某个地方就是找不到。 这么一个简单的结果我却经历了这么久才找到。
  2. 我突发奇想要把英语字母出现频率应用到概率分布上这样子反过来产生的人造词就更加接近真的词汇了。这个是我找到的字母频率,我写了一个小程序要读这个频率,结果遇到std::pair::first/second这样子的成员变量怎么访问的小问题,实在是不经历不知道其中的细节啊,你要把pair的类型定义为const才行,这个实在是我无语了。这个是我保存的字母频率文件 这个是按照字母排序的频率的百分比
  3. 这个是一个比较复杂一些的例子,首先我要把之前的字母频率作为概率分布传递给一个discrete_distribution来表达字母的概率分布,然后使用这个random_device的随机数来产生新的词汇。当然这个检验似乎没有什么意义,不过验证了几个东西,其中有些细节,比如我一开始不确定作为discrete_distribution的参数是否一定要数组还是vector也行,那么直接把map里的频率输出到数组里需要用到特殊的iterator,我还打算自己创建一个iterator,后来发现这个需要实现,直接从vector里获得也不对因为我创建的临时vector对象是临时的,最后发现std::begin/end有针对数组的实现,我太孤陋寡闻了。不过呢,仔细看来这个std::begin是c++11的新功能,我还是有情可原的 首先我从文件把频率读到一个map里,原因是我懒得排序了,map自动排序了。 其实这个部分是把map里的second读到数组里,绑定second是一个成员变量但是也可以当作成员函数一样的绑定。其次,这里的std::begin是针对数组的新功能是c++11的新功能,不过我如果使用boost的就可以不依赖于编译器了。 把频率作为概率分布然后随机产生新单词,我先检验一下产生的数字的分布是否符合字母的分布频率。 对于结果我也不是非常满意,毕竟英语里有很多的字母顺序关系,这个有点像是某些字母之间的依赖关系,可以归结于条件概率吧?
  4. 这个是我把之前的字母频率和英语单词长度的频率加以整合的一个比较复杂的例子,就是根据这个网站的英语单词长度的概率分布我使用了一个简单的regex来parse其中的长度的百分比,连同以上的字母频率合在一起。我把这个英文单词长度的分布概率数据html的片段保存在这里花了好久才找到一个函数绑定的错误,令人心惊的是根本没有编译错误,等于是绑定了一个空的指针吧?其实我现在脑子里对绑定成员变量的方式还是稀里糊涂,因为成员变量不是函数,那么你把它当作函数使用,并且我绑定的时候是定义了一个函数吗?似乎我原来的错误就是没有定义清晰我到底绑定的是什么。不过修正了这个错误之后我的这个所谓的gibberish发生器似乎有点像那么回事了。 这个函数是读取之前就重复的字母频率这个函数果然有问题,我debug了好久才发现了这个疏忽,居然没有引起编译错误是让我着实的心惊啊! 比如这个原来的std::pari里获取second的所谓functor根本不成立啊??我感觉压根没有绑定啊,我改成了这样子 看到了区别吗?你要真正的绑定的函数是传递std::pair作为参数然后调用的,我可能是脑子糊涂了吧??? 这个是使用regex读取英语单词的长度,从网站上拷贝的记录是一个html文件的一个片段,所以,这个属于及其简单的parsing html tag的例子,不过呢,小的技巧是从文件读到string里是必要的因为搜索比对不能正确如果分开的话。这个(?:.*?)是跳过所有不感兴趣的部分直接到有百分比数据的tag结尾,其中第一个?是说这个表达式的内容虽然在()里可是我不想要,第二个?是说要lazy最简单的比对不要backtrace之类,这个很重要,也很subtle。同时使用regex_search的iterator的形式是早就应该掌握的。 然后我们使用这两个分布得到两个discrete_distribution,包装成两个函数。 我们依靠这两个函数生成英语词汇 不过结果我觉得也看不出来更加的像是英文词汇啊?我很怀疑哪里有bug因为很多所谓的单词没有元音字母,这个在概率分布里保证不了吗?我果然找到了错误 我重新改正了getChar里的绑定的错误,这个是新的代码和结果 这个结果似乎才靠谱吧:
  5. 对于成员变量的绑定我觉得肯定是有些机关的,我反复比较我的两个形式,是否有overloading 的歧义呢?应该不是否则编译就无法解决了那么我开始怀疑还是需要static_cast强制让编译器知道这个是一个成员函数,而不是当作成员变量?总之我gdb的好几次完全摸不着头脑。因为传入的参数也都是对的,但是在back_insert_iterator赋值的过程就莫民奇妙的赋值为空值。最后我还是模仿boost::function的类型使用static_cast, 当然这么作的话两种形式其实完全没有区别。至此我的结论是这个不是std::pair的元素类型或者cv签名不同造成的,而可能是函数与成员变量之间的区别造成的吧?这个问题可以使用constructor来解决。 这样子结果就一样了
  6. 我的练习到了这个阶段头文件越来越长了,现在有必要记录一下我的编译配置。 我的头文件是这样子的,我是抵制使用namespace的alias的,个人觉得这个会更加混乱,而且对于当初命名名字空间的人的不尊重,所以,我只使用最上一层的namespace std和boost,这样子几乎std的元素都没有问题,对于boost里和std重名的我都是用boost的全部namespace这样子我觉得挺好。 对于链接的部分是这样子的:

六月三日等待变化等待机会

  1. 大瘟疫时代的纪年方法是记录自己给自己理过几次发,让人想起当初悟空在菩提老祖斜月三星洞学法的年代,菩提老祖问石猴来了多久的时候,石猴答道只记得每次后山的桃树结果就去饱餐一顿,至今已经有七次了。如今我记得我是第二次给自己理发了。
  2. 昨晚gdb那个std::pair也确实跟到了,back_inserter_iterator的operator=也跟到了,但是在内部的container.push_back就看到参数是正确的出来就发现变空值了。我已经开始怀疑gdb有问题了。不过也许我可以做一个实验。原来的这个文档我感觉连编译都不可能通过写文档的人和写代码的人根本不是一个维度的人原来我自己才是另一个低维度的人!在说出这种放肆的话之前连编译都不愿意,真是打脸啊! 事实上这里的lambda::_1的的确确就是一个placeholder根本不需要是真实的参数我们完全就是借用它的操作符重载来实现一个functor来调用,所以我才有这么多的误解。 这可是真实的结果啊,lambda的作者诚不我欺啊!
  3. 但是虽然我们可以把结构的成员变量通过lambda函数调用变成一个lvalue来进行所有的赋值动作,可是我在for_each里的functor却是一个彻底的rvalue无法获得reference来作赋值。我怎么也想不出有什么好办法。 比如我自己创建了两个pair在map里要显示他们还要硬性的把pair里的类型改成const才行: 这个结果是正确的可是你看我必须在first,second对应的pair的类型加上多余的const编译器才同意。 而我想修改怎么也不可以 编译器始终都是 那么我要怎么才能修改map里的数据呢?
  4. 我的结论是这个可能不是其他原因而是inplace的修改本来就是不赞成使用的,比如set根本返回的就是const不许你修改,而map好一些但是返回的key也是不允许你修改的,因为他们都会破坏原先的排序,所以,你只能改变并保存到一个copy来作修改。 这里是一个很好的实践constructor的机会。 我决定使用vector先来实验,然后在用map来验证 果然在map上有很多的古怪,我们已经看到在vector上这些iterator是可以是reference的就是可以随意的修改吧?总之在map里这个pair的成员必须是const,而且如果没有使用const虽然编译通过了可是运行期间会出现很多莫名其妙的错误比如bad allocation之类的错误,所以一定要小心这里的const关键字,我为了这个花了两三个小时在debug还是毫无头绪 特别注意这个const 和这个const非常的重要,出现编译错误是你的运气,糟糕的是没有错误运行出现错误才是痛苦!
  5. 果不其然,不能使用lambda作为成员变量的引用的原因是map的元素是const的缘故,这个和set是相近的就是他们内部进行排序要求不能随意修改元素,必须使用erase/insert而不能直接输出引用供人修改,所以,我使用了vector的pair就可以任意修改了。但是很可能会让很多人望而却步,这个值的吗? 修改的结果是这样子的,所有的年龄都加了5。(谁说整数代表年龄字串就是姓名了?)
  6. 我觉得我找到了这个成员变量绑定的函数没有返回值的问题症结了,这是一个典型的必须要使用constructor的例子。 这里如果没有使用constructor的话编译器就不知道返回值是什么,这个解释对吗?
  7. 所有的算法都是针对一个对象进行修理改造而凭空产生就复杂了,因为它不符合流水线的思考方式。我脑子里想不清楚要怎么办,结果就写了这么一行代码就写不下去了,我本来想产生一个随机字符串,可是现有算法都是针对已有的改造,让我怎么generate呢?
  8. 对于random我觉得暂时打住因为很多高级的概率分布我第一没有什么印象,第二除非用到统计很难有应用。我的这个抄来的表也就不更新 了。
  9. 安装PuppyLinux Tahr我使用usb image运行,这是一个32位操作系统很麻烦,很多包都不支持了。并使用gpartd来分区并full安装,安装编译工具不能使用安装包,需要安装devx sfs
  10. 这个factory也许可以作为明天的学习科目。

六月四日等待变化等待机会

  1. 对于boost::functional我之前是看过的,我一直印象中这个是一个废弃的项目因为它仅仅对于std的很弱的bind1st之类的补缺补漏,完全可以被boost::function所替代,所以,这个一开始还是一个很容易混淆的functional似乎是一个更加特殊化的小部分。可是我看它的factoryforward_adapter完全不得要领,迷惑的本质是我是一个从猿猴进化来的智人,唯一的学习途径是模仿,没有例子我根本无法理解。怎么办?RTFC记得祖师爷的话吗?去下载源代码来看吧!什么boost的头文件还有源代码?没有,可是那些developer自己总要作测试吧。所以,到这个github的总站去找一个个的library 的开发区下载来看他们的测试例子。
  2. 知道boost怎样实现类型匹配吗?我不知道std::is_same是否是依赖于新的type_traits之类的库,可是boost这一小段代码是非常的令人开窍的。 这两行代码如此的简单,可是道理很深刻,至少对我是如此,对于五花八门的typedef或者define之类的宏也好,你有可靠的办法判断他们的类型是相同的吗?不要笑看这个问题,类型可以是一个非常复杂的问题,因为要加上所谓的cv那些const,lvolatile等等,这个几乎是人力不可及的任务,可是不要忘了我们有编译器,它的作用就是拨云见日披露类型的最终面目,所以,利用这个模板类依靠编译器告诉我们什么是同类!不要问我false_type/true_type是什么,我看了一眼就放弃了,对于一个你以为的true/false的判断它的实现是如此的复杂。
  3. 今天浪费了一个上午的宝贵时间,我惭愧以至于不敢吃午饭,现在终于可以吃晚饭了,太饿了。
  4. 我绝对是遇到了非常精微奥妙的部分,因为对于这个部分我非常的难以理解。它就是为什么我们需要forward_adapter的根本原因,可是我实在是不理解。且慢我觉得核心在于result_of的支持是有先决条件的。我是否需要自己定义自己的result? 我的这个实验非常的令人困惑 请注意运行结果是没有错误说明什么呢?就是这个是我怎么也不能接受的。这里说的什么我压根不理解。我相信我如果理解了这里我就能解释为什么了。 看了result_of的实现我简直是心驰神摇啊,在类型计算里使用了递归,我的神啊!当然啊,这个要去看源代码,说明里说的很轻巧,而且似乎根本不对,我压根没有看到需要自己定义什么result的类型。也许是很老版的实现吧?不过我也开始理解其实type_traits也是有迹可循的,就是说我也开始不再不敢仰视的对他顶礼膜拜,不是不崇敬而是不再神话它。比如怎么判断是成员函数指针,这里其实大家一看就明白了 当然这并不是全部还有很多很多,不过大家就明白了我就是穷举所有的的类型,当然啊参数个数是有限制的。 但是对于这个实现你敢不仰视吗?我看得是触目惊心啊!这就是类型的计算!你要写一个递归函数去计算类型,这个meta computing的神奇的玩意,比我高了不知道多少个维度!这就是降维打击!
  5. 代码界有一种人总喜欢标新立异,并且喜欢把自己觉得与众不同当作是彰显自己与众不同的证明。这种现象在年轻人里尤其明显因为恃才放矿是一种博得荣誉与名誉的捷径。我说这些有时候是在为自己的迟钝开脱,比如我从来没有尝试在循环的条件部分里作循环步进,理论上可以,可是谁应该这样子作呢?再比如我现在对于operator和operator&&有种模糊的认识,难道不是都会执行吗?可是问题是comma会把第一个结果扔掉啊想到这里我心里有一种不祥的预感,如果有人利用operator,的这个特性把返回值留在第二个甚至是最后一个语句里这该有多该死! 所以,你看看这个代码是看上去没有问题的,因为长度是奇数11,如果改成偶数就是死循环了。 难道你不该诅咒有人这么写循环条件的卖弄吗?
    begin!=end,begin!=--end
    而且居然在奇数的时候还没有问题就更加可恶啊!赶快把,改成&&吧! 你有意识到std::iter_swap和std::swap的区别了吗?如果你使用的是swap会怎样?编译器是不会提醒你出错的啊!我自己也不禁怀疑我的大脑是否正常为什么交换iterator能够正确?难道不应该是std::swap(*begin,*end);吗?可是查询iter_swap的代码 这是什么鬼怪啊?在c++11编译器下它和std::swap(*begin,*end);就是一样的!我真的觉得我越来越不敢说我学过c++了,前前后后我也学了差不多快20年了,可是我现在越来越觉得我就是刚开始学习那样战战兢兢如履薄冰啊!我也觉的很惭愧因为我从来就没有去读iter_swap的文档,人家本来就是交换对象,iterator本身交换是我自己想像出来的,有意义吗?可是我明明看到在代码里对应c++11之前似乎是在交换指针本身啊?(不过它之前的那么多的模板定义似乎也可能不是的?)仔细再看看代码这个所谓的iter_swap在2011之前的编译也是一样的关于这个问题到此为止了。

六月五日等待变化等待机会

  1. 我只能隐隐的觉得这是一个forward_adapter的可能的bug或者说是limit,因为我至今不能理解作者的文档里的background部分。这是我目前观察到的现象就是对于functor来说他的forward_adapter施加了参数的某种类型的限制吧,不仅忽略了某些reference类型的参数,甚至对于const之类的参数cv都无法处理。 我定义了一个简单的functor那么它的operator()有两个overloaded的参数类型以及连带的返回值以便我们区别两个形式。那么它的forward_adapter能否给出正确的返回值呢? 我们可以不使用boost::result_of直接使用std::typeid来获得functor的返回类型,结果出乎我意外的是第一个形式的函数被无视了 结果所有的typeid返回的都是long在我的系统里它就是一个缩略的l这个说明了什么呢?我的理解就是虽然从c/c++语言的角度来看这个重载是允许的,但是实际上是不成立的。我换了一个测试方法准备用实参而不是型参来测试它的类型 尽管我定一个了一个string&的string变量而且还用static_cast但是编译器依然抱怨ambiguous说不知道要采用哪一个函数,说明什么呢?也许我要读一读这个所谓的forwarding problem
  2. 这个forwarding problem,似乎让我明白了一点点,读的很吃力。我就保存一份吧。顺便说一下,这个问题并不是boost版本能够解决的,我试过1.72也是一样。我差点忘了改回1.65了。
  3. 这里的文献都是精华,就是说大部分都读不了也读不懂,估计也没有人有时间,至少我是读不懂的。
  4. 我对于forward problem依然不甚了了,这个是一个硬骨头而且我也看不出来我的需求是什么,只好暂时搁置一下。因为我测试的例子自己也不明白怎么回事,只是觉得这个可能就是语言本身的限制吧?我考虑我还是先回到最最简单的range上来吧。
  5. 我对于std::generate_n始终不死心,结果看到2011之前居然是返回void,即便c++11返回back_insert_iterator也是一个无用的东西,看了代码才知道这个是仅仅的output iterator连forward iterator都不算,根本没有distance的概念,所以,返回值也是无用啊?
  6. 我肯定是吃饱了撑的突发奇想怎样使用generate_n的返回值嵌套,这个实在是毫无意义,宛如俄罗斯套娃式的有何意义呢?我自己都很难看清楚代码,节省一个返回值而已,纯粹就是一个文字游戏。只不过把我之前的所有的概率分布函数套在一起产生一堆的的随机数,这个有什么实际应用呢? 结果是一堆无意义的随机数分别来自于毫不相干的分布函数

六月六日等待变化等待机会

  1. 补充练习random_shuffle。虽然是简单的重复,但是也需要练习,因为我才意识到它的gen函数指针需要提供一个参数返回值是在这个范围内的随机数。这个使用之前的概率分布传递参数我遇到障碍,只好回归最简单的rand取模的老办法。 用肉眼很难看出它是一个改变顺序的数列,所以我做了一个拷贝然后使用is_permutation来判断两个是一个数组改变顺序的来的,这个纯粹是多余,std算法会有错吗?不过就是实验一下运用的方法。
  2. 从来都是书到用时方恨少,我虽然也看过几次constructor的用途,可是临到使用又迷茫了,还是对于bind的理解不够,这里要把constructor当作一个functor,它的参数也是要bind的 结果当然是正确的了10
  3. 对于上文提到的这个使用boost::shared_ptr的consttructor来创建的例子还是经过实验才搞明白,还是平常很少真正应用的结果。所以把这个例子保留作为学习实践shared_ptr的记录,我居然不知道c++11库里的shared_ptr和boost的冲突这件事,可见平常工作中经常拷贝粘贴的后遗症有多重。 这个例子实际上是一个很好的使用constructor和new_ptr结合shared_ptr的好例子
  4. 我回过头来看发现我已经把我之前学习的东西差不多忘光了不过学习就是这么一个温故而知新的过程,现在回过头来看nth_element是一个非常难以实现的算法,所谓的难在于你如何能够经济的实现。如果你不排序,甚至不局部排序怎么实现?看了看stl的实现也是非常的复杂,因为我看没有什么好的办法,我当初自己实现我也忘记我是否参考了标准算法,可能受了一些启发,我现在再也看不懂我怎么想的。我甚至还想着能否使用max_element之类的多次反复使用,可是很快我就意识到这个投机的想法是基于说如果nth是一个很靠近最大值的能够快速实现的检漏的投机遇到有重复元素就无能为力了。总之,这个不进行局部排序是几乎不可能的。我的潜台词就是说使用partial_sort来实现nth_element肯定是overkill,但是如何经济的实现是非常困难的。
  5. 在range这个库里货色非常的多,单单包装了所有的algorithm就值的熟练掌握,现在我有发现所谓的boost/range/adaptors.hpp里面有大量的我不熟悉的东西
  6. 这里是第一个很铉酷的reversed_range的运用,原来的例子很酷的使用重载的操作符|我看得不知所以然,所以,我就使用我能看得懂的range_detail::reversed_range 例子里是这样子的 可是我对于这个adaptors::reversed不理解,这个重载的操作符感觉太超前了,所以,我就使用中规中居的 而且原来的作者的这个例子我还不知道怎么添加分隔符我看到怎么添加分隔符了,这个完全就是std::ostream_iterator的本身的constructor的参数,我怎么没有一点印象呢? 我看了解说才明白其实我不需要这么直接使用这个低级的对象reversed_range,因为使用adaptors::reverse是一个shortcut,仿佛make_pair/make_shared_ptr之类的可以免去你提供模板参数的烦恼,它帮你调用reversed_range。所以

六月七日等待变化等待机会

  1. 我一直有个疑惑就是namespace range为什么可以写成namespace boost?就是range里的那些算法却被冠以boost,这个在哪里定义的呢?namespace一直是一个最让程序员头疼的问题,因为很难debug其中的错误,一旦加入非常难以清除,所以是一个非常要小心的地方。
  2. 要怎么理解range::adaptors的operator|的重载呢?文档称之为“发生器”。 这里的代码可以帮助你理解 这里代码有趣的地方是关于定义reverse_forwarder这个类型,它就是一个空结构,唯一作用是作为operator|重载的签名而已。而这一切就是为了掩盖内部细节实现类reversed_range,这样子避免了用户关心实现细节。这是一个很高明的做法,这个空结构其实作用和一个enum类似,我想这个做法是尤其灵活与扩展的,因为一个空结构里将来是可以放很多东西的吧?而一个enum仅仅是一个空标志,容量是有限的,同样的实现似乎结构更加防止了程序员的滥用,因为有时候用integer去强制转换enum也许是可以的。而对于一个结构的话很少有程序员敢这么胡来。比如我看到这个莫名的结构就心存畏惧动弹不得了。
  3. 我对于adaptors的各种filter感觉实在是太神奇了,仿佛开创了比lambda更加神奇的一个领域,比如你灵活的使用filtered之类各种各样的adapter,可以灵活的实现原来algorithms里的种种的_if版本的算法。作者给出的例子我发挥了一下就是怎么实现count_if 你难道不佩服这个代码的简洁吗?我对于boost::size的来历依旧感到迷惑eclipse指示它是定义在range里的,这个我不怀疑,问题是为什么它被包含在了顶级的boost,肯定是在那里做了一个alias,问题在哪里呢?为了看到头文件的预处理结果,我使用cpp infile outfile这个强大的工具。我意外的看到了我不熟悉的initializer_list,它原理就是一个很简单的模板类,简单的实现了数组的begin/end/size的容器方法,所以是一个很轻量级的数组包装。需要它的原因呢?终于我找到了boost::copy的定义源头,原来是在boost namespace里使用using range::copy把它暴露在了boost里。这就是一个典型的using的应用场景。 这里是一个把作者的例子串在一起的令人印象深刻的例子 结果是令人惊讶的
  4. 看到一个可疑的网站怎样查看它的证书呢?那么这种Let's Encrypt证书为什么不安全呢?
  5. 这里的关于adaptor的概念很重要,但是我还是不确定我理解的正确难道operator|()是不会改变lhs的参数吗?也就是说容器的原始数据都是做了一个拷贝?而各个adaptor模型还要深入理解一下,因为以前我就对于iterator的所谓的forwared/bidirectional之类认识模糊,似乎adaptor和iterator的model还是有些微的差别吧?
  6. 对于adjacent_filtered我觉得实现上有缺陷,对于长度为一的容器应该特别处理才行,否则返回值似乎意义不大。没有相邻可以比较到底还是否应该返回?不是长度为一的问题,而是始终都返回第一个元素的问题,比如两个相同的元素,使用not_equal_to居然还是返回了一个元素。这个是明显的问题。 返回一个结果0有意义吗?我主张不返回。
  7. 对于copied的定义文档说的太含糊,我简直不敢确认我的理解。所以实验证实两个数值是所谓的sub_range或者slice的概念。 运行结果表明slice是一个半开区间。
  8. 对于filtered就是很明确了。 结果显示只有50的元素才被拷贝。

六月八日等待变化等待机会

  1. 我觉得单单阅读indexed的文档我就感觉非常困难,因为作者根本不把读者当作一个五岁的小宝宝,认为你能阅读这个文档肯定需要一二十年的c++编程经验,那么文档都是写给开源项目组看得样子货。结果我硬生生的就是没有看懂。比如我理所当然的认为这个indexed肯定也是一个返回类似的iterator_range之类的可以实现operator|进行流水线作业的,所以,我天真的想着把adjacent_fitered和这个indexed连起来想着可以把一部分过滤掉,尽管我的理解就是一个类似sliced的东西可是如果有了sliced,何必要indexed?难道有这么trivial的adaptor吗?所以我的理解完全是错误的!我发现我的英文理解肯定是有问题:
    Purpose: Adapt rng to return elements that have the corresponding value from rng and a numer.
    所以它就是一个pair,这个很令人吃惊吧!还是看看作者自己的注释了解一下比文档中没有说的理由吧,因为程序员只有在代码中间的注释写的才是程序员自己看得懂的,因为这个地方写的东西仿佛是武林秘籍字里行间的心得体会,或者给自己看,或者是给武林同道看,要么有炫耀,要么是帮助将来自己回忆用,都是字字珠玑的真言,不像文档里都是写给外行人看的废话。
     Why yet another "pair" class:
     - std::pair can't store references
     - no need for typing for index type (default to "std::ptrdiff_t"); this is
     useful in BOOST_FOREACH() expressions that have pitfalls with commas
       ( see http://www.boost.org/doc/libs/1_44_0/doc/html/foreach/pitfalls.html )
     - meaningful access functions index(), value()
    
    关于这个例子你究竟看懂了吗?我一眼就以为我懂了,做下去始终有问题,这么简单的东西我都搞不懂吗?如果把那个auto换成真正的类型我要怎么办呢?这个还真的不容易,我看代码看了好久才领悟了返回的是一个indexed_range,它的成员是indexed_value,使用的itertor是indexed_iterator,实际上一个auto掩盖了所有的这些实现细节,而且作者的目的也是不需要你了解这些实现细节。这里我要承认我对于这个for+:的loop模式不熟悉,一开始颇为困惑,我把它和BOOST_FOREACH老是混淆,对于宏的实现我有天然的抵制,那么对于这个我怀疑是操作符的重载,难道是c++的新语法我没印象。看来的确是c++的新语法我可能看过但是觉得不喜欢就忘记了(有意识的)。我还能说什么呢?我抵制新的语法? 看看结果就明白它的元素是tuple了:
  2. 如果把adjacent_filtered的所谓缺陷改个名字叫做uniqued,那么我就无话可说了,事情就是这样子的,因为我对于操作符less对于一个操作元的情况耿耿于怀所以不肯原谅前者,而当后者以前者的特殊化的实现冠之以"unique"我就欣然接受了,让我联想到朝三暮四的猴子的行为。但是我遇到的很多不兼容的range,比如adjacent_filtered_range是不能和原来的range兼容也就是说我可以使用sliced作为它的输入,但是不能反过来。 就是说你可以取一个slice来作unique-lize 但是你不能反过来 其实这个问题逻辑上也是有道理的因为作为sliced的参数不能越界,那么unique之后的长度是多少我自己都不知道,这样子使用本身就是危险的。
  3. 我怎么也没办法假装爱上adjacent_filtered,因为除了不可饶恕的无条件输出第一个元素以外,似乎它也不支持lambda比如 编译器总是抱怨什么error: ambiguous overload for ‘operator++’这个实在是耗尽了我的耐心同时因为看起来它的代码不是那么容易理解,我很怀疑是否真的有人在使用?
  4. 我觉得之所以这个adaptors不那么受人欢迎的一个原因就是有一些的实现看起来很有用,但是作者也落入了他自己批评的他所要改进的圈套,因为adaptors原本就是瞄准把一些_if进行取代,比如remove_if,copy_if,find_if等,可是这些adaptor的功能有时候也沦为了鸡肋,比如“indeirected”这个就是一个例子。我完全可以使用lambda来轻易的实现。当然作者的operator重载依然有他的价值,不过。。。 针对这个indirected例子我的反思是有一点点过于简单了,作为能把一行简单的代码写的高大上的风格似乎应该这样子才行。(我这是找打啊) 作者原本的用意是使用简单的手段作为一个“adaptor”和我的思路是不同层次的。
  5. 作为map_keys也是有一点点模糊的意思,首先名字就让人困惑,输出的是keys,我还以为map是动词mapped的意思,所以,很多误解,那么是否我们也需要map_value呢?这个就让人发愁了。我花了一点时间重复了我的主张,看起来作者的做法的确是有可取之处,我的思想似乎是在变化,也许是和我的饥饿感成正比? 我因为忘记了key++结果map里就只有一个元素,我却去debug了好久!
  6. 还真的有map_values啊!我居然是猜出来的。 我的代码只需要改一点点 结果是这样子的
  7. 关于replaced的一个好的东西就是它可以连续使用,这样你可以修改多个元素。比较replace这个肯定是比较让人青睐的。更要注意的是从效率的角度来看并没有什么提高因为它和你反复调用replace_if没有差别,但是很有意思的是replaced_if最后的修改是基于之前的replaced的基础上的。 结果是把2,3改成了5,6
  8. 对于type_erased我确实是难以理解,按照作者的意思也是一般就是使用type force cast之类的,而且似乎它也不是默认就包含在adaptors里的,所以,我觉得可能不是很有用吧,当然我对于它的实现也是很不理解。
  9. 对于regex相关的tokenized我是比较难以理解,最主要的就是iterator的千变万化的运用难以掌握。 照抄例子,初步的理解就是依靠regex来筛选token,那么打印的分隔符是无关的。主要的难点在于match_type这个类型是一个sub_match,这个我以前理解就是两个指针组成的区间,可是居然可以作为ostream_iterator的模板参数,这种使用我脑子转不过弯来。 如果不使用tokenized那么我就要自己写一个遍历regex_search的代码
  10. 一个十分简单的问题可是我因为以前没有意识到以至于debug了好久,中间我始终在怀疑regex的问题,没想到是我的读取文件的问题,就是在ifstream> >str的时候它使用的是locale默认的whitespace来作分隔符结果就把所有的words都紧贴在一起了。这个大侠使用自己的locale里的ctype来重新定义whitespace,这个对于我的简单的需求确实是一个过于复杂的方法了。这个对于我确实很意外,我可能从来没有意识到我的这种读取文件的做法有这个问题? 我以前居然没有注意到所有的word已经去除了空格成为一个word了! 所以核心的问题在于iterator的类型,我意识到这个问题后改了一下代码,首先我获取的一开始就是string作为模板参数,同时我再把分隔符加回去,当然这个不是还原,因为肯定不一定一样的whitespace,因为它有多种选择的。 总之,我得到了words
  11. 经过这段时间的学习我对于大师的演讲有了更深层次的理解,现在才开始明白大师说的“盲人摸象”的比喻有多么的深刻。

六月九日等待变化等待机会

  1. 这个大概是最后一个adaptor:transformed。其他的如strided我感觉不知道要有什么应用的范例。这个部分就告一段落。 结果如此
  2. 关于range::algorithm我现在有了更深的认识,现在发现它的文档有关键的部分我一开始并没有理解。
  3. “教尔做人不做人,教尔不苟竟狗苟。而今俯首尔就擒,仍自教尔分人狗。”陈毅对叛将郝鹏举的最后训斥。)
    张晓明:今天重温邓公这些讲话,对他的先见之明钦佩之至。这是讲话的全文123。我想保存一个版本。期待十年甚至二十年后如果可能的话再来看看有多少应验。
  4. 现在从哲学的角度来看待adjacent_filtered的问题就会明白这个是不可能解决的难题。它的机制是依靠所谓的skip_iterator来跳过不符合的元素,那么第一个元素它要和谁进行比较来决定是否跳过呢?没有,因为它是第一个元素没有办法和任何人比较,就好比一个开天辟地的新线事物你硬要它和之前的同类比较,这个结果是未定义的。只能选择放行。可是反过来说对于任何命题如果没有可以比较就为真的话,这个反而是正确的,比如unique这个命题,对于任何元素如果没有前人可以比较它就是新鲜事物,unique这个命题就成立。那么这个情况下反而adjacent_filtered就是正确的。也就是说如果你不是那么苛求它去比较不存在的前人就自动正确的话,这个算法是正确的,可是你非要要求它一定要比较前人才能正确的话,它就不满足你的要求了。因为它只是一个filter不能返回去剔除元素,能不能剔除必须在第一时刻作出结论。
  5. range里有很丰富的algorithm几乎覆盖了std的所有的algorithm,而有一些新添的算法在boost/range/algorithm_ext.hpp里。比如boost::erase算法其实是非常的贴心的一个东东。
  6. 我之所以昨天说range的算法里有很有意思的东西就在于它的返回值的区间是我之前一直想去作的而渴望的。就是一个流水线如果对于没有改变的range是简单的,但是对于改变的怎么作呢?我本来还苦思冥想使用返回i的Iterator作流水线,而作者早就想到了这个,通过模板参数决定返回的区间是什么类型的。其中的boost::return_begin_found和boost::return_found_end才是你最常用的,我一开始错误的理解了return_next_end, // [next(found), end) range实际上对于unique的返回值应该是return_found_end,如果你想删除无用的数据元素的话。 注意结果删除的重复元素是因为返回的range是return_found_end,就是说要包含"found"本身。
  7. range里的算法非常的强大,就是说你可以类似“复合函数”的意味来反复处理一个容器,形如流水线,但是需要注意到的一点就是如果你不加模板参数的话,返回值是原本的std的原始算法返回值,比如remove_if之类返回的就是默认的一个iterator,这样就无法完成下一次的流水作业。所以,要加上模板参数。如果看源代码模板参数原本还需要两个,就是原本std::remove_if的模板参数就是容器和命题,可是c++的编译器是如此的强大它会自动进行匹配,所以省却了你为了什么是predicate的类型而苦恼! 这个是一个标准的流水线作业,首先排序,然后去重,再删除无用的数据,最后挑选大于5的元素再把多余的部分删除,然后把解果拷贝到显示。一气呵成! 这里我一开始还误解了以为remove_erase_if这个不行,因为它实际上做了两件事,先remove_if然后erase。所以它的做法就不一样了,需要分开来写所以可以把以上的两个步骤合而为一就更加的简洁了。 结果是类似的,哪一个更好呢?
  8. 我对于copy_backward始终觉得用途有限,结果它的返回值也没有实现期待的变化。copy也是这样,我不明白为什么返回不可以是目标区间? 很无聊的结果
  9. 基本上这就是一个复习,等于是把之前std的algorithm都复习一遍。这个inplace_merge我们能做什么呢? 结果显示两个片段合并成一个排序的状态
  10. 看来我是过于乐观了我以为几乎所有的算法都实现了range_return,结果似乎只有很少的几个实现了。我搜索了一下大概就是这一些实现了,他们大部分是所谓的non-mutating algorithm这个让我感到很失望,这个feature其实很有用但是并没有全部实现。 所以这些就是实现了range_return的算法。这里的awk使用:作为delimiter。学而时习之不亦乐乎?我现在重新读自己的笔记竟然也有些摸不着头脑,实验了一下才明白我是在/usr/include/boost/range/algorithm下运行这个命令查看究竟有多少的头文件使用了range_return的返回值。 这里是remove_if的例子 结果是
  11. equal_range究竟有什么应用呢? 结果是截取一个片段
  12. 这个纯粹就是练习,比如怎样实现search_n呢?我用了一个简陋find_if的方法来实现。 search_n的定义是寻找一个子串它必须所有的元素都等于目标值,这里是5,它的长度必须是一个长度这里是4。那么我就定义了一个if_then_else_return的函数,利用一个计数器来看长度是否达到4再返回。 search_n返回的iterator是这个sequence起始的位置,我的结果是当前长度达到目标值的位置,所以需要一个转换。
  13. 新算法里这个iota的名字实在是令人难以理解,文档就更是有些模糊。其实是一个非常简单的小功能。后来我才发现是我孤陋寡闻,这个是c++11的新函数,确实功能让人难以理解了。 结果就是在基数10的基础上每个元素都加一。这个算法近似等于我的这个
  14. 这个insert似乎也不是很难实现,不过应该说确实是很handy的。我使用copy+inserter来实现它。 一目了然的我的结果和boost::insert是等价的。都是把from插入到to+3的位置。
  15. is_sorted是一个颇费思量的实现,不是说有多么难,而是排序遵循的predicate不是strictly-ordered的时候怎么检验的问题,我说的是我打算使用adjacent_find来实现它,那么我要实现的一个“non-strictly-ordered”或者说“semi-strictly-ordered”的否定命题。为什么?比如greater是我的排序命题,它的否命题不是not(greater),而是greater(_2,_1)&&not(greater(_1,_2))因为我要处理元素像等的情况。 这个结果证明了positive的例子,就是说我的算法可以处理元素相同的sorted的case。我觉得这就足够了。不是吗?

六月十日等待变化等待机会

  1. accumulate是定义在boost/range/numeric.hpp它其实可以使用一个简单的for_each实现,那么这就是一个辩证的哲学问题,究竟我们希望程序员应该怎样编程呢?比如一个循环一个加总之类的trivial的几行代码,我们是希望程序员简单的写这些重复的简单代码好呢?还是让程序员依赖他们的记忆力去使用某个标准库去编程?从出错的概率来看你要赞成后者,因为你可以说程序员应该是专注于更复杂的逻辑问题而不是浪费精力在鸡毛蒜皮的具体库函数上。话说的是很好听,可是这里的潜台词是你希望程序员熟练记忆一大堆的库函数,不管这个库函数有多么的trivial,那么用记忆力来替代简单的编程能力的丧失。究竟熟是孰非是个很难争辩的论题,它全都取绝于这个trivial的代码究竟trivial到什么程度,究竟有多大概率程序员在trivial的代码上犯错误。从另一个角度来看标准库在很多时候并非“标准”因为在程序员的世界里从来就有操着不同口音的异类,而反权威是每一个有“才华”的年轻人的不二选择,因为说到底编程语言就是一种语言,而编程用到的库函数更像是一种方言,或者某个小团体的行话,垄断话语权永远是编程世界的主题,你使用了某个库函数就被认为接纳了某种方言,入了某个帮会,人们被人为的分隔在一个个小世界里彼此不想往来。究竟是否应该有一个开放的编程库呢?我们看到boost是很多有才华的经验丰富的大师贡献分享他们多年积累的舞台,然而,在分享的过程里我们还是能够感受到贡献者自己的思想习惯理念,你接受的同时也被他所左右感染。从正面来看是一种提升学习,从负面看你接受了一种思想不得不排斥其他的思想。这个是永远的无尽的主题。目前我想也许给大家一个选择也许未必是坏事,因为lambda这种编程方言能开辟另一个让每个人不再依赖于很多简单的库函数。 结果显示两个结果一致
  2. adadjacent_difference实际上是和std::adjacent_difference一一对应的。 我过了两个月自己都看不懂自己写的代码了,原来我的这个freq是英文字母的出现频率,而discrete_distributtion应该加起来是100吗?我是一点也想不起来了。 结果显示了字母之间的差值我看不出有什么特别的分布特点。
  3. 这个纯粹是为了加深对于inner_product的记忆。没有什么更好的实现办法了,我只能分配临时的对象使用transform作乘法然后使用accumulate作加总。 结果如此
  4. 相比之下partial_sum的实现是很容易的。可以很标准的使用transform实现。
  5. 我忘了for_each是可以handle rbegin/rend的了。 for_each对于iterator的类型不讲究
  6. sort_heap就是连续的调用pop_heap,但是前提是代码根本不能判断heap的边界,就是说你本来必须指定heap的结束的位置以便把pop的元素放在哪里。可是range的algorithm都是用一个range来作参数,你并不想heap总是在end,所以这个是设计的缺陷,因为pop_heap是只有一部分才是heap,我感觉这个算法的range的包装是不成功的。比如我本来可以通过不停的调用pop_heap来实现sort_heap,可是我传递的参数始终是同一个容器,这个是无法达成的。 我反复调用pop_heap就是sort_heap创建了排序的结果
  7. 这个视频压缩的文章我很想收藏一下。

六月十一日等待变化等待机会

  1. 前两天感觉有些太累了,今天不得不慢下来。这个是关于range的新的篇章any_range。之前已经接触过了所谓的type_erase,但是感觉没有什么用,因为似乎type_cast能作的事情不需要专门这么一个东西。今天作者推荐读这篇关于type erase的文章,希望能够理解用意。今天估计是看不完了,因为到了具体的讲解就是高了一个数量级的难度。就是说明白了why和明白了what与how是根本不在一个数量级的难度。很多普通人包括政治家都有能力明白why,但是在what与how的问题上就停滞不前了。这就是人和猿猴的差别。
    • 这段话的深刻程度是每一个开发者应该时时刻刻牢记在心的:

      Remember, in the world of OO programming, an interface exposes what an object does for its clients, not what the object is, and not how it does what it does.

    • 究竟为什么我们需要type erase呢?作者首先从一个实际的例子开始的。
    • 这个是作者希望大家牢记的
      Good engineering involves compromise at every turn. A good, working, finished product is never pure by the standards of any one idiom or methodology. The art of good engineering is not the art of discovering and applying the one right idiom over all others. The art of good engineering is to know what your options are, and then to choose your trade-offs wisely rather than letting others choose them for you.
    • 这个是另一个关于优化的箴言

      Optimization whose effect no user ever notices is the root of many evils, among them the failure of software projects and the subsequent failure of businesses.

    • 作者点出了什么是type erasure:

      Type Erasure as the Glue between OO and Generic Programming

      In their book on C++ template metaprogramming[4], Dave Abrahams and Aleksey Gurtovoy define type erasure as "the process of turning a wide variety of types with a common interface into one type with that same interface."

  2. 下一步需要学习的是boost::iterator因为有很多和std::iterator不一样的东西,尤其是iterator_adaptor在range里已经表现出了异常的灵活度。这里是学习transform_iterator的例子 结果就是使用iterator的操作是把transform的逻辑包含在iterator里而不是包含在一个tranform的函数里面。这个是一个思想的变化。

六月十二日等待变化等待机会

  1. 我愤怒是因为每天都在一些无比愚蠢的事情上浪费我宝贵的精力!关于boost_unit_test_framework似乎就是一个被人遗忘的部分,我花了差不多大半天时间来理解一个hello world级别的错误:main在哪里?
    • 首先我看到很多的库里都使用boost/test下的unit test写的测试例子,那么要怎么运行它呢?我经常感到困惑。模糊记得有些库使用一个test_main来定义自己的测试例子。
    • 比如在range里有这么一个例子那么我要怎么编译呢?很明显的我需要链接boost_system,还需要boost_unit_test_framework,然后我就始终得到链接错误找不到main,难道我需要定义自己的main吗?我定义了要些什么呢?读这些关于test的文档似乎从来没有任何人有这么白痴的疑问啊!
    • 阅读源代码我只是更加的困惑似乎定义的main是为了专门测试unit_test_framework动态库本身的?因为我尝试BOOST_TEST_DYN_LINK和BOOST_TEST_MAIN完全不是为了运行测试例的!
    • 然后我就开始了漫无目的的搜索,和阅读文档,可是我需要关心怎么写测试例吗?我连hello world都不成功的时候!但是隐隐约约的很多人都在指出必须要链接静态库因为动态库使用的是另一套宏命令比如auto什么的,我完全摸不着头脑,真的吗?这么无耻?
    • 然后是我在我的eclipse for c++的配置里怎样强迫链接静态版的boost_unit_test_framework呢?在-l里做文章是徒劳的,写全路径是一个我最讨厌的做法,因为ubuntu的toolchain的设置现在安装boost库是在/usr/lib/x86_64-linux-gnu下,想想吧简单的64/32版本是不足的,因为工具链的名字需要考虑的,这个是我不愿意踏足的地方。
    • 最后我只好在eclipse里使用linking flag里的-static来强制所有的库都选择静态库如果同时有动态和静态的话。在eclipse你看到如下的命令
    • 可是我的问题是什么?我的问题是boost unit test framework链接main找不到啊,我为什么折腾这个?因为只有static 版本里定义了main,动态库是另一套做法,我的感觉是可以定义你自己的main来呼叫testcase,可是谁需要这样子的情况呢?为什么最简单的情况不是hello world的情况!有多少人第一次使用他是定义静态库的呢?
  2. 有时候我是很迟钝的看不懂编译器关于模板参数匹配的错误,这个简单的问题几乎花掉了我一个晚上的时间。最后才明白原因是range里的find有一个新增加的模板参数range_return_value我却忘记了,它的出现导致了匹配上的莫名其妙的错误。(应该不是莫名其妙,是我不理解看不懂错误) 比如这个简单的例子的一个定义,我发现了一个奇怪的现象就是明明有boost::find为什么作者还要费劲的自己定义个新的几乎一模一样的find呢?于是我就单独实验这个小函数 但是我始终编译都通不过,我迷惑的是参数完全合法啊。最后我才意识到find<return_found>才是真正的返回iterator就是和std相容的类型。太让人气氛了。浪费了一个晚上的时间。
  3. 今天非常令人沮丧,我完全就是被羁绊在细枝末节上,完全忘记了当初需要解决的问题是什么?我本来是要继续领会iterator的结果不知道为什么却跑去折腾unit test,完全是因为我想要运行作者的unit test来理解他的用意,结果跑偏了十万八千里。这中间我才明白了一个基本的道理,所谓的boost::forward_adaptor是一个纯粹的类型转换,就是说推理参数类型并加以转换。就是说他是在你需要把一个函数或者functor要作为参数传递给另一个函数的时候的定义型参的帮助。他根本和实际调用无关的。这么浅显的道理我却一直不理解!

六月十三日等待变化等待机会

  1. 对于range的所有算法的对象,也就是作为参数的所谓的range是一个什么对象我始终没有一个清晰的概念,只是模模糊糊的觉得就只能是容器要支持begin/end的方法的容器。可是今天看了作者的测试例的代码才意识到这个还包含pair就是把容器的begin/end两个iterator的pair也是支持的。这个是给人一个全新的认识,所以作者提供了这个例子要强调这个可以所谓range的一个所谓的“view”的概念,这个是非常强大的,因为之前我记得我对于sort_heap的抱怨,因为pop_heap的操作是一个需要你指定heap末尾的算法,也就是说heap结构不提供size这个property你必须自己指定heap的结尾,然后pop_heap才知道你要把元素放在哪里。而sort_heap的算法依赖于你不断的调用pop_heap达到的,所以如果range支持这种pair的形式你就可以灵活的使用这个所谓的"view"了。 结果说明这个pair是等效的。 我想说的就是这个改进唯一的效果就是我能够把原来的std::pop_heap改为boost::pop_heap,因为原本我认为我没有办法调用后者,可是这个小小的改进究竟有什么意义呢?从编程的角度看我几乎没有任何的改进,我少打字了吗?我能够避免使用循化而纯粹遵循functional programming了吗?也没有。所以以下这个代码属于完全的无意义: 从结果来看看这个sort的效果
  2. 对于这个例子的理解就很困难了,因为很多是一个type的问题,不是那么明显的逻辑的对错问题,先留意下以后再跟进。eclipse里和visual studio一样可以设置代码文件是否参与编译,这个功能就很贴心的。因为我可以把多个boost测试例放在一起一个个运行来学习。总而言之,这里的测试例子往往是比较高级的检验有些是防止升级出错的高级的watchdog,我看不懂是正常的,我还是回过头来看example吧。
  3. 开始读iterator。我感到震惊的是我对于bvector是一无所知的。就是说vector<bool>不是一个真正的container,因为根据这篇文章的开宗明义的例子它就编译不过。很快的我就被带偏了因为要学习iterator的高级功能你是要恶补metaprogramming的,这个我觉得比functionalprogramming还要艰深。
  4. 我其实已经反反复复看到这个type generator,但是我不知道它有这么一个学名,以前也没有看过它的解说。而更高级的做法是使用所谓的metafunction这些都是当初我望而生畏的mpl的基础部分,现在我饶了一大圈涨了一些经验值再回来啃硬骨头可能好一些吧。
    • identity是很容易理解的。可是apply就有些玄虚了,我看了好久才明白它就是名如其人把一个lambda运算绑定它的参数evaluate一样。看懂这个例子至关重要,首先我们要明白一个mpl::int_这个模板类是一个把所有的整数都作为各自的类型,这个是我很早以前就幻想的一个实现,任何自然数都是他自己的类型。需要include boost/mpl/int.hpp然后这个模板类实际上定义了一个整数类型的相加的函数。这里说函数确实比较奇妙,它是类型的计算函数。因为每个整数都是不同的类型,而类型想加是他们的值相加之后的和的一个模板类型。(多么的拗口啊) 接下来的测试是检验和展示怎么使用这个函数。这里关键是展示怎么使用apply函数,它把lambda也就是一个计算类型的functor类似的东西和其后的类型参数进行计算。apply所得结果一定会又一个type定义的,如果一切合法的话。而这个结果类型r1,r2通过他们的value我们可以证明我们的计算是正确的。不过我对于quote2这个函数的代码看不懂,它似乎什么也没有作? 总之这个mpl::quote2是一个很复杂的东西,我只能理解它是一个模板模板类的东西,因为我们的int_plus本身是一个模板类,他的模板参数是两个类型。。。我的脑子要炸了
    • 这里是关于quote的文档

      quoten is a higher-order primitive that wraps an n-ary Metafunction to create a corresponding Metafunction Class.

      看到我划的重点了吗?它是高级函数,难怪我脑子直接爆炸了。它比我生活的维度高了一个数量级啊。不过看了例子我也就恍然大悟了,quote内部定义了一个内生的类apply其实就是真正计算类型的,它或者看看绑定了的参数的结构是否有一个成员变量type定义了,如果是的话就用它,否则就是原生地绑定的类型作类型。 f1有定义自己的type,那么 内生的这个结构quote1<f1>::apply<int>::type当然就是使用那个type了,它就是int了。所以这个内生结构的名字叫做apply是恰如其分的。这些就是metafunction的强大的power,类型的计算是高出一般程序员领悟的高阶运算,是不同维度的事物。
    • MetafunctionMetafunction Class相比后者又是更高一级的东西了吧?真是太高了我仰望的脖子都酸了。首先metafunction的定义是很清楚的

      A metafunction is a class or a class template that represents a function invocable at compile-time. An non-nullary metafunction is invoked by instantiating the class template with particular template parameters (metafunction arguments); the result of the metafunction application is accessible through the instantiation's nested type typedef. All metafunction's arguments must be types (i.e. only type template parameters are allowed). A metafunction can have a variable number of parameters. A nullary metafunction is represented as a (template) class with a nested type typename member.

      划重点了:首先它一定是参数是各种类型的模板类,一定有一个typedefed成员类型type作为返回值
    • metafunction class是这样子定义的

      A metafunction class is a certain form of metafunction representation that enables higher-order metaprogramming. More precisely, it's a class with a publicly-accessible nested Metafunction called apply. Correspondingly, a metafunction class invocation is defined as invocation of its nested apply metafunction.

      看了定义就明白了quote就是真正的metafunction class,因为它是靠着内生的apply结构去试探计算类型。。。
    • begin是mpl里的不是range或者说boost的begin,而且mpl里的vector也是完全两回事啊。所以,编译这一个简单的例子最好都使用明确的namespace才好
    • Iterator Metafunctions还是比较容易理解的,advance比较简单。

六月十四日等待变化等待机会

  1. 不知道你是否如我一样的感到震惊,居然能够使用这样的prior/next来计算类型!这个例子看起来似乎平淡无奇,但是细思极恐,它怎么做到的?首先我甚至不知道first的类型究竟是什么!
    • 首先你没有办法使用typeid来辨别类型,因为它是探测变量的,而参数是类型是没有办法使用这类函数的。所以唯一的办法是使用boost::is_same<T,T>这个方法。其实你看到boost::is_same就知道这个是怎么实现的了,很有启发性的,说到底这个就是最核心的技术,使用模板匹配
    • 我对于mpl没有一个门户式的入口头文件感到很烦心,无意中发现boost/dll.hpp似乎能够完全覆盖所有的mpl的头文件,我以为它有什么神奇的办法,怎么发现呢?还是使用cpp来看preprocessor的处理结果,然后发现其实没有什么神奇的,因为dll.hpp里的头文件把mpl下的很多用到的头文件包含了。所以,最好的办法不是只包含一个无所不包的头文件而是用到什么包含什么。比如我的这个测试需要的就是
    • 我每次接触这个metaprogramming就感到绝望,因为它比functionalprogramming还要困难,它几乎连debug的机会都没有,这个比看generic programming的模板错误还要无助,因为你手头没有什么工具来查看类型。我模糊记得好像boost又一个类型的assert东西也许可以借用?
    • 如果没有静静心心的去阅读iterator的概念比如这个是最简单的forward iterator,不要以为你理解了对象的iterrator你就里所应当的理解类型的iterator,他们相似但是高了一个维度。是不可能理解这些metafunction如next/deref
    • 首先从理解integral constant开始。就是我们所说的每一个整数都被包装成了一个metafunction。这个我是没有意识到的,我脑海里还是没有形成metafunction和metafunction class的区别,我依旧用面向对象里的类来理解metafunction这个就不对了,因为它们应该更加像functor,所以他们是函数!我要牢记这个经典的例子才能理解什么是int_ 这个是令人震撼的地方,基本上编译不通过也就以为着assert不通过,这些都是compile time就能决定的类型的计算!那么你可以想像整数作为的metafunction可以像Iterator一样的使用next/prior,那么integral constant是否是一种iterator呢?这个似乎是最最简单的metafunction,而且作为基础如果这个没有理解的话就不要再深入了。彷佛很多艰深的武林秘籍的学习需要有最起码的内功基础,如果不达标却一意孤行只会走火入魔的。 我这里追加了一个小小的补充,这个对于加深理解两个方面,首先所有的metafunction的参数必须是类型,我始终还是抱持着模板参数可以是常数的概念这个是不对的,这里就看到了必须传递一个int_的参数作为类型参数。其次,很多时候编译器报出来的莫名奇妙的错误,可能仅仅是因为你没有包含正确的头文件,比如boost/mpl/advance.hpp
    • 这个bool_可能是比int_更基本的,我要牢牢记住掌握,尤其要注意有很多的常量metafunction比如true_/false_。
    • 再重复一遍char_以便加深记忆。哪怕拷贝粘贴一遍也是学习因为你会发现它需要头文件boost/mpl/char.hpp。所以,毛主席以前说高考作弊其实也不是坏事,因为作弊的时候抄答案也是一种学习,抄多了就学会了,我的理解就是当时的工农兵子弟基础太差高考对他们太不公平,干脆就放水让他们去大学读书改变阶级固化。当然我主要是觉得抄答案也是很好的学习。人就是猴子进化来的只会模仿。
    • 这个size_t可不是POD的size_t,它更像是int_的unsigned版本吧?
    • 我现在还不知道ntegral_c是做什么用的,难道是一个传递参数?他和vector不同的地方在于vector_c存的是类似于实参一样,
    • 我怀疑vector必须完全一样他们的元素类型才能一样?我始终无法解决一些我的猜想。
  2. 我遇到一个无法理解的问题就是mpl::distance的类型是什么?为什么以下不对呢?这个第二天才找到答案

六月十五日等待变化等待机会

  1. 昨天百思不得其解的其中一个问题终于有了一个发现,这个多么的微不足道,但是它揭开了一个小小的窗口,它透露出的光亮彷佛当年孙悟空被封在一个金角大王的乾坤宝物里连金刚钻也凿不透一样,突然之间角母星君用它的天然犀牛角钻出了一个小逢,可是只要有了这个小小的缝隙齐天大圣就懂得怎样腾挪辗转了。所以的它的意义就是开创了怎样透过编译器的错误信息来查看对象的本源的先河。这个是很简单的一个vector_c的“假”向量,我说它假是因为它不是真正的vector,因为,真正的mpl::vector你只能用typedef来声明更新这个理由太牵强,难道vector_c不需要typedef来初始化吗?它的好处是可以更加灵活的初始化,尤其是它包装了integral_c的类型,可以不用枚举式的罗列,而当使用mpl::push_back之后我发现它的成员的类型就变了,我的猜想是我通过mpl::begin/next之类的iterator来访问,而这些类型是需要整个mpl::vector的类型的,就是说它哪怕增加一个元素的结果就是整个iterator类型的改变,这个是我昨天反反复复实验失败的猜测,需要再验证。但是今天对于vector_c的iterator告诉我们它的元素经过mpl::deref得到的类型并不是简单的mpl::int_而是mpi::integral_c。这个看上去预料之中的“发现”给我指明了这个方向,也许昨天我的猜想也许不对。 通过标准的范例来理解是我弥补阅读synopsis不足的缺点: 而在这个基础上我来检验vector_c
  2. 事情就是这样子的,一旦打开一个突破口很多百思不得其解的难题就迎刃而解,彷佛烧红的餐刀碰到黄油一般顺畅。昨天我一直不知道为什么的问题就是mpl::distance的返回类型的问题,文档里没有说,看代码看得头昏脑胀,我昨天隐隐约约的觉得是mpl::long_的类型,但是始终卡在一个地方,今天终于找到了,原来这种所谓的“复杂”的metafunction它往往依赖一种所谓的“apply”内部自定义模板类(或者更准确的说metafunction)来实现所谓的type,所以,你不能直接使用mpl::distance<first,last>::type而需要mpl::distance<first,last>::apply::type。这个就是我经过一天才意识到的“发现”。 所以,昨天我即使意识到了distance总是用mpl::long_而不是像当然的mpl::int_我也没有结论,因为我没有意识到apply的问题。这个彷佛《笑傲江湖 里魔教的大力神魔凿洞距离破壁只有寸许的薄薄的石壁,可是就是这么一层窗户纸捅不破还是前功尽弃。编译器只有100%的正确没有丝毫的含糊与模糊。(事实上编译器有模糊的地方,但是这些都是要避免的,而所有的似是而非都被当作了错误。这一点人类社会是完全无法做到的,尤其在政治文化领域里大家都喜欢利用其中的模糊含糊作为自己的优势)
  3. 明白了这一点我想发挥一下,可是这个关于advance的东西我昨天本来就没有疑问吧?不,我昨天也是卡壳在apply上,所以,这个是一个突破。
  4. 对于mpl::plus你认为它的实现容易吗?普通的想法肯定是这个是一个二元参数的函数,可是当您看到它是一个不定长的参数,你意识到了其中的复杂度吗?也许我们都认为连加减是小学生都会的玩意有什么难的呢?可是事实上,加法归根结底是一个二元操作运算。所以,当我看到它的实现还是心头一震: 是否受过训练的程序员第一时间就想到它是递归实现的呢?看来我的训练还不足。 很敏锐的你应该能够预言到以下的定义是不成立的,不是吗?最多递归就是三次四个参数! 对于mpl::integral_c能否像普通的mpl::int_或者mpl::long_一样的使用我一直心存疑虑,这个测试打消了我的疑惑,因为看代码就知道它实现了operator()是一个彻彻底地的functor(metafunction) 注意这里也就是明确的说明了mpl::plus返回的是mpl::long_的类型而不是mpl::int_这一点和mpl::distance很像mpl::distance是无条件的返回类型为mpl::long_,而mpl::plus是一个纯粹的模板函数,但是这里的类型转换是一个很高级的东西,我不是很确认int_转long_是通用的原则,也许是系统编译器相关的吧?所以,以下也是一个断言 意外吗?我刚才的断言就是打脸,这个怎么解释说明什么?说明二元运算的结果取决于最后一次的操作元。这个就是证明
  5. 对于众多的算术运算arithmetic operations来说,是一个举一反三的过程,基本上都和加法类似,唯一值得一提的是这个头文件覆盖了所有的算术头文件boost/mpl/arithmetic.hpp
  6. 在mpl的lambda肯定又是一个硬骨头,我且睡一觉在说。
  7. 我对于always的用法感到困惑,既然是水火不侵油盐不进还定义它干啥呢? 这个例子其实说明一切
  8. 如果你觉得always是无脑流的无聊,那么立刻这个unpack_args是立刻来了几个数量级的烧脑啊。 这个很难理解吧,我们按照文档来分解一下:首先这个unpack_args就是把一个metafunction连同后面的参数合成一个unary_function。这个就是证明 顺便说一下我对于boost::is_same的返回值突然有些迷惑就做了一个实验 这里实际上是展示了一个学习metaprogramming的工具,这个似乎是唯一的办法就是利用编译器来检验类型。我觉得我不需要再去看那个传说中的什么assert了,应该就是这个或这个类似这个断言的工具了,因为没有什么工具能够解决类型,只能依赖编译器在编译期判断对错,而boost::is_same本身就是这样子的,因为它就是一个经典的模板类型匹配的metafunction。
  9. metaprogramming究竟有多么难呢?我个人以为它的难度绝对可以抵挡住任何号称有多少多少年编程经验的高手的挑战。我个人的观点是它并没有什么现成的体系工具来发展,因为对于c++编译器来说,我觉得压根儿没有设计来作这个工作,因为类型的计算操作操弄本来是编译器的工作,那么程序员把一部分编译器在编译期的工作提前到代码阶段来实现这个难度是可想而知的了。当然,实现的手段在我看来本质就是依赖模板类的模板参数替换,回过头来理解SFINAE才能明白这个过程的可能性有多少,很多时候我们是借助于操纵编译器这种无声的替换失败来将结果导向我们预定的轨道。
  10. 这里是另一个学习的例子,就是怎样一步一步来探索mpl::apply,我想让他来用在mpl::plus上参数是一个两元的mpl::vector,当然应该可以有更多的参数,不过第一步就是这样子的。回过头来才看清楚unpack_args是一个多么贴切的名字,它就是把一个seq的参数和functor绑定成一个unary_function。 首先我认为把一个长的类型用typedef是不二的法则 那么我们就可以在这个基础上证实apply就是把参数应用到metafunction上,他当然必须是一个functor才能被apply,这就是为什么我们必须使用lambda的placeholders。 这里我顺便证实了一个我必须牢牢记住的事实,就是说mpl::plus的返回值是mpl::integral_c,这个如果不记住我会吃亏的

六月十六日等待变化等待机会

  1. 所以通过观察mpl::list的mpl:begin你能够了解到什么是iterator的类型,然后你才能真正体会到mpl::deref的意义。 比如这个例子 我做了一些小的实验 这里证实了我之前的判断就是所谓的iterator里是包含了seq的长度元素信息的,也就是说增减任何元素都改变了iterator的类型导致不兼容。注意这个mpl::l_iter就是这个iterator的,它并不需要包含除了mpl/list.hpp的额外的头文件。这个就是证明iterator的类型的改变
  2. 那么mpl::vectormpl::list的区别是什么呢?比如同样mpl::pop_back之后他们的类型是否一样呢? 我模仿之前的mpl::list做了个测试 但是区别在于mpl::list经过pop_front之后那个元素确实是被删除了,可是mpl::vector却不是的,因为vector的内存分配特点实现者不愿意重新分配内存,于是内部使用了一个mask来展现给你,这个是很有意思的
  3. 你会像我一样的对于mpl::set感到震惊吗?因为它的mpl::begin居然是我认为的最后一个,难道它的比较是greater吗?如果查看代码就明白mpl::set_c是应用类似递归的定义来实现的,所以,它首先把最末的一个元素压栈,所以第一个元素是传入数组最后一个元素 这里的技巧是可以用mpl::at来判断mpl::set是否有某个元素 但是正规的做法还是使用has_key,这个要比at来的直接多了
  4. 关于mpl::count你有什么想法呢?我因为首先是在mpl::set看到它还以为它是一个专门为某个容器实现的方法,实际上它是mpl::count_if的包装。而它的返回值更加令人吃惊,因为它是unsigned long int。 所以你看到count返回的类型是比较不寻常的,所以,普通人都只关心它的value,而不要纠结于他的具体类型啊。
  5. 不要使用这个没有用的key_type因为它根本就是一个identity模式完全无用,不会判断元素是否存在。我可能误解了它的用意,它也许更主要是对付比较复杂的pair的吧?同样的value_type也是基于pair才有意义吧?
  6. 顺便说一下,我一直在迷惑_c的含义,原来他们都是integral_c的特殊化,称之为Integral Sequence Wrapper
  7. 对于mpl::range_c感觉很强大,它本质是一个random access sequence我对于它的iterator的类型感兴趣
  8. 你看看mpl::range_c 的定义并没有对于其中的type有什么限制,那么你能用字符串或者浮点数,或者一个复杂的类吗?但是这个错误“is not a valid type for a template non-type parameter”限制了模板参数的类型。这个Template non-type arguments是一个非常重要的概念,我过了半个月再次遇到才开始理解,而要理解它必须先了解std::integral_constant以及constexpr等等的概念!一环扣一环啊。 c++的标准是这样子的:

    THE SIMPLE ANSWER

    The standard doesn't allow floating points as non-type template-arguments, which can be read about in the following section of the C++11 standard;

    14.3.2/1      Template non-type arguments      [temp.arg.nontype]

    A template-argument for a non-type, non-template template-parameter shall be one of:

    • for a non-type template-parameter of integral or enumeration type, a converted constant expression (5.19) of the type of the template-parameter;

    • the name of a non-type template-parameter; or

    • a constant expression (5.19) that designates the address of an object with static storage duration and external or internal linkage or a function with external or internal linkage, including function templates and function template-ids but excluding non-static class members, expressed (ignoring parentheses) as & id-expression, except that the & may be omitted if the name refers to a function or array and shall be omitted if the corresponding template-parameter is a reference; or

    • a constant expression that evaluates to a null pointer value (4.10); or

    • a constant expression that evaluates to a null member pointer value (4.11); or

    • a pointer to member expressed as described in 5.3.1.

    答案当然是否定的,凭直觉也知道,这个参数是为了能够enumerate的一个区间,浮点数可以吗?两个复杂类的对象有什么意义能够enumerate呢?事实上编译器首先要求T是一个constexpr

    A constexpr variable must satisfy the following requirements:

    • it must have constant destruction, i.e. either:
    • it is not of class type nor (possibly multi-dimensional) array thereof, or
    • it is of class type or (possibly multi-dimensional) array thereof, that class type has a constexpr destructor, and for a hypothetical expression e whose only effect is to destroy the object, e would be a core constant expression if the lifetime of the object and its non-mutable subobjects (but not its mutable subobjects) were considered to start within e.
    (since C++20)
  9. 有趣的是在我看什么是LiteralType的时候,我突然意识到我不知道怎么才能生成一个constructor带模板参数的类的对象了。 比如这个是我拷贝来的例子稍微改动了一下,它有一个constrcutor带有模板参数,你是不能使用conststr<N>因为编译器会说他不是一个模板类。当我头脑清醒了一点我才想起来我已经遇到过这个问题了。这个在constructor使用模板的做法是典型的用来克服数组长度不能被当作类型传递给函数参数的缺陷的,所以人们才使用模板来传递数组长度,我之前已经研究过这个问题,结果又忘了,在实践中得到的亲身经验才能灵活运用,别人告诉的基本上不懂得举一反三。 那么你只能让编译器去deduce这个模板参数 这个结果还是值的注意的,因为编译器正确的设定了start的数组长度7包含了结尾的null字符。
  10. 有一阵子我神情恍惚忘了我怎么去重载operator<<我居然想着我应该是重载我的定义类的成员函数,这个简直太丢人了,时间长了我居然忘了,成员函数的话我的类就成了LHS,那么我的目的是用cout输出,难道要把它放在RHS吗?理论上是可以,可是这个太诡异了吧?正常的都是这个形式,那么唯一的选择就是声明一个friend的全局的operator的重载而已。
    Expression As member function As non-member function Example
    a@b (a).operator@ (b) operator@ (a, b) std::cout << 42 calls std::cout.operator<<(42)
    问题是如果我同时定义了全局和成员函数,我调用的时候小心的不让起歧义,编译器是否允许呢?
  11. 在电影编剧的预言中美国的未来就是这部电影《Alita: Battle Angel (2019)》所描述的世界。美国在失去了制造能力之后,在世界大战中战败,被包围在瘟疫污染的大陆上苟延残喘,仅存的人口分散在少数几个城市里,人们生活在弱肉强食的无政府社会,没有警察,没有政府,社会被大制造工厂所把持,而这个制造工厂不属于美国,而是存在于一个高高在上的天空之城,他们生活在高科技的天国,每天把自己的生产的过程中产生的工业垃圾倾泻到美国,而美国人只能依靠这些工业垃圾东拼西凑组装一些低级的半人半机械的垃圾。而所谓的美国人民都是一些没有文化与科技知识的科技原始人,每天沉迷于杀戮与游戏中。

六月十七日等待变化等待机会

  1. 感觉boost是一个纯粹头文件的库,那么是否可以利用一下precompiled header呢?想想看我需要include多少boost的头文件啊?当然对于我的实验来说几乎没有多少增益,因为我只有一个代码文件所有的boost的include的头文件也许就只被包含一次如果他们都有ifdef的guard的话,那么其中的增益就是不用再次读取所有需要的头文件而已。那么对于大的工程拥有众多的代码文件的话这个好处就大多了。那么怎么作呢? 首先,阅读gcc的官网的指导是必要的。我印象我其实以前也读过,但是都是似是而非的,不甚了了。其实就是因为gcc不像vc需要你专门使用一个stdafx.cpp这样子的代码文件专门预编译,gcc只要你有一个与你的头文件名相同的并加上后缀名.gch的话,编译器就很聪明的使用了,可是也许太聪明了反而让人不敢明确是否我的项目已经在运用了预编译的功能了,这个真是一种讽刺,你做的太好了,用户反而狐疑起来了。这个的确是一个难题。但是gcc文档说的太学术,像我这类猿猴级别的程序员依然难以真正领会,所以,这样子的实际指示还是需要的。总结起来是这样子的:
    • 首先,gcc不懂得要怎么帮你创建预编译文件,这一点就看出还是微软的办法好,因为程序员反正是要编译器帮他创建.gch文件的,索性还不如把这个作为一个必须的步骤来要求。也就是说,你必须手动作这个编译 这里的编译开关是我遵照gcc文档强调的通过观察我的eclipse里的现在设置的编译输出拷贝来的,这其中的道理其实很简单,因为有些很关键的开关比如架构-m,异常,message-length等等是非常要命的,如果你的源文件和头文件不一致那就是一个灾难。我把输出放在了原本的头文件的同一个目录下,这个是里所当让的,我原本就是需要这个头文件,如果它找不到编译肯定不行,如果编译器能找到当然就有可能使用预编译了。
    • 其次,是怎么知道预编译有在实施。这个问题才是大家关心的,因为这个也就是我之前强调的微软的做法更优越的地方,其实加速很有限,也许就感觉不出来怎么知道成功了?所以,这个里要知道这个开关-H只有它才能打印出gcc编译头文件的状态,当你看到了输出里有你就安心了。顺便说一下,我原来以为verbose可以看到这个,实际上不行,就是说-v并没有输出这个结果。
    • 当然对我来说验证这一点并不需要一定使用-H,因为当我添加了新的头文件之后编译并不成功就能证明这一点了。这个时候,我感觉预编译实在是好烦的一件事。
  2. 很多时候你的收获来自于意想不到的例子里,比如这里的mpl::deque,你可能认为这是个比mpl::vector还要简单的结构不值得花时间,可是它的例子里提到的mpl::at_c让人眼前豁然一亮。 我对于我的记忆力始终都是表示怀疑的,但是我还是认为我从来没有接触过mpl::at_c。查看代码发现它就是针对了mpl::advance只接受mpl::long_而不是mpl::int_设计的,它等价于 阅读它的源代码就知道它不过是advance+deref的包装。 那么你有兴趣知道它的iterator的类型是什么吗?说出来吓人一跳的
  3. 这个是我做梦也没有想到的,居然mpl里也有string,看来所有的数据结构都可以在类型的世界里找到。这个说明了什么?我已经脑子转不过弯了,是否每一个字符串都是一个独立的类型呢?当初发明类型就是为了化繁为简,跳出只见树木不见森林的短视而上升的一种抽象,那么现在我们反其道而谓之每个个体都是他自己的类型,那么还要类型做什么?这个抱怨当然是可以发一下,我自然明白至尊武功的修炼就是这样子的过程。当初风清扬教授令狐冲独孤九剑剑法的时候,开始的时候就是习练无比繁复的剑法,然后让他努力忘记所有的剑法,等到他忘的一干二净的时候反而看到任何简简单单的出手立刻联想到世界上无比复杂的招数,所谓无招胜有招并非什么招数都不会的初学者,而是通晓天下所有招数后不羁绊于任何门派的招数的融会贯通。等到他先见识了世界第一复杂的现象,然后再去归纳统一。然后跳出了藩篱之后再次重入就是一个从具体到抽象,然后再从抽象到具体的升华过程。 你现在明白为什么作者初始化的时候使用了这么奇怪的办法吗?实际上它也暴露了一个初始化的问题,就是平常你会用这种奇怪的'hell'的形式吗?当然我知道我和作者一样的懒得打字才这样子投机,可是编译器担心你传递的是字符串忘记了它应该是"" 如果你去看mpl::string的源代码就会如我一样的困惑,在mpl的世界里没有constructor的概念,那么在模板类的初始化对应于constructor的是什么呢?对于这个复杂的宏我不相信有多少初学者愿意花时间去研究因为他太复杂了,可是当你看到这个令人惊奇的结果你要怎么解释呢? 仔细检查就会发现编译器是有警告你: 说明了什么呢?现在回过头去想mpl::string那个复杂的宏作为模板参数就会明白,这个递归定义的宏限制了每次只能作四次递归,所以,作者只能把参数拆分成四个一组才行。 打印的结果不过是再证实一下我添加的结果而已:hello nick world!
  4. 顺便说一下我的头文件的预编译居然花了16秒,所以,我实行预编译boost头文件甚为必要因为这个头文件的清单每天都在增长!!!我现在每次编译的时间只有两秒多等于节约了十几秒啊!g++ -O0 -g3 -Wall -fmessage-length=0 -v precompiled.h -o precompiled.h.gch

六月十八日等待变化等待机会

  1. 关于mpl::c_str它并不局限于mpl::string,因为它核心是把一个seq变成一个array,这个是很强大的功能,因为一个整数数组有时候更加的好操作。
  2. 昨天我本来想使用mpl::find来返回iterator作为mpl::insert_range一个mpl::string,现在总算扫清了之前的障碍。想法很简单但是当你有很多未知的疑惑的时候,你的焦点就常常偏移以至于在细枝末节上卡壳,所以,魔鬼在细节。
  3. 怎么理解mpl::empty_sequence呢?实际上这个是补充你没有办法直接定义空的range/vector/list等等的缺陷,因为什么类型是空类型呢?注意void可不能算是类型,这个我感觉也是数学上的类似的难题,比如说空集合是一个实实在在的集合,你没有它很多判断都没有办法作,就好像天天都在拿着空字符串来和所有的字符串比较,那么你能实实在在的产生一个空字符串吗?它是看不见摸不着地,一个长度为0的字符串真的存在吗?或者说它无所不在。但是从指针的角度看void指针只是一个类型任意的指针,那么真正不存在的类型是什么呢?这个简直就是现代物理上为了解决宇宙总质量的平衡而杜撰出来的所谓的“暗物质”一样。所以,这个empty_sequence是一个特别制作,它的类型就是它本身empty_sequence。而它的begin/end的实现是有意思的,首先看看boost::begin是使用eval_if来看看这个sequence自己是否有定义,有就用它的。所以,empty_sequence自己就定义了一个空空如也的begin,注意它不定义自己的type,让你直接就会在企图访问begin/end的时候想要mpl::deref的时候出错,因为mpl::deref是要使用iterator的type的。这个做法避免了错误的访问,虽然你依然可以使用begin/end,他们返回的是真正的类型,因为type没有定义。而且,对于很多sequence的begin/end的类型是不一样的,因为是半开区间,end当然是不同的,可是对于empty_sequence则要求他们是一样的。 我觉得我的这个例子更加能说明问题 也就是说这个独一无二的类型mpl::empty_sequence::begin是一个不同于任何的类型。你使用mpl::deref对他就会失败,因为 会失败报出错误
  4. 我觉得我的例子要比作者的好一些,就是能够展现mpl::transform_view的强大。其实,如果你明白transform怎么运作的,就知道普通的mpl::transform是需要你有可能重新分配一个目的seq,而这个tranform_view是帮你分配了一个临时的seq。 给你一组类型,然后你想给他们都加上指针,你还可以对于这个view再加一次指针成为指针的指针。同时,这里我才亲自明白了mpl::at_c是一个大大的方便,比mpl::at需要一个long类型不同,前者是把用户的数字内部转为类型mpl::long_。
  5. 究竟mpl::_和mpl::_1有区别吗?这里的mpl::_是所谓的non-specific,是有特殊意义的
    [Note: Every nth appearance of the unnamed placeholder in the bind<f,a1,...an> specialization is replaced with the corresponding numbered placeholder _nend note]
  6. 我这个是自己的实验,中间打岔都忘了为什么了。原本是实验transform_view,结果意外的在一个细枝旁门上卡了一下子,就是mpl::sizeof_是个什么东西?它是一个怎样的metafunction?我一开始都找不到它的头文件,后来才明白是boost/mpl/sizeof.hpp它实际上是所以,它是一个metafunction。变成了一个functor一样的metafunction。我觉得我的名字都是瞎叫的 这个例子是把作者的分解了一下
  7. 对于mpl::applympl::apply_wrap是什么意思,他们的区别是什么呢?我看了半天才明白,他们都是帮你绑定模板参数,也就是帮你做了一个typedef来返回你需要的type,而区别就是后者针对的是metafunction class,这个是高了一个维度的类型,它的类型不是直接在metafunction里实现的,而是内部有一个metafunction命名为apply的实现模板类帮你实现,所以他的typedef是针对内部的这个apply得到的。其实写起来真是不清不楚一看就明白的。很简单的。 我感觉对于invocation的三剑客是最难的核心要多花时间才行。 其实说是很简单,还是花了我不少的时间,我都饿晕了才写完。首先我们来实现一个最简单的加法,第一个int_plus是为了metafunction的 因此我们可以很轻松的使用apply因为它的实现直接就定义了type,注意是因为内部的mpl::int_有自己的type实现。 现在我们针对apply_wrap实现一个metafunction class的版本。注意,在apply里面的typedef必须有typename跟着,编译器说我的这个type是dependent type。如果没有编译器提示我是永远也不明白的。 注意它不是直接就实现了type,而是内部的apply这个内部的metafunction帮你实现的,所以必须要使用apply_wrapN来绑定 看董懂了吗?我都快饿死了。
  8. 今天又是一个充满了挑战的充实的一天,我不禁想起了这首老歌《我的未来不是梦》,难道我的未来真的不是梦吗?
    你是不是像我在太阳下低头
    流着汗水默默辛苦地工作
    你是不是像我就算受了冷漠
    也不放弃自己想要的生活
    你是不是像我整天忙着追求
    追求一种意想不到的温柔
    你是不是像我曾经茫然失措
    一次一次徘徊在十字街头
    因为我不在乎别人怎么说
    我从来没有忘记我
    对自己的承诺
    对爱的执着
    我知道我的未来不是梦
    我认真地过每一分钟
    我的未来不是梦
    我的心跟着希望在动
    我的未来不是梦
    我认真地过每一分钟
    我的未来不是梦
    我的心跟着希望在动
    跟着希望在动
    
  9. 你是否像我一样对于我看到的递归实现不满意,比如说我完全可以实现一个不定长模板参数的累加metafunction。我参考了一下variadic模板参数现在我的int_plus可以实现不定长参数了,这个是我今天最满意的了。 第一步是声明一个空架子的不定长参数的空结构。 > 第二步是真的最终实现的方式就是两个参数的最基本的加法。(准确的我应该再考虑一个只有一个参数的,不过这个我懒得做了,因为没有什么实际的意义) 第三步是最重要的,因为递归就是在这里发生的。这个是所谓的template specialization吧? 这个可以工作吗?这个测试程序检验了结果

六月十九日等待变化等待机会

  1. 早上起来想了解一下mpl::empty_sequence到底是不是真的是sequence,因为可以用mpl::is_sequence,当然答案是可以预期的,否则作者就是在浪费大家的时间,那么sequence在这些大师眼里到底是什么呢?看mpl::is_sequence的实现就是这个类型必须要有两样,一个是tag,一个是begin不能是mpl::void_ 看到这个就明白了
  2. 这个现在就是在打扫战场,因为我之前有一个大的扫描,可是还是遗漏了太多了,因为mpl实在是台大了,我觉得它的规模不亚于stl的大部分,想想看几乎所有的算法都可以应用在类型上,而且比普通stl的容器还有扩展,这个就足够说明它有多少了。这个iterator_range为啥这么耳熟,我总感觉我在哪里见过,似乎是在boost::range里,但是两者当然是不同的,正像当初我以为boost::bind和lambda::bind是一回事结果吃了大亏一样,即便你把stl的algorithm原样照搬到类型里,它的实现也是高了一个数量级的困难。比如,一个简单的类型的判断都是困难的,判断一个类型是否支持“begin”就是非常的复杂,基本上就是经典的SFINAE的基本应用,这个就是那个著名的01_size这个实在是胡言乱语,因为它是O(1) size的算法,我肯定是眼花了。不过我所说的大致的概念不错吧?测试的经典,一个yes_tag是一个size为2的char[2],而最最准确检验类型的办法是使用函数参数,只有编译器匹配参数成功才有返回值,因此同坐sizeof(func(arg))这样子的做法能够知道返回的是yes_tag还是no_tag(它是一个char[1]),当然这样的描述是及其不准确的,因为这一切都是在编译期完成的动作哪里有什么函数的调用,不过是函数参数的匹配,而不同参数对应了不同的返回值,注意sizeof()测量的是函数的返回值的大小,这就是著名的0-1测试或者yes-no-tag测试,这个几乎是唯一的测试数据类型的办法。 这个标准的例子说明了range_c和iterator_range可以组成等价的“数组”,可是他们的类型确实天差地别,注意mpl_equal大致和std::equal类似是比较一个个的元素,至于怎么访问元素是无关的。那么他们的类型是什么呢? iterator_range的类型是 而range_c的类型是

六月二十日等待变化等待机会

  1. 俗务缠身,心绪如麻。这个是我看到的大的图画,有助于跳出树木来看森林。 我决定先摘取low hanging fruit尽快取得一个大的milestone。
  2. 一定要小心precompiled header的开关,必须和我的工程的编译开关完全一致才行。g++ -O0 -g3 -Wall -fmessage-length=0 -v precompiled.h -o precompiled.h.gch
  3. 但是所有的一切都是紧密相连的,我试图了解一个简单的数据类型就牵扯出了一大堆,每个都要补课,fold是非常的让人难以理解,于是我要去理解O1_size,这个还是很容易的吧,我一开始误会它,其实它就是一个简单的获取size的。然后我遇到了lambda,可是它是包含了quote,和protect,任何一个我都感到陌生。
  4. 我必须避免战线拉的太长了,首先我要把这个fold放一下,因为我完全无法理解,看上去结果完全就是一个count_if 但是它绝对不是简单的count_if,我现在还理解不了。
  5. 我对于mpl::protect的理解就是我之前在学习lambda的时候接触的protect,那个时候,好像也是针对lambda::bind的不把macro展开一样的意思所以,我觉得原理都是一样的吧? 这个例子相当的复杂,需要以后反复的深入理解,我现在还没有能力 首先这个模板或者metafunction class因为它是使用内部的apply来实现的。需要熟悉这个做法:
    protect is an identity wrapper for a Metafunction Class that prevents its argument from being recognized as a bind expression.
    深刻理解是需要认识到通常我们是把这个声明一个类型的 可是怎么理解它是Identity呢?以下这个就是成立的啊!所以怎么才能把protected的取出来呢? 所以,这个就解释了protect是什么东西了!它就是一个类似于alias的东西,它把它所包含的类型重命名(其实是继承),那么它保持了所有的包含的类型的功能。 这个时候我定义了一个通俗的op来证明所谓的protect和它是等效的 然后我们看看r2和op是不是在apply_wrap2来看是等效的
  6. 现在我看mpl::quote就容易理解多了,它的名字我现在很容易接受,因为就是把一个metafunction变成metafunction class,我在吹牛之后战战兢兢的写下了一个我自己的看不懂的例子,说老实话我这个纯粹是自己想出来的,绝对不是仿照什么作者的例子,说明我真的深切的理解了我背诵的定义。这个是我今天最值的自豪的一个例子。 首先我定义个一个类型组。 然后我把原本一个metafunction包装为metafunction class 因为它是metafunction class我需要使用apply_wrap才能把它变回一个充当functor的metafunction 那么我们得出的结果其实是有办法转为标准类型的,不过我还没有学到。
  7. mpl::lambda可能是最核心也是很复杂的一个东西,因为我现在脑子里对于bind/quote/apply/lambda就是一团浆糊。这个作者的例子我连编译都有问题。 这里是我的debug的一个结果 说明了什么呢?lambda就是创建了一个functor绑定了一个固定参数,我想证明这一点 这个就是证明了,lambda就是类似于定义了一个functor,可以使用apply_wrap来绑定的。 这里是原来作者的例子,我一开始有编译的问题,后来发现似乎apply的参数必须是mpl::int_类型而不能是整数数字。

六月二十一日等待变化等待机会

  1. 早上起来本来想迅速的写一个最简单的验证lambda,结果没想到费了九牛二虎之力才完成,发现我还是对于最基本的东西有误解。所谓的lambda就是原来的function,而其他的bind的对应的是apply,而quote是没有什么可以相对应的。
    • 首先定义个我们的lambda
    • 这里是我们要处理的数组 注意它的每个元素不是而是这就是为什么上面的lambda的参数的类型是这个。
    • 现在我们用filter_view得到我们的view,注意这里的lamba是可以直接当作functor用的。
    • 注意这里的view不是一个vector的类型,而是一个lazy的view,似乎是计算出来的。我们只能用mpl::equal来比较其中的元素
  2. 我从来没有认真的看过placeholder的实现。现在看了才明白placeholder就是一个内部实现了apply的metafunction class,而它的apply方法就是返回第几个参数。所以,它一定是使用apply_wrap的,因为是metafunction class。 这个例子非常好,因为通常你总是认为apply是apply在functor身上,那么能用在placeholder上吗?因为placeholder就是一个metafunction class,所以它是可以的,返回的type就是第几个参数。
  3. 对于mpl::lambdampl::bind以及mpl::quotempl::apply这几个关系如何呢?我一直脑子是一团浆糊,今天终于有了一个比较。人类的认识始终是在比较中实现的,没有差别就没有认识。普罗大众从来不明白什么是什么,因为如果你不教给它什么不是什么,它是难以理解什么是什么的。
    1. 首先我选择一个最最简单的metafunction class:mpl::always,它的特点就是它的apply方法是类型本身。所以,这个就是使用apply的地方。注意,apply内部使用apply_wrap,而它是直接调用传入的functor的apply方法。
    2. 现在我首先使用lambda得到一个functor,这个做法注意我是直接尊重always的模板参数,只不过因为我使用了placeholder,lambda注意到了把它包装成了functor。
    3. 然后我们使用bind也作一个functor,可是,注意,我需要使用quote,这个我自己也还没有想明白。
    4. 现在我们知道这两个functor是等价的
    5. 你想知道这两个functor的类型是什么吗?对了,就是之前的always绑定了模板参数。

六月二十二日等待变化等待机会

  1. 我再一次领教了Iter_fold的厉害,我一个早上都在苦苦挣扎,这是一种在黑暗中看到了一扇紧闭的窗口透出的细细的光,你知道它能够给你开阔一个新天地,可是你死活就是打不开它。因为我已经自认为明白了如何使用递归作循环,可是始终就是卡在哪里出错。而且我想要证明就是iter_fold里的两种实现方式似乎不是必须的,除非是有的编译器不支持这种多重递归?我看到的gcc在抱怨900重递归就报错了,我印象中微软似乎更加的脆弱。但是在类型的编译里超过900的一个数组应该是不大可能的了。总之我前进不了。我需要换换脑子。
  2. 我曾经反复在问自己这个究竟能做什么?我现在依然很模糊。似乎稀里糊涂学习一个看上去非常高大上的武林神功,却看不出有什么用途。模模糊糊的总想起《三体》的名言:
    1. 你们是虫子!
    2. 消灭你与你何干?
  3. 这个是非常的艰苦的debug,因为对于metafunction的递归几乎没有任何的办法来debug,我只能把一个成功运行的例子肩并肩的摆在一起来看到底是怎么回事。起因是这个iter_fold的寻找最大值的例子。我的想法是如果单单完成这个功能可以简化代码,而这个也是一个经典的怎么实现递归循环的好的例子。可是当我高高兴兴完成了这个小小的改进之后,我却很失望,因为它的功能非常的有限。比如我完全无法实现adjacent_find/filter之类的功能,或者说很难,因为它是一个完全递归的函数,并不会在第一时间返回,也就是说我连存放返回值的位置都是紧密耦合的在我的回调函数里。总之,我觉得它的作用很有限。只是帮助我理解了如何使用递归实现循环。当然,我主要是对于代码中分成四个一组作递归是否有什么优越性感到困惑,本意是想证明除非这个是要突破递归深度900的权宜之计?增加到数组长度3600?
    1. 首先我们声明一个空架子以便可以作speicialization来递归。注意这里的int N作为参数至关重要因为我们要递归,如果没有这个差别的话,编译器会抱怨你没有specialization。
    2. 注意这里是我们的递归“函数”,其实是一个模板类。注意这里我们才定义了state,这个是我用辛辛苦苦的一个上午的debug换回来的,因为我们的返回值就在这个state里,可是它不是递归的结束,我之前随手在这里做了typedef结果返回值就是始终不对我无法解释递归定义的压栈和出栈的顺序在编译器里是怎么实现的。总之不要在这里定义!
    3. 递归总要有一个结束状态吧,这里就是结束条件,否则递归就是无限的了。请务必在这里定义返回值state!
    4. 让我们看看测试程序及其结果吧
  4. 但是很多的算法也许都是基于这个fold这类方法实现的,比如,,所以,我才发现了代码里使用iter_folde_if,不过仔细一看它是一个内部函数,所以是实现细节,其实也对,我也就是对于fold/iter_fold只有一个state感到受限制,只要多出一个存储变量我就可以实现adjacent_filter拉,而所有的遍历的算法可能内部都是基于fold这种递归来实现的。比如我仿照例子实现一个find_if这类的算法。(不过看源代码它使用这个内部的iter_fold_if要容易的多了吧?)
  5. 很多算法可能你单单看懂例子就需要九牛二虎之力,至少这个简单的pair就是这样子的,我凝视了很久才明白这段代码是在作两件事,第一个是在count数组个数,第二个在count正数的个数,不过我看完代码再看文档发现我的英文阅读能力还不如我阅读程序的能力。
  6. 我一直不理解为什么没有generate_n这类算法,于是我自己实现了一个简单的,不过因为我不知道怎么才实现input iterator,于是这个实现只能假设sequence支持push_back了。
    1. 首先声明一个空架子
    2. 然后就是递归定义
    3. 递归总要有结束的条件啊
    4. 这个就是我的测试

六月二十三日等待变化等待机会

  1. 我对于lambda的理解还是不够,实际使用中总是把它和bind弄混淆,这个就是我想要实现的一个back_inserter的东西,但是要怎么用还是没有想清楚。
  2. 对于我之所以无法实现back_inserter这种功能,我现在发现是因为push_back返回的是所谓的v_item,就是vector里的一个元素,我怎么获得容器的引用呢?我目前只能获得它的base的类型是mpl::vector 这个例子其实不明显,我想要说明的是base是push_back之前的,所以,你得不到push_back后的类型
  3. 我感觉我现在比一个月前有很大的进步,就是现在看invocation的代码比较容易理解了。比如现在看这个arg就明白它不过就是名如其人的placeholder是一个指示参数位置的typedef而已。
  4. 现在回过头来读这个unnamed placeholder也明白了,它纯粹是依靠它的位置而不依靠下标,这个有时候是省事的。
  5. 当然现在阅读lambda文档似乎也明白了一点,它更主要的是把placeholder使用quote包装生成metafunction class,除此之外应该是和bind等价的吧?
  6. 我越来越不知道应该怎么使用bind了,因为似乎我必须要使用lambda。比如这个练习就是说lambda是针对placeholder的functor,而bind则是绑定了实参 等效的bind方式
  7. 终于大功告成完成了mpl的学习,那么现在就是一个大问题,这个高深的工具做什么用呢?否则就是屠龙术了。在stackoverflow的帖子里说fusion是一个很好的应用,据说spirit/phoenix是fusion的前身。

六月二十四日等待变化等待机会

  1. 为什么需要fusion或者说什么是fusion STL containers work on values. MPL containers work on types. Fusion containers work on both types and values.
  2. boost的简介中fusion被定义为基于tuple的算法Library for working with tuples, including various containers, algorithms, etc 所以,很明显的我要重新温习一下tuple,好在刚刚那些记忆还在,而且它是一个很具体的很小的库。
  3. 这个是一个典型的我被带偏的实例,本来是无意中发现的,结果引出了一串的问题和疑惑和证实。这个是题外话,我突然发现我不怎么知道怎样定义一个数组的引用char(&)[10]这个在我眼里怎么看都不像,有一度我还恍惚它是函数类型,当然这个是恍惚。所以,我只好用笨办法来证明一下它的size。 随后我发现我没有办法使用mpl::begin来操纵一个数组,当然这个是无厘头,因为mpl对付的是类型,我们在std里是有begin对付数组的,这个也是c++11的新代码,那么它的类型是什么呢?我一度也偷懒使用auto,但是只要简单的查看代码就明白对于数组返回的就是数据类型的指针。当然对于数组的begin前提是要能够让编译器知道它的size,数组是不成问题的。因为我们能够使用sizeof获得它的size就说明编译器是知道的。 结果如下
  4. 这个是tuple的入门,我似乎上一次没有真正领悟,就是说make_tuple在推理类型的时候最后只会使用POD,不带任何的const以及reference的等等修饰符,这个时候只能使用boost::ref/cref来包装,我模糊觉得这个就是我之前百思不得要领的boost::forward_adaptor之类的???
  5. 我再一次的被带偏,完全的游荡在boost的海洋里不知所终,似乎以前也注意到了result_of,也曾经的为一个functor有多个重载的opertor()的返回值而烦恼,这个时候发现了这个result_of真的是一款利器,配合BOOST_MPL_ASSERT的宏的确是一个十分强大的探测类型的神器!直到这个时候我才意识到mpl文档里特地把宏专门拿出来作为它的贡献之一,的确,如果没有这个工具,简直不知道要怎样debug类型! 这个我抄来的例子说明了我们如何来对付functor的重载后的返回值,如果没有mpl的assert你要怎么办?我曾经尝试过std::typeid但是它完全无法阅读。
  6. 我再一次深深的感受到,如果你对于mpl或者更加底层的type_traits之类的类型操作库不熟悉的话,想要真正看懂大部分的boost的代码是有很大问题的。也许这就是mpl的应用?我的体会是模板不再仅仅局限于实现generic programming的工具,而是成为程序员利用编译器操纵类型的工具。毕竟类型是一个看得见摸不着的东西。至今连打印类型的人类可读的方式我都没欧看到,也许一些深入readelf之类的库有这个功能吧?但是这个又是一个编译器紧密相关的不能成为通用跨平台的库函数吧?唯有模板是一个通用支持的方式来“探索”类型。
  7. 这个是一个开脑壳的例子,它充分说明了fusion的强大与应用场景,比如你灵活的使用tuple处理各种各样的类型的数据而不是像stl那样的呆板,因为是多种类型混杂的sequence,你不可避免的会需要处理判断类型的相关,这个时候怎么才能把mpl的类型算法函数糅合进来呢?其中的lambda不可能是boost::lambda,也不能仅仅依靠mpl::lambda,这个就是fusion的名字所在,它融合了type and value的算法。 我们首先有一个打印的functor 然后我们要只是打印指针类型,注意,filter_if是一个fusion的算法,它要怎么样把pred传递呢?对了,它是模板函数,使用模板参数传递functor,这个地方就是它的神奇之处,它传递的是一个类型functor,但是它是作用于一个tuple的,所以,fusion内部是要对于每一个元素懂得到底是使用类型还是使用值?还是说fusion永远只是处理类型的functor,因为,如果不是的话,这个对于值的处理交给range的算法?目前我看不明白代码,我只能假定fusion的filter纯粹就是针对类型的处理,否则你应该使用更加简单的range的算法。
  8. 我对于我不懂的东西似乎都敢于大胆的批判,因为我觉得fusion在实现这个deduce是有缺陷的,因为,我模糊的记得mpl里面有着类似的更准确的实现,关于类型的识别是一个很困难的事情,因为你有可能有成千上万的typedef/#define的宏定义,被程序员绕几个弯子就会头晕的,所以,我以为这个简单的deduce是太小儿科了,为什么没有使用类型递归去真正的去除reference呢? 当然我对于这个deduce的用途并不是很明确,如果是简单的传递参数,那么似乎forward_adaptor试图解决类似的问题,我不确定是否如此,简单的去除const reference吗? 因为其他的类型都是identity而已 可是如果有程序员给你指针的引用呢?我觉得这个结果是不对的,照例说你可以理解char*是一个类型啊。 这个就是我的证明 不管怎么说如果不使用递归是不可能准确的解决这类问题的。我记得rmpl里好像有类似的remove_const_reference之类的实现吧?
  9. 对于fusion::pair我还是不大能够理解,就是它只是存储一个second,它的first仅仅是存储类型,难道它就是所有的fusion的特点,它是类型和值的混合?比如它的重载的操作符==是一个模板函数,首先是first的类型决定了只有同类型才有可能相等,其次,second的value也要相等才行。
  10. 对于下载所有的boost源代码的流程我一直不清楚,现在从一些适用于windows的脚本我琢磨可能是这样子吧?只因为我对于git还是不会用。我是这么下载boost的develop branch的。
    这个对于master分支也是一样的。

六月二十五日等待变化等待机会

  1. 真正的理解SFINAE是一个很难的过程,我其实并不是真正的理解boost::enable_if,听上去容易,可是真正的意义是什么呢? 假如我们对于一个函数的模板参数心存疑虑,以至于压根不想让用户使用危险的非法类型,那么我们可以在函数返回值上做文章,一个函数的最低要求是要有返回值类型,void是一个完全合法的类型,所以,我们需要让它最低限度必须是void,那么这样子的定义就是一个对于函数是否成立的试金石,比如我们需要这个模板参数必须是一个满足mpl::is_sequence条件的类型 注意这里我们使用了typename因为后面我们定义的type是一个依赖于模板参数的类型,而更关键的是这个boost::enable_if<bool>::type,因为enable_if如果省略了第二个默认参数的话它就是void,也就是说我们的bool如果是true,这个type就是void,否则就是没有定义,也就是说type未定义,这个不要误解为编译器根据SFINAE原则会放过你,不会的,因为它找不到办法,这个函数不成立!所以,你被抓住了,编译失败了。当然前提是你真的有违法的调用这个函数传递它一个非法的类型比如func("hello");
  2. 对于fusion的种种算法我只能慢慢的体会,真正的掌握还需要时间。比如这个for_each虽然简单,但是更加的使用,因为相比之下fold的应用似乎很难。 结合lambda的placeholder,我们可以节省了实现一个简单的increment的functor
  3. 对于fusion的算法我总算是认识到了一个粗浅的道理,就是说它的pred一定是一个接受类型的mpl的函数,也就是说它是使用tuple里的类型来调用的functor,所以,我想要使用任何关于值的函数都是行不通的,因为predicate的参数是以类型的形式出现的。这个是fusion的特点。 我曾经想尽办法想使用mpl::greater来做成一个lambda的回调函数,可是不行。 所以,这个是最好的范例。
  4. 我本来一直在想既然fusion::vector/list/map等等为什么没有类似push_back/front或者insert的方法,我想每个人都会像我一样想的。所以,我一直在想究竟它的存储结构是怎么样子的呢?有一阵子我甚至在想内部的存储一定是使用fusion::pair,这个是很顺理成章的,而且我也感觉所有的算法都是针对每一个元素的类型来调用回调函数,那么如果类型和值不是在一起的怎么办呢?但是查看fusion::make_vector的代码如此的简单,根本没有任何数据结构,它仅仅是推测参数的类型并且把参数的类型去除const reference,就完了。所以,这让我只能推断fusion的sequence根本就没有数据结构,它就是一个compile-time的声明,完全依赖于参数类型的“推理”的一个无runtime存储的“sequence”。

六月二十六日等待变化等待机会

  1. 这个可能是非常小的一点体会,就是我在查看transform的代码的时候,始终找不到任何循环或者递归的代码,那么transform要怎样遍历一个sequence呢?就是说它做好了所有的transform的准备工作,包括检验是不是sequence,是否每一个其中的类型可以convert的定义,但是就是没有任何的动作,后来gdb之后我才想起了文档里提过的fusion是一个lazy的做法,就是所有的都是只有当sequence实际使用的时候transform才起作用,谁要用谁负责循环的实现,这就是lazy,那么我看到的范例是调用sequence的operator==,它负责检验两个sequence的长度以及每个元素的可比性。这个是多么细小的细节,可是如果不知道的话就是想破天你可能还在迷惑中。明白了这个道理我感到很满意,今天哪怕我不看任何一行代码我都是可以满足的了!
  2. 所谓的view就是针对每个不同的view分别实现相应的最基本的方法,比如begin/end/at等等。

六月二十七日等待变化等待机会

  1. 果然我昨天什么都没有作。一个人暗示自己做什么就会得到什么。我其实对于fusion的期望值是太高了,我以为它是非常的有限的吧,就像作者说的它的目的就是使用tuple,可是当我想要真正的应用一个tuple的时候,我才发现你没有办法使用insert或者join之类的动态的增长这个tuple,因为它是compile-time的定义,如果你也不知道它有多长,那么运行期也没有办法。 比如我费尽心思想去写一个能够容纳任何类型的无长度限制的tuple,可是这个根本就不行,首先,类型你要怎么去判断?你要怎么利用cin的操作符>>自动判断类型?我想不出怎么使用模板来利用它。这是一个不对称的问题,因为cout<<是可以输出但是不代表你可以输入,何况输出也是不知道类型的。其次,你定义了vector里就只有一个boost::any元素结果它的长度始终就是1,不可能自己增长的,哪怕你使用fusion::join,可是无法使用一个变量来引用它。因为编译期和运行期是两个截然不同的概念。所以,我的想法完全没有意义。 即便我去定义一个可是你要怎么赋予它不同的类型呢?fusion是事先知道类型才声明的编译期的定义,而不是运行期的不可知的对象啊。所以,从这点来说fusion似乎完全没有什么实用价值。
  2. 我继续在boost的黑暗森林里摸索,看到一些新奇但是完全不是自己想像的一些好玩的东西,可是单单理解它的用途就需要多么大的努力啊。这里是一个metaparser我完全不理解它能够做什么,尽管它的说明是那么的清晰,因为我已经有了fusion的教训,很多你以为的和实际的相差甚远。不过,发现它提到了这个metashell的新奇的东西,让人简直是颠覆了认识,原来世界上还有这种神奇的东西?但愿它是我想像的解决metaprogramming debug的难题的东西!可是我完全不知道要怎么使用它。
  3. 下载metashell并安装: 这里可以下载源代码,以及编译指令
  4. boost的学习就是这样的,当你开始某一个方面的时候你发现它使用了另一些你不熟悉的库,结果你不断的被带偏了。在metaparser讲解debug的技巧的时候我才知道原来你可以使用type_index来作一些相应的工作。 结果就是这样子

六月二十八日等待变化等待机会

  1. 完全的陷入一种半迷茫的境地。因为我对于metaparse的目的及意义的不理解,spirit是一个runtime parser,那么所谓i的compile time parser究竟是一个什么概念呢?纯粹使用大量的宏来实现的吗?我已经开始感觉到了它的复杂因为我的eclipse崩溃了,我发现我在进入metaparse的领域后cpu内存的使用大量增加导致eclipse的parser直接崩溃了,到现在为止我的笔记本不仅彻底死机一次,而且eclipse的indexer常常无法完成而不反应。还第一次遇到eclipse要求我把所谓的parsing-based proposals(Task-Focused)这个东西关闭掉。很显然的对于类似这个BOOST_METAPARSE_STRING的这种实现来说,编译器都感到了吃力别说是我这个人脑了。
  2. 随着下载metashell以及编译的实践我觉得开始更加深入的理解这个metaparse了。不过我对于这个#include <metashell/formatter.hpp>不大理解,首先,我还找不到这个文件,编译metashell非常的慢,只好等编译完了再看看。其次,直接使用的binary里有可能有吗?很可能是我的namespace搞乱了metashell吧,总之,metaparse和mpl以及std都有vector,这里的namespace不能少。

六月二十九日等待变化等待机会

  1. 工欲善其事,必先利其器。完全是在摸索工具阶段,首先,metaparse导致eclipse完全无法工作,增大内存也无济于事,我怀疑是indexer的问题,也许是无限循环的吧,因为我眼看着内存窜到8G也不停。下载最新版eclipse也不行,看来只能暂时禁止metaparse,直接使用metashell,因为到现在为止我也不知道它究竟能够做些什么。
  2. 然后遇到很多从前就遇到的问题,我本来想改变Ubuntu的界面配置,只能使用gnome-conf,可是怎么把应用加到launcher上呢?我在/usr/share/applications/下的eclipse.desktop文件似乎不能被识别,也许需要什么编辑添加,但是我可以直接在~/.local/share/applications/下添加,不过有些unity-generated=true不能被gnome添加需要注释掉。
  3. 我的openssh-server默认居然是不允许password,需要修改/etc/ssh/sshd_config的PasswordAuthentication yes
  4. 然后ssh-server抱怨cipher的问题,说server的是aes128-cbc, 3des-cbc, blowfish-cbc, cast128-cbc, arcfour, aes192-cbc, aes256-cbc,在ssh -c 3des-cbc就可以了,或者是在~/.ssh/config添加Ciphers 3des-cbc这个需要在/etc/ssh/ssh_config里激活cipher的list
  5. 我决定必须转战到另一个领域里去,因为这个metaparse完全是一个泥潭,没有武器工具怎么战斗,虽然可以使用metashell可是意义是什么呢?我看了一下concept checking觉得它虽然很好,可是它是作为库的作者的工具,我作为使用者不需要现在去了解它。
  6. 我不是十分确定我的目标是hana,为此我很快的浏览了一些不太重要的库比如tti,static assert等等工具,因为他们都是一些具体的工具,彷佛是一把螺丝刀,我即使没有也可以使用别的万用工具来实现,或者不是十分必要。那么hana说了什么呢?
  7. 这一段文字及其的难以理解

    The purpose of Hana is to merge the 3rd and the 4th quadrants of computation. More specifically, Hana is a (long-winded) constructive proof that heterogeneous computations are strictly more powerful than type-level computations, and that we can therefore express any type-level computation by an equivalent heterogeneous computation.

    但是我以为它肯定是一个比fusion强大的东西。这里我第一次真正明白了原来c++里有四种不同的计算

七月一日等待变化等待机会

  1. 各种各样的纷扰。今天当我想给hana做一个快速的粗略评估看看它的工作量以及边界,因为战事越来越紧张,也许没有这样整段的时间来集中攻击这么一个坚固的堡垒,尤其是经过了fusion的教训我对于一个库的学习目的价值要更加的小心,有些是前进道路上的必须的节点当然要不惜代价学习,可是如果有些只是岔路或者选择是否要涉足需要权衡,毕竟资源与时间永远都不是无限的。最主要的是九攻不克而影响士气与决心是最直接的后果。在评估中,我“意外”的发现我对于这个integral_constant一无所知,这再次暴露了我对于c++11/14的重大不足。但是仔细想想看,这个integral_constant果真仅仅适用于常整数吗?我觉得它更像是一种模式,一种类型可以枚举并且有固定行为常用的个体,关键之处在于它的常用性吧?
  2. 首先,就是关于constexpr这个概念我始终是不清楚的,之前有摘录但是太复杂并没有真正的理解。这个就直接导致了我对于这个string literal是否是constexpr有着模糊的认识。这个在c++11以前是可以强制做到的吧,反正对于编译器的警告可以置若罔闻,但是现在要实际使用就遇到问题。只有这个形式是可以的:constexpr char my_str [] = "hello";但是我的问题是它不是一个const 这个是我的测试,我不知道是不是有意义 关键是这个结果让我迷惑不解因为HELLO居然不是一个常数!虽然我给他加了constexpr 还有const,可是它依旧是char [13],这个难道是boost::index_type的处理?
  3. 关于怎样把一个string literal做成一个std::integral_constant我费了九牛二虎之力才明白,其中的关节我觉得更像是一个编译器的bug,比如我声明的literal如果是local的,就是在函数内的就不行一定要是全局变量,这个似乎完全是没有道理的。查看我的笔记才意识到我已经遇到这个问题了,但是它太复杂了,我第一次压根没有看懂。现在对于这个TemplateNonTypeArgument的问题就理解深入多了。 这个样子的结果是,可是我为什么声明在local就不行?我再次阅读c++的标准感觉这个static storage duration可能是影响的关键,因为在我的脑海里我始终认为locally declared static实际上仅仅是scoped global variable,但是这个也许在编译器实现的普遍概念上不一定,总之在逻辑上local的就不是一个duration可以控制的吧?比如我在一个动态库的函数里这么声明那么这个函数如果永远不被调用呢?当然我的argument也可以说常数的计算不需要在运行期来决定,是编译期决定的。总之,我有一点点同意string literal是一个特殊的东西,它也许是需要特别的内存分配,所以,constexpr不应该是local的。
  4. 总之,我因为一个入门的例子里的一个常数1_c引发了如此多的结果,真不知道这样子的跑偏能够有多快的进度,读了user-defined literal,我简直就蒙了,我真的有学过c++吗?我以后简历里就不要提我学过c++11/14了,因为我只知道c++98,也许c++03都不知道,如果有这个东西的话。这个是我能找到的关于操作符的大全可是你能找到关于操作符""吗?找到了这个是c++11才有的
    operator "" suffix-identifier (5) (since C++11)
    这里我还是把这个操作符大全摘录下来吧
    Expression As member function As non-member function Example
    @a (a).operator@ ( ) operator@ (a) !std::cin calls std::cin.operator!()
    a@b (a).operator@ (b) operator@ (a, b) std::cout << 42 calls std::cout.operator<<(42)
    a=b (a).operator= (b) cannot be non-member Given std::string s;, s = "abc"; calls s.operator=("abc")
    a(b...) (a).operator()(b...) cannot be non-member Given std::random_device r;, auto n = r(); calls r.operator()()
    a[b] (a).operator[](b) cannot be non-member Given std::map<int, int> m;, m[1] = 2; calls m.operator[](1)
    a-> (a).operator-> ( ) cannot be non-member Given std::unique_ptr<S> p;, p->bar() calls p.operator->()
    a@ (a).operator@ (0) operator@ (a, 0) Given std::vector<int>::iterator i;, i++ calls i.operator++(0)

    in this table, @ is a placeholder representing all matching operators: all prefix operators in @a, all postfix operators other than -> in a@, all infix operators other than = in a@b

  5. 经过两个多月的学习,我需要重新领会大师关于什么是c++的演讲,我就是一个盲人,我正在摸象!想到这里我心中情不自禁的唱起了那首《毛主席的话儿记心上》

七月二日等待变化等待机会

  1. 这个又是一个振聋发聩的新东西:变量也可以是模板化了。 这个例子是摘抄的说明一个变量也可以是模板化的,这个新特点让人觉得一个变量可以作为一个结构来用。 我有一个想法,我应该定义一个宏让每一个变量都可以打印它的名字 比较有趣的是float和double的精度差别挺大的。而double和long double不知道有什么区别。

七月三日等待变化等待机会

  1. 完全被带偏了,我需要一个最最简单的应用来测试一个v4l2-based USB radio,所以,我从XawTV v3取出了最最小的代码来实验。我发现v4版本反而不工作,也许是这个dsbr100只支持v4l而不是v4l2吧,我也懒得探究,核心的问题我发现它的调谐效果不好,还不如我自己买的几块钱的mp3播放器自带的fm radio的效果。这个是我随后在puppy linux 6.05上的测试,包括我自己编译的dsbr100.ko驱动的实验,以及这个简单的radio的程序运行。测试视频已经上传到了youtube
  2. 硬件开发的困难是远远大于软件的原因就是程序员不仅仅需要逻辑,还必须要很多的硬件知识,甚至于常识。很多人以为常识是天生就会有的,其实不然,这里的常识是真正的开发者的共识,而门外汉是无法理解的。我一直想摆脱音箱以便我的收音机能通过我的笔记本的麦克风扬声器直接播放,这是一个简单的合理的要求,尤其是在需要debug的情况下。可是这个基本的要求我花了一个早上才部分实现,说是实现并不是我在动手改造,而是程序原本有的我却不知道怎么使用。第一步是概念,要做到这个需要什么?其实说穿了很简单你需要一个playback,但是你还需要一个sound capture设备,这个第二点对于缺乏硬件基础知识的我就花了不少时间才理解。播放当然好办,可是我们的usb fm radio它是在它的芯片里处理信号通过音频线输出,那么要让笔记本播放是不是透过类似麦克风这样的capture设备才能输入呢?这个很难想到吗?然后是设备名字要怎么说?我一开始执拗的以为就是/dev/snd/下面的设备,可是libasound始终说不认识,反复搜索才明白,这个在alsa管理下已经包装了一层,设备有很多扩展也有虚拟设备,所以,使用的形式是hw:x,y,其中x是卡,y是设备。怎么知道呢?可以用aplay -L和arecord -L注意是大写的L可以给你细节。可是播放设备还好一些,可是capture设备始终抱怨设备忙打不开。这个时候才有第二层的感悟,在硬件管理的世界里永远只有一个人能够独占式的访问设备,这一点在显示设备也是类似的,否则设备的缓存就乱了。所以,既然pulseaudio这种管理着声卡自然不让第二个直接访问设备。其他程序只能访问alsa/pulseaudio 的server,这更加类似一个mixer的角色。那么我的问题我怎么告诉我的radio我要让他访问那个server呢?我不知道,最后我找到了答案,告诉它是default程序会根据alsa的配置文件找到谁是default。因此我的小程序确实可以alsa_loopback了,可是新的问题来了,收音机播放会自反馈导致尖叫噪音,这个怎么办?这是处理音频程序的最最基本的问题,我却毫无头绪。刚刚说完常识,我就把自己的脸皮打得脆脆的响,这是一个不可能的任务。世界上的所谓的loopback本身是作为测试的,也就是说没有什么v4l2支持fm radio直接的数据读写的功能,这个是驱动压根就不支持的,我听到的笔记本的声音是从麦克风传入的所以才有回声,所以才有反馈。我已经对于loopback没有十分的兴趣,所以,这篇文章再有意义,我都懒得看了。我之前没有看到这个根本的radio interface所以我才会误会以为它也有类似webcam这样的功能,实际上是不可能的,因为radio就是一个tunning的功能,以及及其有限的user control的功能,直接读写数据是不可能的,我也看了内核的dsb-r100的驱动文件,的确是这么的简单。那么问题就是windows下为什么能够录音呢?我还幻想看看windows下的驱动看看反汇编能不能有什么线索,说不定它的Usb接口有什么内部api用来录音,且不说我这么的不知天高地厚的厚脸皮,就算是linux的反汇编我也看不懂,后来想其实windows录音也是很简单的,注意到它把音频输出线接到声卡上的Linein那么和我现在的Loopback是有些相似的,那么从声卡的输出也许就是容易多了。前提是我需要音频输入,我的笔记本怎么有呢?
  3. 其实录音的问题挺复杂的,你究竟要指定哪一个设备呢?我的服务器是有声卡的,而且还是挺不错的,是华硕的oxygen系列,我插入了headerset插孔,然后使用默认的设备arecord -c 2 -f cd /tmp/test.wav就可以录下收音机播放的信号了,这个可能和我没有microphone有关系吧。因为在笔记本上没有声卡的输入,而microphne是默认的输入设备,录音我也没有办法验证。总之,这个是一个复杂的问题,就是说fm radio只能借助于声卡的输入来进行录音,因为貌似usb链接的设备但是并不负责音频信号,它的信号是直接通过analogue的音频线输出的,因为并不是数字信号啊。所以这个xawtv项目是针对模拟信号源的电视与收音机。我录了两段视频来解释,第二段是因为我错误的插入了声卡的插孔。其实我需要的是headerset input。
  4. 但是问题并没有结束,你其实本可以买一个很便宜的收音机然后使用电脑来录音,如果你有一个usb的声卡的话。我录了视频来解释这个意思。

七月四日等待变化等待机会

  1. 终于回到正轨,继续看hana的helloworld,但是每一步都是地雷,我完全没有信心了,原本以为我有了比较扎实的mpl的基础,可是发现我对于local function这个部分必须恶补,我本人对于它的厌恶是有害的,因为如果要接受c++的现实必须要去拥抱它。而再一次的我看到c++14的新feature的user-defined-literals让我胆战心寒,难道我以后命名一个变量再也不能随心所欲的使用下划线了吗?我又再犯糊涂,这里不是变量名而是literal,没有什么混淆的,是我自己脑子不清楚了。不过这个新的操作符""的重载还是让我看得目瞪口呆。 这个例子让我深深的震撼,这是怎么做到的!而且结构就是简单的结构没有任何的额外附件,因为我用平常正常声明的结构来对比大小访问方式都没有区别,这个实在是太神奇了! 这个是结果:
  2. 我的git repo设置都有问题,不仅仅是原来设置的问题,我本地使用最最简单的micro_httpd不能支持git,问题是我明明知道我的server不是git-smart,那么就应该直接禁止啊,所以,我无需改造micro_httpd来支持参数,只要这样子的环境变量就可以了:GIT_SMART_HTTP=0 git clone 我使用GIT_CURL_VERBOSE=1 才发现git clone实际上索取的url是这样子的:GET /myprojects/radio3/repo.git/info/refs?service=git-upload-pack。而micro_httpd这种原始人是不支持带参数的。
  3. 看样子micro_httpd是不用更新了,不过留下一个空架子只有一个简单的编译脚本。以后有计划再实现新功能吧。

七月五日等待变化等待机会

  1. 昨天看到代码里有这个__attribute__((__unused__))google才知道这个是压制变量没有被使用的警告的,这个在lambda的参数是常常遇到的。注意属性是放在变量和函数的后面。
  2. 我感觉如果我不攻克phoenix,我可能会有很多的困难学习hana,因为local function对于我是一个非常弱又绕不开的难点。同时,对于phoenix在多大程度上改进lambda我也疑惑。他们是什么关系呢?既生喻何生亮?首先,这个是一个温习与提高,我心里也很忐忑,究竟这些不同的库能否混用呢? 这个是简单的结果,首先我把随机数中第一个小于50的区间返回,然后把数组中奇数和偶数分成两组。 这里我补充了一下对比phoenix和lambda的条件做法,这个也是我非常反感的操作符滥用的地方 结果分别是大于50和小于50的数字
  3. 顺便说一句,我对于编译器是否真的使用inline始终表示怀疑,但是,确实的inline函数是不会出现在symtable里的,那么我的问题就是我能够使用lambda::bind它吗?粗略想来似乎不大可能,实际上是可以的,因为bind是一个编译期的东西,无非是地址,并不是动态调用依靠symbol name去表里查找,所以,是毫无关系的问题的。我觉得是有关系的,因为inline似乎是有条件的,当我的inline函数无人引用的时候,它的确不会出现在symtab里,可是当我用bind后,它就存在了。我的理解看来函数地址是必须有一个symbol来对应才能绑定吧? 我定义了一个inline函数 随后我使用lambda::bind它,并调用。 结果是正确的 当然它在symboltable里是不存在的OMG,它在symtab里是存在的! 当然如果我把我的bind去掉就找不到了。
  4. 这里有关于lambda vs phoenix的讲解,我还只能理解个大概,纯粹英文文字程度吧,因为目前我对于一些基本的lambda/phoenix的东西还搞不清楚。比如我在比较lambda::constructor和phoenix::construct,我完全不理解后者的用法,前者是一个functor,我有一个工作的很好的例子,可是后者我连基本的用途都搞不懂。经过差不多两天才搞明白了phoenix::construct的奥妙,它的代码非常的难看因为有很多宏,而且定义里的函数construct是有重载的两个形式,我一开始eclipse指示的不正确也误导了我。 我现在分别使用lambda和phoenix创建一个数组 可是结果phoenix根本就是空的,当然查看代码知道phoenix::construct不是一个functor,而是一个函数,那么显然我这么bind是错误的它的确是一个functor,我下面有证明。 所以,我只能使用函数调用,可是我目前只有常数可以 结果是常数 可是我连一个最基本的调用都编译不过去。我们来证明它是一个functor 结果当然是hello,那么为什么我们以上的测试不成功呢?
  5. 对于constructor/construct的用法我还是不确定,似乎phoenix::construct的做法让我感到困惑。我先实验了一下怎么使用lambda::constructor的例子但是我还是用到了一个conversion constructor,这个让我感到很不安,因为这个不是使用constructor的理由吧? 我定义了一个constructor需要参数int的结构,它能够被当作string使用,因为它有conversion constructor 现在我们创建一个数组 结果是可预期的
  6. 这个再次复习一下,我估计我以前也做过吧?就是怎样分配一个shared_ptr的vector 结果如下

七月六日等待变化等待机会

  1. 昨天我始终无法理解phoenix::construtct的机制,因为代码看不清楚,我甚至不明白它是怎么定义的,模模糊糊的我觉得它本身不能作为一个functor,但是它可以生成一个functor,就是说它本身是一个函数,它返回一个functor,这个让我很烧脑,我不知道要怎么绑定它,因为总是报错,也许是什么简单的错误造成的?这里是简单的展示,感觉作者认为这个东西很简单不值得给出一个基本例子,想对lambda::constructor来说是很简单的东西吧? 我不知道要怎样使用phoenix::local_names::_a来作局部变量
  2. 对于phoenix::construct的代码我看了差不多两天才看出一些眉目,终于明白了它是怎么回事!首先construct是一个函数不是一个结构,这个简直是废话,可是,它是可以作为functor来使用的,因为它返回一个functor它返回一个functor,而这个functor被调用后返回一个模板参数定义的对象。要解释这个需要看看我的下一个例子。,那么我如果要传入construct的参数是另一个函数的话,我就必须bind这个construct,令人欣慰的是它的重载是模板的,所以我可以使用模板参数直接告诉编译器我需要的是哪一个形式,这个是第一个bind,但是不要忘了它返回的是一个functor,因此在for_each的回调函数要再bind,这个需要下一个例子来理解。 结果是正确的
  3. 这个例子表现了construct返回的是一个functor,而调用这个functor才能得到对象。 结果就是你期待的 我觉得这个例子更加能够清晰的说明 结果就是一个随机数
  4. 对于lambda::bind和phoenix::bind有什么区别呢?这个界别的代码及其复杂我连边都摸不着,但是我可以负责的说两者的实现是不同的,为什么呢?似乎lambda的实现比较简单就是把参数个数罗列枚举,而phoenix似乎比较高明,这一点让我第一次对于phoenix的佩服的地方,但是我发现似乎两者的功能并没有什么区别,我重复了之前的lambda::bind的例子结果是一样的。 结果是正确的

七月七日等待变化等待机会

  1. 针对昨天的发现,那么问题是phoenix::construct和lambda::constructor相比究竟有什么优缺点,或者说既生喻何生亮?我做了一个进一步的比较,这个对于理解似乎更加的直接。前者是一个函数返回一个functor,调用functor返回的是对象的constructor,后者是构造一个对象的constructor的functor,调用functor就是调用了对象的constructor,这两者的微妙的区别究竟有何不同的使用场景呢?我现在还想不出,感觉后者可以反复多次的绑定不同的constructor的参数类型,前者就是直接绑定了? 我始终觉得这里有一个非常微妙的地方就是phoenix::construct的模板参数看上去只有一个,但是这个是误导,注意到我昨天绑定的形式是两个,而这个正是我花了将近两天才搞明白的地方,它是有两种constructor的参数overloading的,一个的实际上是一个default constructor就是string的空字串,就是说下面的这个实际上的模板参数类型是这个样子的。为什么可以只传一个参数呢?因为模板参数是可以推导出来的。因为带实参的就注定不是那个default constructor了。这个问题需要看phoenix::construct的代码才能明白。 这个代码就是我说的 而我之前被折磨的是因为我错误的绑定了这个形式的 这里用到的string的constructor是一个平常我比较少使用的形式,就是取数组的前n个字符。结果都是hello
  2. 这一段关于environment/context/action我看得眼睛都疼死了也不大明白,这个跳过吧,实在是不懂。
  3. 大师说过:把一件简单的事情搞成很复杂实际是很简单的;但是把一个很复杂的事情做到很简单实际是很复杂的。我现在已经具备了前者的技能,可以炫耀一下怎么作。比如我要怎么初始化一个shared_ptr呢? 任何人都知道这个简单的做法 可是我有办法把它写成这样子 当然他们都能打印出那个宇宙及其人类社会的终极答案的伟大数字:42!
  4. 我散步的时候其实是想明白了为什么phoenix::construct比lambda::constructor好的理由,或者说各有优点的地方吧。就是前者对于带有实参的情况可以不要再去很繁琐的绑定,我试图要用一个例子来说明,结果发现完全不明显,不是结论不对,而是我的例子不说明问题。 结果是两个数组的数字是一样的 这个例子真是一个反例,我甚至不懂得怎样写一个局部变量。因为lambda::var在phoenix里似乎不接受,而我又不会写局部函数,所以,变成了一个常数。 相比之下,我更加钟意lambda的风格,至少局部变量我是驾轻就熟的 结果就是这样子的 要达到同样的效果我不知道phoenix要怎么作?
  5. 一个让人恼火的就是phoenix很多都没有基本的例子,而且也没有测试程序覆盖,让人完全摸不着头脑,比如这个local_variable究竟是干什么的?要怎么用呢?看代码也是茫无头绪!

七月八日等待变化等待机会

  1. ubuntu的发行代码(codename)我老是记不住。这里是下载bionic的内核代码的git: 但是那个编译指示太老了,我发现这个编译ubuntu kernel更好。
    1. cp /boot/config-`uname -r` .config
    2. yes '' | make oldconfig
    3. make clean
    4. make -j `getconf _NPROCESSORS_ONLN` deb-pkg LOCALVERSION=-custom
    都不好,我觉得我为了编译一个驱动而编译整个内核是一个错误,这个是文档所清楚表明的。不过我out of tree编译的驱动怎么安装才是正确的呢?我觉得我不应该自己拷贝那些modules.xxx文件,应该运行depmod自动生成,对不对呢?
  2. 我发现使用这个makeself是一个及其好的self-extract-install脚本。我保存了一个版本
  3. 对于construct/constructor的实践在继续,原因就是我对于它的使用还是不确定,因为实际使用中是否需要使用ref非常的关键,我为了这个参数类型花了两个小时。我们先来实现一个std::back_insert_iterator的创建。
    1. 对于这个向量我们需要创建一个它的back_insert_iterator
    2. 首先我们使用lambda::constructor来创建一个iterator,注意这里我们需要使用boost::ref来传递参数,否则iterator就是在一个copy上无效的操作。
    3. 如果使用phoenix::construct的话,是这样子的,注意这里要使用phoenix::ref,如果使用boost::ref的话编译会出错。
    4. 我们来使用这个iterator来填充数据并打印出来
    5. 结果是一样的
  4. 但是我要怎么实现这个nested容器的算法呢?也许这个就是phoenix需要解决的?至少我看到lambda是无能为力的。
  5. 这个说明了怎么制作esxi installer的USB installer
    1. 先用fdisk把usb设定成fat32我就是这里搞错了,我以为是linux的类型。
    2. 接下去就是要制作一个fat32的文件系统/sbin/mkfs.vfat -F 32 -n USB /dev/sdb1
    3. 要制作syslinux bootable(注意我的ubuntu不允许sudo cat,只好用dd): /usr/bin/syslinux /dev/sdb1
      sudo dd if=/usr/lib/syslinux/mbr/mbr.bin of=/dev/sdb
    4. 接下来用mount -o loop iso image以便把所有文件拷贝到usb上。
    5. 需要Rename the isolinux.cfg file to syslinux.cfg.
    6. In the /usbdisk/syslinux.cfg file, edit the APPEND -c boot.cfg line to APPEND -c boot.cfg -p 1.
    7. 这里告诉我们怎样修改boot.cfg: kernelopt=runweasel ks=cdrom:/KS_CUST.CFG

七月九日等待变化等待机会

  1. 我觉得这个明显是phoenix的bug,因为我是对比lambda的实现作出的结论,至少是从现象上来说的。我要么是出现了幻觉要么是哪里有个及其愚蠢的typo,总之我被折磨了好几个小时以为有什么奇怪的现象发生了,至少目前来看两者还是一致的?我现在可能已经有些犯糊涂了。 首先我们知道phoenix的construct和lambda的constructor在某种程度上是等效的,我的实例也证明两者都是可以对实参工作的。那么很自然的如果我定义他们的functor来实施看,这个是lambda的做法 结果是正确的,那么来看看phoenix的做法 可是我如果进一步定义一个generate_n的仿函数的话就不行了,这个连编译都通不过
  2. 对于这个问题,我还需要理解,我很怀疑我的问题是这个BB和BLL的微妙差异

七月十日等待变化等待机会

  1. 昨天被工作打断了学习。我想先从lambda的无名函数开始,也许我的问题是有解决办法的。首先,练习一个最最简单的关于operator[]的例子。我觉得这个地方是一个关键,我始终无法理解的地方就在这里,之前关于forward_adaptor也是卡在这里,究竟函数参数在wrap之后怎么变迁的呢?
  2. 我想我终于找到了,说来惭愧,这个就是lambda的官方文档写的关于nested algorithm清清楚楚,我当时就是没有看懂,看到和没有看到一个样。有准备的头脑不是说就是你想准备就有准备,而是你经过了实践遇到了问题,在积极探索解决问题的头脑才能是处于所谓的有准备的状态来等待发现的到来。这个正是和球场或者战场上的有准备的球员和士兵一个道理,空喊时刻准备着是没有用的,只有不停的训练而且是有针对性的训练找不足,补短板,不停的发现问题解决问题才是时刻准备的状态,也只有这样子,机遇到来的时候,你才是有准备的头脑有希望抓住机会。 对于无比复杂的generate_n我都写出了,结果居然卡在了for_each,只好先将就着这个吧。这个是我第一次正经的使用BOOST_FOREACH 结果是这样子的
  3. 然后我才注意到了怎样解决for_each的begin/end难题,其实,这个是多么普通的要求啊!居然有这个call_begin/call_end我以前怎么没有注意到这个所谓的call_begin/call_end是一个相当复杂的宏CALL_MEMBER,大概的原理是把类型中可能的const去除取得纯粹的类型(当然肯定是容器类型了),然后定义T::const_iterator的返回值,再去调用o.begin/end而已。不过这个宏实在是太具体了,所以,不开放给大家通用,我使用static_cast应该也是可以吧,反而是直接使用range里定义的boost::begin有很多问题,似乎是namespace的某种技巧,抑或是range的iterator和std::iterator的类型我搞错了。,但是话说回来了,for_each你要怎么打印一个换行符呢?functional programming如同行云流水完全不再有循环计数的累赘,但是掩盖了循环的细节之后,我要怎么打印换行就困难了。有一利必有一弊。 结果就是不换行的一长串,这个完全抵消了它的优点。
  4. 对于lambda::bind和boost::bind的微妙差异,我一时还无法体会,先记录下来吧。
    The bind expression inside foo becomes:
    bind(bind(bar, 1, _1), _1)(x)
    
    The BLL interpretes this as:
    bar(1, x)(x)
    
    whereas the BB library as
    bar(1, x)
    
    To get this functionality in BLL, the bind expression inside the foo function can be written as:
    bind(unlambda(f), _1)(x);
    
  5. 其实,有时候FP是很难的,因为一个流程和步骤化的操作是矛盾的,所以,与其费尽心机去写一个无比复杂的函数不如简简单单的写一个临时函数,所以,解决我的嵌套容器的算法就是这么简单。这个是我今天最满意的两行代码,一行分配一个数组的数组,另一个打印他们。 结果是这么的赏心悦目!
  6. 我吃完午饭后有了一个更好的,也就是更加彻底的做法,就是这个无名函数。只不过这个写法太没有技术含量了,变的什么人都看得懂了。 结果是正确的,
  7. 我完全看不懂这些callable究竟有什么区别!怎么叫做deferred?怎么就是polymorphic?这个东西太难了。你能想象我居然不知道decltype这么好用的好东西,它和auto一样成为无数小白的无敌利器了! 需要注意的是decltype是针对entity,就是说不是类型,这一点恰恰是我深感头疼的,因为关于类型我已经有了BOOST_MPL_ASSERT配合boost::is_same,那么对于expression就是完全无能为力的,而这个经常是接触hana/phoenix屡屡遭遇的,我经常被模板参数的错误而苦恼,对于一个expression现在使用decltype这个试金石真是程序员的大幸事啊!可是我始终还是搞不明白result_of和decltype,其中boost::result_of和std::result_of也是不一样的。我都快饿晕了,也没有搞明白。唉,我真的是白学了十几年啊。
  8. 我实在是不明白我的阅读能力有问题吗?好像我的阅读能力的确是有问题!这里的std::result_of为什么定义失败呢?
    template <class Fn, class... ArgTypes> struct result_of<Fn(ArgTypes...)>;
    Fn
    A callable type (i.e., a function object type or a pointer to member), or a reference to a function, or a reference to a callable type.
    ArgTypes...
    A list of types, in the same order as in the call.
    Notice that the template parameters are not comma-separated, but in functional form.
  9. 实际上根本原因是我对于函数的类型不是真的熟悉,比如,一个基本概念是自由函数是一个类型吗?这个简直是白痴级的问题我居然不清楚,函数怎么可能是类型呢?functor当然是类型了,因为它是一个结构。我使用typeindex::stl_type_index::type_id<func_type>().pretty_name()来打印了这些类型的型名,当然对于一个函数它不是类型,你只能使用typeindex::stl_type_index::type_id_runtime来得到它的签名 对于结果有很多有意思的地方
    1. decltype(func)本身就是函数的“类型”,那么这个类型的引用和指针类型是怎么书写的呢?这就是第一和第二。
    2. 函数类型和它的引用的类型是看不出区别的两个都是int (int),换句话说func_ref和func_type在decltype眼里是一样的。但是这个仅仅是打印的pretty_name,实际的类型id是不同的。我的证明如下:
    3. func或者说自由函数本身不算是“类型”这也就是模板参数不可以使用func的原因,它说到底就是一个地址,不像类型往往并没有地址,只有大小吧?不过对于c的做法总是使用typedef定义一个结构似乎破坏了这个规矩。所以,c不是一个类型很强的语言嘛。总之,使用type_id_runtime。
  10. 我之前还怀疑过boost::result_of和std::result_of有区别,现在我认为两者是一样的,应该是前者不依赖编译器的新特征。它的关键有两个,一个是它的“函数”必须是callable,而且必须跟着参数组。 结果都是int

七月十一日等待变化等待机会

  1. 关于函数尤其是成员函数其实是有很多的误区的,至少我都是一知半解。这里我想做一个小小的实验展示各个类型以及返回值是怎么定义的。 首先定义一个结构:
    1. 首先我们从最基本的开始,那么decltype(my)是什么呢?它是callable吗?我觉得它指的是MyStruct是一个functor,所以,MyStruct(string)并不受callable的限制,很像constructor,但是它是operator(),这就是为什么functor得名的一个原因,它是"callable"的吧? 结果是这样子的其实就是MyStruct(string)。注意my(str)的返回值就是string,这个是和我们的期待一致的。同样的MyStruct(string)是它的函数签名类型。
    2. 这个让我一开始意外了一下,其实说明我脑子糊涂,它是一个函数的调用,当然是只有返回值代表它的类型了。 所以它的结果就是int
    3. 成员函数的“签名”是怎么样子的呢? 它的签名和我的预想有不同吗?这个就是标准的。
    4. 那么我们要怎样来写一个成员函数并使用result_of来获得返回值呢?(这个问题看似没有意义,但是如果要“自动化”编程,这个是很有意义的功能,比如你要serialize一个结构的话。 返回值是这样子的 其实很简单的:string(MyStruct::*(string))(const string&)
    5. 接下来的问题是如果我们如果带错了参数,编译器是否会检查呢?我首先获得成员函数指针地址,并得到它的型参类型,但是我故意使用错误的参数类型,张冠李戴,然后看看boost::result_of是怎么推理的,结果这似乎是一个bug,就是说根本不检查成员函数的真实的地址,而是根据型参来推理是哪一个函数的才得到返回值,这个是否是bug还是不足或者是标准不支持呢?我看了后面std::result_of的例子认为这个本身不应当被支持吧? 结果就是说依靠型参推理的,而不是依靠函数指针,这个当然是对的,因为指针我们只有类型,并不会考虑真实的地址,因为result_of得到的就是型参int (MyStruct::*)()
    6. 而std::result_of比boost::result_of高明的地方就是这个根本编译不过 而与之对照的是boost::result_of没有问题而且结果就是int
  2. 关于这个phoenix的所谓Adapting Functions实在是太深奥了,我花了差不多一个下午才理解了其奥妙之处,它的这个宏BOOST_PHOENIX_ADAPT_CALLABLE是需要深入理解的。我在想有没有什么工具能够把宏展开来,因为我是可以在eclipse里看到展开的结果,但是如果能够拷贝下来更好啊。
    1. 首先我们来理解一下为什么需要这个Adapting Functions,参考文档我做了一个我自己的相似的functor,在这里我逐步意识到这种functor是无法有状态的,这一点非常的重要,可惜的是我意识到这个却没有转化为行动挽救我的失误。我之所以意识到这一点是因为我原本有定义成员变量,可是我然后意识到我在这种模板函数中要怎么才能根据参数的类型就相应的调用不同类型的成员变量呢?似乎没有办法啊,除非我依靠什么神奇的宏并且变量的命名有相应的规则,但是这个实在是超过普通的模板能力了吧?所以,我是从这一点推理出这一类的函数就是一个constexpr的模式,这个就是为什么文档的例子是一个plus,当然我的例子其实就是一个identity,都应该是符合constexpr的模式的。
    2. 请注意这个A0 operator()(const A0& a)const!它非常的关键,因为我们先来做一个实验看看result_of的语法,就是因为这个const导致我们对于这个result_of不论是否采用const都是正确的 而这一点很关键,就是说BOOST_PHOENIX_ADAPT_CALLABLE这个宏是选择boost::result_of<const MyStruct(int)>::type。而operator()const和operator()的函数签名是不同的,所以,没有定义这个const的话这个宏BOOST_PHOENIX_ADAPT_FUNCTION会报错的。
    3. 那么我们如果没有使用这个宏BOOST_PHOENIX_ADAPT_CALLABLE的话要怎么使用MyStruct的模板函数呢?没有对比就没有需求,看了这个非常笨拙的调用方式你才能理解为什么需要这个宏 也就是说为了得到这个结果你要如此违反本能的使用这么复杂的做法。任何人都会想难道这个形式不行吗? 比如MyStruct(n)或者MyStruct(str)不行吗?当然不行!因为MyStruct本身并非模板类,只是它的成员函数是一个模板函数而已,当然如果你已经有了一个MyStruct的实例的话的确可以 结果是可以预期的
    4. 如果我们使用了这个宏的话有什么好处呢?我于是用它定义了一个adaptive function就是myId 然后我就可以如此容易的使用我的functor了
    5. 看上去似乎和我们先去定义一个临时变量的做法类似,比如MyStruct my;,可是实际上是不同的,它实际上是使用了一个临时变量MyStruct().operator ()(n)的做法。 为了说明这一点我仿照宏的做法定义了我的类似myId的一个函数myFunc 这里注意返回值必须使用typename因为boost::result_of<const MyStruct(const A&)>::type是一个dependent的类型。那么这个函数有什么好处呢?就是说它和用宏定义的myId是等价的
    OMG!为什么一个看似平淡无奇的宏花了我一个下午的理解呢?最主要的是卡在operator()const的问题上,我一开始没有加上它导致我定义的myFunc居然没有问题而BOOST_PHOENIX_ADAPT_CALLABLE总是报错。
  3. 我一时的眼花把两个不同宏看成了一个,这个BOOST_PHOENIX_ADAPT_FUNCTION我感觉非常不错,因为我还没有想到有这个办法可以变成一个functor。可是难道我使用lambda::bind不也同样能够做得到吗?
    1. 首先定义一个简单的函数
    2. 然后我们使用这个宏就做成了一个functor
    3. 我们怎么知道这个是一个functor呢?我们有一个利器可以来查看它的类型 第一个是一个函数的调用那么它的decltype是它的返回值的类型就是string而已 第二个是函数的签名 你有没有被吓倒了?我是帕了,很难想象这个都是编译期的消耗而没有运行期的损耗,我对此感到担心。不过我看看函数签名感觉也许就是一个比较复杂的list之类的结构,也许内存浪费了一些但是指令也许不多吧?我不是很确定。
    4. 那么我们还是看看这个functor的使用吧 结果是
    5. 那么我们如果有了lambda::bind干嘛需要这个庞然大物呢?先看看lambda的签名吧: 好像两者差不多啊!
    6. 不过因为string是臭名昭著的又臭又长我想换一个int的参数看看函数的签名如何 现在看看结果如何

七月十二日等待变化等待机会

  1. 我实际上对于c++的语法已经被这些搞混乱了,比如这个是phoenix的重载了operator[],可是c++11/14是built-in的语法吧?这一部分的语法我一直没有很好的学习过,现在要补课啊!可是这个c++11/14/20的lambda语法十分的晦涩难懂啊!我脑子已经开始把boost::lambda和c++的lambda开始混淆了。那么spirit的重载是怎么样的呢? 需要注意的是c++11的operator[]是依靠编译器的语法实现的,根本不是什么操作符的重载(废话),那么{}里的是一个statement,所以,当然要以;结尾。可是boost::lambda/phoenix的operator[]的重载根本不是一个statement,它是不能用;结尾的。 结果显示了数组的全部数字

七月十三等待变化等待机会

  1. 偶然看到社交媒体上的这个帖子,就是怎样递归定义一个functor吧?大概是这个意思吧?
    1. 如果使用一个functor也可以这样子定义 结果就是120
    2. 当然如果允许我实现了递归定义可以使用phoenix_adaptor_function的宏来把它改造成一个functor
    3. 可是我的这个和作者的问题相差甚远,这个实在是无关的,因为不是functor
  2. 那篇文章里把函数指针存在vector里的最大新颖之处是存的无名函数,这个的确很有意思。 结果是这样子的
  3. 这个lazy function的例子很不容易啊,首先,这是一个nested container,对于这种嵌套式的容器要使用嵌套的for_each很不容易,因为lambda::placeholders在嵌套里是分不清楚,因为我们要定义一个functor而不能使用实参,否则对于lambda来说实参都不是问题。不过我也发现作者的例子其实不是很好,原因就是作者也没有对于内部的vector先分配size,导致for_each不灵,这或许就是作者实现了push_back的原因,而对于一个空的vector作者的单次push_back让一个vector才一个元素很不使用,所以,我改了它的算法先分配了元素大小,这样就再次使用嗯for_each。 首先作者定义了一个for_each的lazy functor 然后我们定义了一个function f。它需要一个嵌套的vector和一个int作为参数。 这个是结果

七月十四等待变化等待机会

  1. 关于昨天的嵌套的vector的操作,我打篮球的时候想到了这个是一个非常简单的no-brainer的问题,既然lambda不能接受型参,那么就使用c++11的builtin的无名函数来传递实参不就行了吗?所以,我的改进的做法我觉得比boost的lazy函数至少在解决这个问题上容易多了。 结果是这样子的
  2. 这样子使用c++11的无名函数破坏了generic programming的原则,但是容易的多了。我把嵌套vector的初始化和打印都写成了函数,这样子以后可以反复使用了。
  3. 我感觉我必须转战到proto的战场,因为phoenix是一个高度依赖proto的库,至少我是这么认为的,我原来以为proto是一个runtime的应用,但是看起来phoenix能够跨越阴阳界限是依赖了两个大法师的功绩:mpl+proto?我是这么猜测的,因为这个例子我几乎摸不着头脑。而且我之前看的所谓actor,context,environment,action,expression,evaluation,evaluator等等我完全的lost。这里也是完全看不懂,看来必须要转到proto去了。
  4. Proto的开篇就是莎士比亚,我都看蒙了。
    There are more things in heaven and earth, Horatio, than are dreamt of in your philosophy.
    -- William Shakespeare
    实际上wiki对于expression template的解释才让我有些开窍。其中关于矩阵加的例子让我想起了大师也有类似的问题,不过是从语言本身来考虑。这个expression template的确是一种高深的技巧,的确如莎士比亚的哈姆雷特的对白所说的我是多么的孤陋寡闻,天堂里有着我想像不到的良辰美景,无知限制了我的想象力。

七月十五日等待变化等待机会

  1. 完全“偶然的”发现了一本书。说是“偶然”是因为我早已忘了我什么时候买的这本书,唯一可能就是去年回国的时候买的,应该从来没有看过,或者看了开头也没有看懂吧,我猜想那时候我还没有这个能力看这本书吧?虽说我对于模板是有基础的,那也不过是很多年前看过侯捷翻译的那本大多数是容器方面的。今天偶然去爬山要假装在高山之巅阅读随便抓了一本书不小心就带上了它,同时还有一本《三体》,所以可见我压根没有打算“看”它,对我来说书的看字是很准确的,因为我就会看它几眼而不会阅读。阅读是一个智人才有的能力,我是猿人只会看书。但是经过了这几个月的训练我发现第一章是很轻松的,仿佛修炼了九阳神功之后内力充沛修习乾坤大挪移是轻车熟路一般的感觉,第一二章应该不在话下。这个就是我的读书的方法,我只能读的懂我读的懂的书,就又如我只看我看过的电影,新片子一律不看因为你不知道外面的烂片子有多少,小心玷污了眼睛。

七月十六日等待变化等待机会

  1. 这本书《c++模板元编程实战》确实是不错,因为我在一个小时内居然睡着了四五次,请注意这是我对于一本好书的最高的褒奖就是能够让我睡一个好觉。这本书确实是不错因为它非常的适合我现在的需求与状态,而且作者确实是这方面的专家他的点评非常的到位。然后我决定晚上看前几天买的《Big Bang Theory》的最后一季,我其实并不喜欢第四季以后的部分,反正这部戏女人多了就不好看了。

七月十七日等待变化等待机会

  1. 阅读李伟前辈的著作发现了一个小问题,似乎是我的ubuntu18.04默认的gcc-7的编译器不支持c++17的语法gcc-7是支持的,看这个表就知道大部分feature在gcc-7以前就开始支持了。,我的发现是这样子的。首先,c++17对于模板的variadic的参数是支持的,对于variable template的支持是从c++14就开始的,那么对于以下的语法编译总是报错 现在知道了我的错误就在于一个空格必须插在>=之间。我的观察是即便我在编译指令里强制-std=c++17,但是最终的gcc编译命令依然是GNU C++14 (Ubuntu 7.4.0-1ubuntu1~18.04.1) version 7.4.0 (x86_64-linux-gnu),那么gnu c++14究竟支持多少c++17的feature呢?我发现这个页面非常值得研究究竟支持多少。我的考虑是这个领域已经不是随随便便浏览浅尝辄止的范畴,完全是在一个相当的深水区,因此,必须要从工具与环境做起,而且工欲善其事,必先利其器是不变的原则,与其受制于系统默认的编译器限制不如自己动手编译最新版的gcc,因为c++这些年发展很快很多时候我感觉编译器不一定能追的上语言标准的发展。从大师的演讲来看语言的发展似乎进入了一个快车道。 我以前也多次编译过gcc所以,这个流程并不陌生,不过照方抓药还是省事省力的。而且他有一个脚本可以自动化,我也想改造他的,不过似乎已经很好了,以后在说吧。 首先是下载编译的dependency
    1. wget --no-check-certificate https://gmplib.org/download/gmp/gmp-6.2.0.tar.gz
    2. wget --no-check-certificate https://ftp.gnu.org/gnu/mpfr/mpfr-4.1.0.tar.gz
    3. wget --no-check-certificate https://ftp.gnu.org/gnu/mpc/mpc-1.1.0.tar.gz
    4. wget --no-check-certificate ftp://gcc.gnu.org/pub/gcc/infrastructure/isl-0.18.tar.bz2
    其实后来我发现统统从gnu gcc infrastructure的ftp下载似乎更好。我把作者的脚本略加改动存一个版本
  2. 我编译完了gcc-10可是我的模板依旧不能被接受,究竟c++20是否是被gnu++17接受呢?我想我误会了,因为gnu++17是默认值,就是说这个是gnu自己的扩展和c++20没有关系。再重申一遍,这个表比gnu/gcc的容易看一些。
  3. 我花了差不多整整一天才有些头绪,我觉得variable template和普通的一个结构的variadic template arguments是有某种微妙的差别吧,亦或者我有什么关键的地方是的我花了好几个小时才总算找到了错误,这个非常的tricky,一开始我以为是gcc的bug,后来才意识到这个是语法的一点点的歧义性。我在下一节来解说!没有找对,但是我觉得编译器的问题的可能性比较小因为这个feature并非新东西,首先variadic template是c++11就有了。
    Language Feature Proposal Available in GCC? SD-6 Feature Test
    Variadic templates N2242 GCC 4.3 __cpp_variadic_templates >= 200704
    Variable templates则是c++14就开始支持了。
    Language Feature Proposal Available in GCC? SD-6 Feature Test
    Variable templates N3651 5 __cpp_variable_templates >= 201304
    我目前的gcc-7编译器完全是支持的,根本不需要编译更高版本的编译器。我尝试着不使用variable template,而是使用传统的structure似乎是可以的,所以,我怀疑这个是vairable template本身对于variadic template的天生的限制吧两者在模板方面没有功能上的限制!
    1. 首先是一个前置声明,我感觉variable template就是这个地方没有办法做到。
    2. 这个是递归的终结条件
    3. 这个是递归逻辑
    4. 这里是测试程序
    5. 这个是结果
  4. 终于找到了bug!这个是一个语法的“歧义性”的问题,当然是我自己的错误,编译器没有错!这就是一个空格引出的血案!我再次看李前辈的书上的代码,他的“<”和“=”是换行了!我能说什么呢? 这个是我花了好多心血才复原了李前辈的例子 结果是正确的 那么我为什么一直编译出错呢?请看下面就是我的非常tricky的错误: 这个是正确的语句 而我的错误在哪里呢?
    constexpr size_t Accumulate<1first, Number...>=2 first+Accumulate<3Number...>4;
    编译器认为第1和第4操作符组成了模板,而中间的第23则是比较操作符,这个是不是让人哭笑不得?我无语以对,长久以来我对于编译器判断模板的<>很有信心,对于早期c++98的时候双重模板和operator>>混淆不清的问题解决的很彻底,导致我现在对于模板的符号随随便便,这一次终于吃苦头了!编译器没有错,错的是我!因为我在第2>=之间没有留出空格
  5. 整整一天我就看了大概不到两页书,把gcc-9/10统统编译了一遍!每个都要好几个小时!我的心得是为以前的做法太苯了,其实不然,因为gcc需要的三个库gmp/mpc/mpfr几乎都是不变的,所以,我完全可以先把他们编译好了,然后再多次下载不同版本的gcc来编译并链接他们。而这位作者的做法是中规中居的每次都这三位老兄放在gcc的目录下省却了配置,不过也许这三个库编译不用太久吧?不过配置三个库的引用路径太罗索了,还不如让gcc的configure自己去作,因为其中的stage1/stage2相当的复杂,我还一无所知。

七月十八日等待变化等待机会

  1. 我的体会是如果你没有系统的学习过c++11/14/17的话,你几乎不敢说你学习过c++,因为它引入了那么多的新的feature,c++98简直就是另一种语言,我的感觉就好像c++98和c99是两种截然不同的语言一样,当然我的感受也许有些过火,可是当我看到fold expression之后,我茫然然的一无所知! 所以,你能想象吗?曾几何时我一直以为模板参数的非类型实参应该只能是整数,因为这个想法是来自于enum的概念似乎这个概念还是对的。当我看到这个...被使用的出神入化的时候,我简直就是目炫神驰了。 看看结果也是有意思的,首先打印的结果是21.4 但是你无法assert它的结果就是如此,比如这个是不对的:这个BOOST_MPL_ASSERT_RELATION不应该被这样使用,我觉得我是滥用了它。 这个真的是昏头了,这个宏非常复杂,我不应该用它来测试! 不过我对于std::assert的使用感到困惑,似乎它对于挂号应该是,处理吧?的处理有问题,比如这个就不行总之,我觉得我以后就不再用std::assert了因为BOOST::ASSERT非常的好吧? 顺便说一下,fold expression使用+来操作两个操作符,所以,这个string也可以结果很好
  2. 李前辈有一个稍微“中规中矩”的fold expression,它的这个版本还是比较能够让我接受的,至少是从直觉上我可以预计返回的结果,因为上一个例子使用auto来定义返回值让人完全无法预测返回值的类型,比如sum(1,2)返回值类型是long int,那么sum(1.2,2.3)返回值就是double了,这个当然是模板的目的,可是毕竟这个fold expression的实现机制还是比较让人难以理解的,除非它内部就是如同boost的fold那样实现的?就是使用递归的方式拆解操作数? 这个模板使用整数还是比较能够让人理解的。 所以这个测试是正确的
  3. 一个早上我就看了半页书!
  4. 对于c++11引入的move constructor&&我始终不理解。
  5. 你听说过fold expression吗?我是在boost里才接触到的,我没有想到很多c++的新feature都是来自于实践,而boost就是c++最好的试验田,很多新想法新实践都是在这个广阔天地里扎根发芽开花结果最后丰收在c++的新标准里了。我仿照例子写了这么一个东西,觉得很好玩。 我觉得我要把这些背下来
    1. Unary right fold (E op ...) becomes (E1 op (... op (EN-1 op EN)))
    2. Unary left fold (... op E) becomes (((E1 op E2) op ...) op EN)
    3. Binary right fold (E op ... op I) becomes (E1 op (... op (EN−1 op (EN op I))))
    4. Binary left fold (I op ... op E) becomes ((((I op E1) op E2) op ...) op EN)
    为什么要重载呢?首先cout传入参数是引用类型,其次常量必须使用move constructor才行,其实就是literal作参数的问题。 那么我们可以输出这么一串的参数,你想过它可能有什么应用吗?我还在想,当然啊,如果所有的操作都可以用某个操作符统一的话,比如人工智能大数据的处理的一套流程的话??? 结果你想得到吧?
  6. 我看了integer_sequence之后简直是胆战心惊,这哪里还是我熟悉的c++98啊,我只能说我压根儿没有学过编程。我对于这句语法的逗号“,”感到不理解 这里的例子比如这个语法我还看得懂,它符合Binary left fold (I op ... op E) 可是这个例子的语法我都看不懂了 它符合那一条语法呢?这其中的,要怎么理解呢?这种语法完全就是编译期的事情,我连gdb看看的机会都没有,究竟它依照的是那一条语法呢?我连google都不知道要怎么问问题!
  7. 一波未平一波又起,我还在迷惑sequence pack的时候,看到有人利用intializer_list巧妙的构造一个从来想不到的作用。 你能想到它是怎么打印的吗?这里并没有使用fold expresssion但是使用initializer_list同样的起到了打印的作用,这个简直就是让我见了鬼了,怎么还能这样玩?我这个乡下人非要被你们城里人玩死掉了! 它是这么玩的 这样也打印了1 2 3 4 5 你觉得这个assert究竟是不是应该是true呢?一般我猜想返回值是一个ostream的对象总是不会当作0,所以都是true,但是这个做法总是让人心里难受。因为它有一种奇技淫巧的味道。

七月十九日等待变化等待机会

  1. 关于昨天的问题,我想了一早上,从gcc/lex之类的语法分析很难,这里有一个ebnf的在线网站,我觉得最接近的就是template argument list。但是我觉得也许,被重载了也不一定吧?所以先看看这个可能性再说。
  2. 我越看越惊心,这个paramter pack要怎么才能掌握啊?比如在brace-enclosed initializers里我就是非常的茫然。这个语句里是可以对于sequence pack里每一个成员调用函数?我在语法网站找不到这个 这个是c++网站的例子稍微改简单了 这样的调用 结果是
  3. 关于initializer_list初始化的trick被众多人所使用,其实应该说这个原本是paramter pack expansion吧?我其实都已经糊涂了,就是说我们使用了operator,的副作用去evaluate一个expr,当然,的右边是一个常数用来给initialize_list来初始化,这里在()外面是...会分配给这个函数?听上去我自己都无法理解。这个例子其实和上面那个没有区别,只不过作者解释了为什么一开始使用这样的初始化编译不通过 编译器报错说无法推理x的类型。我个人认为正常的做法是这样子初始化的。总而言之,这个都是使用了paramter pack expansion和我之前的疑问似乎不是一类问题,因为没有,在中间。
  4. 我现在考虑昨天的问题可能是这样子的,就是这个,就是普通的comma operator,然后因为左边是一个parameter sequence,所以,调用的时候自然的就????我对这个解释开始怀疑了,因为正常initializer_list里的expansion是不需要,的,这个语法我好像找不到啊?c++iso的资料居然要花钱买!也就是说为了排除initializer_list的干扰,这个语句是成立的这个地方的唯一解释就是普通的,操作符? 对于x的类型我一开始还认为是一个initializer_list,结果你猜是什么,居然就是sequence的最后一个元素的类型。 看了结果你就明白了
  5. 因为一个小的据点的阻挠结果两天毫无进展,仿佛强大的德国装甲师居然被一辆斯大林重型坦克阻止前进一样。然后尝试李前辈的那个计算整数二进制表达形式的1的个数的小程序导致eclipse直接就崩溃了。重启电脑看看如何?
  6. 重启后我想再次确认我的precompiled header是正确的,因为前两天好像反复使用不同版本的gcc编译过。为此我需要加上-H -Winvalid-pch来观察是否有! ../src/precompiled.h.gch
  7. 我已经使用过很多次https://www.tldp.org/的文章,可是它的名字我现在才明白。
  8. 我感觉我经常有一种幻觉,似乎代码敲进去编译不成功,后来不知道怎么删改,又重新敲了一遍结果一模一样的就编译过了?这个当然就是典型的见鬼了,可是我也怀疑是否我真的遇到编译器的问题呢?这个递归就是一个典型,我们小时候都听说过怎样计算一个整数的二进制表达的1的个数,那个时候看到标人用一句循环计算很震惊的,因为在图形编程里所有的算法都要讲究效率。那么现在看看模板的元编程的威力吧。不过书里面提到了我从来没有想到的问题是产生了多少的实例是否都可以被重用呢? 我做了一个简单的实验如果我测试的数据变大的话,编译好的可执行程序增大很多。
    测试程序可执行程序大小
    5591280
    5592288
    5594872
  9. 一天时间就前进了半页书!
  10. 再次强化练习递归模板,学习这个例子。这里的例子里有一个非常好的防止递归的做法就是使用sizeof来判断parameter pack的size是否为0来决定是否要递归,但是如果不使用constexpr的话编译不成功,编译出错的根本原因其实还是因为sizeof...(args)根本不起作用,为什么加上constexpr效果就是真正的替代了递归呢?我的理解是constexpr需要参数必须是literal,就是把原本的parameter pack要作实例化所以得到的是真正的size。这个就是我的理解,非常好的例子和技巧! 结果是这样子 然后作者为了避免递归使用if constexpr(sizeof...(tails) > 0)这里的constexpr很重要否则编译通不过。这个就是改进后的避免了递归的版本,非常的好!

七月二十日等待变化等待机会

  1. 李前辈的例子有一个shortcut递归的技巧非常的值的学习,就是说对于alltrue这类问题,可以利用第一个false就直接返回而不是机械的把所有的实例的结果都过一遍。前辈的例子实际上有更好的通用性,同时也是一个很好的技巧类似于enable_if这样的结构更加的好,因为并不是所有的情况都能够使用直接的判断吧? 作为对比我列出前辈的例子,我把变量名改了改。这里需要注意的是specialization的必要性,因为没有递归的截至条件就是无限循环超出编译器的模板层数。gnu有一个参数可以改变循环层数-ftemplate-depth=。这个当然不是我们希望的因为这个情况一般就是错误的代码,而加大递归层数经常是危险的因为整个系统的内存都有可能耗光,我的eclipse已经分配了4个G的内存了。 这里就是我强调的递归停止条件,我自己写的时候还在疑惑size_t(0-1)到底是奇数还是偶数,这里也是一个细节,其实你定义了value根本不需要关心isPreOdd等等了,因为用不到。 关于前辈的例子我发现还是有很多教育意义的,首先,我又一次遇到variable template的模板括号和等号“歧义”的问题!比如以下的代码的specialization中,如果constexpr bool AndValue<false,TNext>=false;中间没有空格的话编译器会糊涂并不知道你定义的是模板还以为是>=的逻辑符号,报出的错误当然也是很莫名其妙的了。这个错误我已经犯过了,而且找了一整天之久。 这个是防止递归的技巧,我说过“类似”enable_if,但是不同,因为enable_if是用于定义。总之这个是一个技巧吧。 很自然的这里阻止了递归的无限发生自然也就不需要specialization的递归终止条件了。所以,有些殊途同归的意思,但是减少了大量的实例,当然是优越的多了。
  2. 朋友!
    你到过黄河吗?
    你渡过黄河吗?
    你还记得河上的船夫
    拼着性命
    和惊涛骇浪搏战的情景吗?
    如果你已经忘掉的话,
    那么你听吧!
    这里的“黄河”就是Curiously Recurring Template Pattern(CRTP)。如果你如我一样对于它闻所未闻,那么以后请不要再自称你曾经系统的学习过c++编程。这个乍一看完全不可能编译成功的模式开创了微软的Active Template Library(ATL)。当然这种被称作上下颠倒的继承实际上是一种“静态编译期继承”,就是说它的继承是透明的不能做到“跨代”继承,比如我的例子中继承类的子类是无法在基类的接口中被调用到的。也就是说除非在基类的接口中提到实现类否则无法实现到我说的也对也不对,其实我觉得我的结论并没有错,就是说如果不在基类中增加实现类你无法做到新的实现类的部分,但是这个问题本身就不应该是问题吧?对于接口的应用者来说这种继承始终都是编译期,不可能做到运行期的virtual function的能力。总之,在我的例子里我可以再添加新的实现类连同基类的“新”接口实现。
    The Microsoft Implementation of CRTP in Active Template Library (ATL) was independently discovered, also in 1995, by Jan Falkin, who accidentally derived a base class from a derived class. Christian Beaumont first saw Jan's code and initially thought it couldn't possibly compile in the Microsoft compiler available at the time. Following the revelation that it did indeed work, Christian based the entire ATL and Windows Template Library (WTL) design on this mistake.
    CRTP的神奇是什么呢?首先,我们来声明一个模板类来定义它的接口,当然它和传统的类继承用虚方法实现的区别在于它要实现假定实现类的类型和实现方法的名称。不过,也是无所谓的,因为没有实现类的定义,这个模板基类也根本不可能被使用,所以,以后等实现类有了再改也来的及。 然后我们在实现类里继承是最关键的,因为看上去是循环定义,这一点我想c++语言的发明者可能也没有想到它可以这么用吧? 这个继承真的行吗?我们看看吧。 真的可以啊! 如果我添加了新的实现类它是这样子的 调用方式 和结果是
  3. 君子贵有自知之明,这一点c++的class是不全的,比如我现在才知道居然有这个限制,就是this指针只能在成员函数体内,否则你就得到一个这样的错误“invalid use of ‘this’ at top level” 我是在作里前辈的课后练习题想要实现一个返回类型自身size的元函数。当然sizeof就是了,可是如果你写一个可以返回任意类型的size的元函数恰巧传入的参数就是你的这个struct本身会怎样,当然我选择使用一个struct来实现元函数才出现了这个问题就是说CRTP不可能违反基本的常识,模板的参数不可能是结构自己,那个绝对是无限循环的谬误了。题目是很容易。 题目并没有要求返回结构自身的size,我是额外测试一下,对于空结构sizeof一律返回1,因为this指针不能在函数体以外得到。所以,我迫不得已使用一个非static成员函数来实现。而且不能是constexpr的。 结果
  4. 这道题目也太简单了,不过还是要做,因为上一道题如此简单都引出了this指针不能在非函数体访问的问题,所以,切不可大意。这个是验证输入类型的大小和数值是否一致
  5. 前辈的问题是学习了这么些元函数的形式我还能想出什么形式?我也学习了几个月了,可是脑子里却如泥沙俱下没有留下什么精华,不知道根据函数返回值的大小来判断一个类型是否匹配已有的函数参数类型这个算不算一个高级函数?就是有名的SFINAE,但是这个enable_if类似的复杂东西我是写不出来的。
  6. 这道题目是比较开放的,前辈只是说一个元函数输入什么就返回另一个元函数,它再输入什么又返回另一个元函数。我做的不是很好,勉强的实现了部分。 基本是把之前的函数搬过来了。第一个返回来的很难称之为函数了,因为我已经把参数绑定了,第二个算是返回一个函数因为我开放了输入size的部分参数。 结果是这样子的
  7. 我整天都觉得我理解了,可是面对一道这么简单的问题我又茫然无头序:怎样使用SFINAE作一个元函数判断一个类型是否定义了type这个成员。结果我傻傻的尝试了几个小时还是作不出来,我知道用那个臭名昭著的yes/no来判断函数返回值,可是写不出来,而且我还一直尝试希望使用部分特殊化,可是总是不成功。这么一个常用的技巧我还是没有掌握。还是要参考例子 首先定义我们的Yes/No来用一个假函数来实验类型匹配,说起来容易,让我写又写不出来了。还是要别人提示才行 这个是我们用来测试的两个结构 测试一下 结果如下

七月二十一日等待变化等待机会

  1. 早上醒来才意识到昨天前辈的那个递归判断奇数的方法实际上是用来判断质数的,否则没有什么实际意义啊!这里我需要用到两个variable template我不知道能否减少一个,不过好像是不行吧,因为他们对应的是两重的循环?我对这个判断是否为质数的元函数还是比较满意的。我几乎可以肯定我的元函数有错,但是我一个早上脑子都处于失控状态怎么也转不过弯了,怎么改都是死循环,我甚至开始怀疑我的脑子进水了。 测试一下217是否是质数,这个我还真的心算不过来。答案当然是true了!
  2. 下午开完会我经过了午睡似乎脑子清楚了一些,似乎是很简单的一个问题,为什么我会搞不定呢?但是这个似乎是无意义的一个东西因为递归太多,你也只能解决900以内的数字的质数问题,目前我的编译器默认是900层递归,寻找质数能有更好的办法吗?这个似乎是无解的,因为元函数都是使用递归来计算,这种只有循环在runtime解决的问题不应该交给compile-time来解决。 这是一个最粗浅的原型,我先存一个结果 我的测试不知道要怎么才能发现失误,早上的问题就是测试太简单了。 当我想改进这个小函数想把它放到一个结构里以便计算sqrt(N)这样子我就可以减少很多的循环,这个平方根表达式肯定是不行的,我是打算使用结构里的constexpr static来把它当作常数,可是我遇到了另一个问题,就是结构里面定义的variable template不能递归定义。 就是以下这个不成立: 我使用了g++-10也是一样,看来这个是语法或者实现上的问题:
    错误:不允许显式模板参数列表
    ,因此我的元函数只能傻傻的算到输入值而不是停在sqrt处。
  3. 一个递归浪费了一天时间!
  4. 前辈出的作业题真的很不容易做啊:使用本章学到的循环代码书写方式编写一个元函数,输入一个类型数组,输出一个无符号整形数组,输出数组每个元素表达输入数组中相应的类型变量的大小。我结果把题目理解错了,要求的是类型数组,我却输入了实参数组,而且我没有使用循环,利用了variadic variable,根本文不对题。不过费了好大的劲儿! 这里我使用了一个initializer_list,并且使用了我不熟悉的sizeof(types)...得到了所有成员的size的一个数列,作为数组的初始化。这个方法对我来说好难的。 测试程序是这样子的 结果如下 注意这里字符串大小实际就是指针的大小8,因为64位OS,double也是8。
  5. 我把实参改成了型参,但是我依然不理解要怎么使用循环的方法,因为元函数的循环就是递归,而我的返回数组要怎么递归返回呢? 调用是差不多的 结果如下
  6. 那么我就尝试使用递归来作这个题目吧, 我实在是有些饿了,这个题目如果使用结构我真的不知道要怎么作了。 怎么调用呢? 结果如下,我太饿了。
  7. 最后一道题是使用分支短路逻辑,给定一个整数数列,判断是否存在值为1的元素,如果有返回true。我之所以使用函数而不是一个结构是因为只能用if判断if constexpr (sizeof...(tails)>0) 测试部分
  8. 第一章总共才26页,感觉好累啊。我原本以为我已经练过九阳神功,那么乾坤大挪移的修习应该很容易才对,没想到这么困难,真是战战兢兢啊

七月二十二日等待变化等待机会

  1. 早上突发奇想要实现一个平方根,其实和求质数也差不多,但是这里就遇到了前两天类似的递归不能停止的问题,现在我有些明白了,就是说递归的停止条件不能依赖于表达式的评估条件,原因是这个是元计算,是先把表达式解析如果没有停止条件的模板“特化”配合就会无限循环,我的理解是计算是在表达式解析之后才开始的,所以,一定要有模板的“特殊化”来终止递归。 测试一下,不过不能超过1800因为我的设置是递归不能超过900,所以,这个东西和求质数一样是一个没有多大意义的实验。 结果是29
  2. 在开始阅读第二章之前我有些忐忑,题目是我很不熟悉的policy之类的,对于tuple,我也是心有余悸。于是我决定跳过去一章先把关于深度学习的基本概念了解一下,这个是前辈的最精华的部分的解释,就是所有接触元编程的人都会有的困惑:如此高深的技术要怎么使用呢?它真的是一种传说中的屠龙术吗?

七月二十三日等待变化等待机会

  1. 注意:前方高能!我花了一早上在阅读前辈的“VarTypeDict”的设计思路,感到非常的钦佩,这个是对于高维度的仰慕与崇敬。其中的技巧与思路是比我高了不知多少维度,因为在一个0维维度生活的类型的生物永远无法理解生活在一维空间的线类型的生物可以拥有无限的点来做成线,而更不要说生活在二维空间的类型生物居然可以拥有无穷多线组成面,因为它本身对于线都无法理解更不要说面了,因此对于不可知来说它们对于点类型生物是一样的不可理解。高维度生物可以理解各种低维度生物的困惑与烦恼,因而他们的智慧带给他们的除了知识就是各种维度的烦恼与困惑,不像低维度的生物永远只有他们所能理解的低维度的烦恼。你愿意理解我作为一个低维度生物尝试想像高维度生物的对于低维度生物的烦恼的理解的烦恼的烦恼吗? 这个只是想要验证一下前辈的高级技巧中的一个部分,就是怎么样能够“预设”填充默认的模板参数。这个技巧是我所无法理解的。 以上是前辈的核心逻辑的一小部分,就是如何使用一个“空”类型NullParameter来填充一个模板类的模板参数。(这个我是做完实验才有些明白递归在干什么)下面是我们的实验需要的测试空模板类并测试它的类型 现在你想必理解我的困惑了吧,为什么Dummy的模板参数被填充了呢?是怎么被填充的呢?那个递归的“挤占”是怎样把NullParameter“推”到Dummy的模板参数里的?难道这个也成立吗? 答案是肯定的,就是给人一个印象我们在把模板参数硬性的“推”进了Dummy里。这个原理是依靠c++的哪一条法则呢? 我把我的疑惑写下来后就豁然理解了前辈的技巧:核心就在于递归的终止条件,因为我们使用一个计数器控制我们填充了多少次的NullParameter然后,在这里我们就“收获”了这些填充的NullParamter并把他们代入到Dummy里去,这个是多么的清晰,但是对于没有受过训练的低维度生物无法立刻透视其中的奥妙。这里的Types不是我们自己填充的吗?难归前辈对于很多部分都细心的做了解释,维度对于这个不做解释,因为作为高维度生物虽然能够理解低维度生物的困惑,但是它并不能完全准确的把握低维度生物的所有感知,这个就如刘慈欣在《诗云》里一样的那些大神们,除非他们愿意“降维体验”高维度生物永远无法完全准确的感受低维度生物的思想感受啊。换句话说就是前面的递归是在pack parameter,真正的结果是在递归终止条件里才体现出来,这个是很难被我立刻领悟到的,因为很多递归往往主要逻辑输出是在递归条件里,而这个递归只不过利用主要递归在作循环处理,而真正的结果出现在最后,于是我的注意力都被吸引到了理解递归逻辑怎么回事上去了。前辈专门指出c++标准里允许一个空typename...T被作为模板参数T...代入,这个是这个技巧的基础部分。
  2. 我对于前辈对于读者的要求大吃一惊,原来前辈没有给出VarTypeDict的实现核心。(也许在下载的代码部分我不想先去看我不看是对的,这个是闭卷考试啊!)这个要我自己去作练习,我很忐忑, 简直是倒吸一口凉气!那么就一步一步的从最最简单的函数来开始实现吧:这个是一个最最简单的函数就是给定一个类型数组返回给定的类型的相符的index。这么个简单的函数我居然折腾了好几个小时!首先,我想使用递归来实现template variable的方式,可是编译器为什么不允许模板参数有两个相同的类型呢?比如这个就报错?我打篮球的时候才领悟到所谓模板的型参就是要唯一性,它们只是你用到的实参的类型,是没有理由重复的!实参可以怎么的排列组合用到的型参就是不能出这个范围而已!如此基本的模板原理我居然不知道,真的是羞愧无比,白学了十几年啊。 最后我只能使用type_traits里的is_same来实现 测试程序。注意找不到的类型我故意返回sizeof...(TParameter)表示这个返回的index是无效的。
  3. 感觉现在我已经深入到了比我原来预期的还要复杂的领域,我的计划是彻底的被打乱。我本来希望在phoenix之后学习spirit,然后再去转战hana,可是意外的“找到”了前辈的这本“武林秘籍”,结果开始修习一种有实际法门的实用的武功,而不是之前的打坐运气的基础的内功心法。这个就是矛盾的地方,上乘内功心法修习极其困难,就算有心法口诀但是没有高手指点往往不明白修习的目的意义,这最需要和实际武功的“法门”运用诀窍来结合才能发挥上乘武功的威力。但是遇到既精通上乘内功又能充分将其结合运用实际武功的高手来指导你修习具体的一门功夫,而且还要耐心解答你的修习过程的具体疑惑的机遇实在是可望而不可及啊!尤其是修习上乘内功最容易走火入魔,因为内功的修习远远比外家功夫困难的多。想想看运行期的逻辑错误你用gdb一跟就明白了,编译期的错误就是语法错误,而模板的错误简直是不可理喻,往往一个错误苦思冥想一天也无解,如果没有高手指点经常会陷入困境内功修习往往半途而废。这个就是元编程如此困难的地方,因为它是一个抽象的高阶函数,往往实际并无法应用导致修习者对于它的理解会经常偏差。更困难的是现有的编译器原本就不是为了元编程所设计的,因为这部分的很多内容实际上编译器的一部分准备工作,想想看preprocessor和模板的千丝万缕的关系,就理解很多的编译器内部黑暗的细节要暴露出来往往还跟不同版本和实现的编译器相关了。最后一个很重要的部分是c++的11/14/17大发展,每一次都开启了元编程的新领域,而很多时候我们对于这些新特点的陌生导致很多的困扰。不熟悉c++11/14/17/20的新特点还是不要轻易尝试这个领域,否则就是没有内功底子而要强行修练高深武功的痛苦。毕竟名门正派严格培训出来的至少在c++基本语法功底有扎实的基础,比那些无师自通的野狐禅来的优越一些吧。
  4. 这个是前辈的一个相当核心的函数,他用了很多的笔墨来解释其中的细节,我基本上就是抄正楷式的学习这个技巧,它要求你把一个模板类的其中指定的一个类型进行替换然后返回新的类型,这个是非常好的一个范例告诉我们怎么和模板类来操作。 这个是我的测试,我曾经想用mpl::vector,但是想像看它其实不是一个容器,它实际上是一个编译期的类型的list的宏,所以,不要以为它是一个容器,因为容器实际上是一个运行期的对象吧?

七月二十四日等待变化等待机会

  1. 我越来越感到吃力,前辈的后记是一个最好的诠释:“方家休见笑,吾道本艰难。”
  2. 这个练习对于我来说也是要犯心脏病了,感觉是在走钢丝,当然对于行家里手也许不算什么,可是我却非常的忐忑。首先,对于传递boost::shared_ptr的数组的形式作阐述我总觉得很别扭,这个语法我始终转不过弯来。其次就是c++的新的&&的传参数我也很不熟悉,因为我要它是为了传递右值,可是我有一个左值,难道反而不行吗?于是我就都改成了传递引用,这个肯定限定了参数的选择。前辈把一系列的参数使用decay“矮化”为void*,那么你再重新取出他们的时候你要怎么还原他们的类型呢?当然明显的是因为它是一个模板函数,模板参数和实参里的数组值是一一对应的,所以,我的实验就是重现这个过程。或许这个是太国语明显了,可是作为使用者来说这个是必须的吧?Absolutely! 一个递归函数把实参和型参都提取出来然后还原并打印。 这个是我调用的函数 怎么装配实验数据呢? 运行的结果和你的期待是一样的吗?
  3. 早上打篮球的时候我又领悟了我之前的另一个错误,我一直不明白constexpr和if结合起来是一个类似于操作符一样的,而错误的以为constexpr后面跟表达式是一个语句,所以,我曾经想把constexpr用在?:这样的操作符里,这个是不可能的。因为if constexpr是一个操作符。这个也可以当作是每日一个点点滴滴的学习吧。虽然是c++11的基本,可是我几乎没有认真的阅读过大师的c++ programming language的那本c++11的大作,仅仅是因为信用卡的积分多了就去买了一本厚书来放在书架上充门面而已。
  4. 读我的笔记,当时我不知道怎样输出一个数组是输入类型的size的做法,我又想了一个更加简洁的做法。 其实我只是把函数改为了一个直接声明,我觉得这个更加的符合元函数的风格。声明也算函数。 这个是测试 运行结果
  5. 我又要补课了,因为我居然不理解move constructor,它的用意是什么?首先,一个临时变量往往不能被引用,这个是我承认不合理的地方,要明白rvalue vs lvalue的定义,我这一点居然没有答对,我以前一直以为lvalue就是能够修改,而rvalue就是不能被修改,这个貌似正确的直觉只知其然不知其所以然,这个和编译期的所谓人为的编程语言上的const之类的提示性无关,那个只是一些人为的提示,比如有程序员故意const_cast,这个并不代表它真的就是lvalue/rvalue的属性就能够改变,这里的定义才是关键
    An rvalue is an expression that does not have any memory address, and an lvalue is an expression with a memory address.
    。联系到如果是literal,它根本就是指令里内嵌的literal,怎么可能去改变呢?所以有没有内存地址才是区别lvalue/rvalue的本质。我居然连这个问题都答错了,恨不得打自己几个耳光!几乎所有的string literal实际上都是变成了elf里的一些literal string为什么我们不能引用它呢?比如string& str = "hello world";这个是每一个程序员都很郁闷lvalue reference指向literal这个从内存上就是错误的,因为后者没有地址可以引用。所以,只能使用rvalue reference。这个本来也没有问题,问题在于创建rvalue reference往往伴随着临时变量的生成,而c++11的move constructor可以把临时变量生成的过程“跳过去”(这个我也不确定是否准确,你是否需要为这个类定义move constructor才能实现呢?)
    明白了move constructor才能理解一点点的std::forward这个东西,我之前就是对这个理解有困难,现在才有一点点开窍了。这个是拷贝的例子 那么这个wrapper的返回值是什么呢?换句话说std::forward想要达到什么目的呢?我其实早已耳闻过c/c++里的forward的问题,就是说函数参数传递不能完美做到,但是都是似懂非懂,语焉不详,所谓的只闻其名,不知就里,是典型的老外那帮“English Programmer”的路数,说出来口若悬河,其实写下来不知所云。总之,自我批评结束,我现在的理解就是它能够最终传递一个参数的原本的类型。这里是一个更清楚的解说
    Don't let T&& fool you here - t is not an rvalue reference [2]. When it appears in a type-deducing context, T&& acquires a special meaning. When func is instantiated, T depends on whether the argument passed to func is an lvalue or an rvalue. If it's an lvalue of type U, T is deduced to U&. If it's an rvalue, T is deduced to U:
    总之这个问题是非常的复杂,根据这个资料连大师都犯了一个错误:
    And Bjarne Stroustrup has a mistake in the 4th edition of "The C++ Programming Language" when describing std::forward - forgetting to explicitly provide a template argument when calling it. This stuff is complex!
    那么从std::forward的代码来看它就是把参数的的reference去掉然后static_cast它为&&这个含义是什么呢?为什么这个就能够解决perfect forwarding的问题呢? 我觉得我的头脑已经开始混乱了,因为我开辟了太多的战线!以上的函数的传入参数的类型是T&&那么不管它怎么变形,经过 std::forward<T>(arg)它的返回值的类型也是T&&,那么从传递参数的角度看是不是就是一个“忠诚”?我的这个理解不知道对不对?

七月二十五日等待变化等待机会

  1. 早上起来检视我的笔记,我对于很多基本的语法又开始怀疑了。如何传递一个数组的引用是我就纠结过的事情,似乎我还是没有明白 我并不是第一个也绝不会是最后一个有这种疑惑的,如果你传递的是据说就是an array of references,而我们需要的是reference of an array 我觉得stackflow的解答很经典就摘抄如下。
    These three are different ways of declaring the same function. They're all treated as taking an int * parameter, you can pass any size array to them. This only accepts arrays of 100 integers. You can safely use sizeof on x This is parsed as an "array of references" - which isn't legal.
  2. 眼睛太酸涩了,爬山还是觉得眼睛酸涩。休息一天吧。下午沐浴午休醒来读毛主席诗词选,甚嗨。
  3. 对于c++11里引入的&&;作为参数传递的作用是怎么样子的呢? 我做了一个承接参数的小函数然后我们来测试接到的函数的参数类型如何,比如我现在就是在判断参数类型是string& 先对应的函数调用是这样子的
    实参声明 函数参数收到类型 评点
    这个是出乎我的意料的,就是说rvalue reference最终还是体现为reference而不是pass by value我现在开始开窍了,就是说这个&&只是表明constructor会是一个move,比较聪明而不是变量本身有什么特别的
    这个没有什么好奇怪的,声明是什么传递的就是什么
    这个也是出乎我的意料的,就是说这个也是rvalue reference最终也是体现为reference而不是pass by value,这个和上面的&&类似的
    这个没有什么好奇怪的,声明是引用传递的就是引用
    这个没有什么好奇怪的,声明是什么传递的就是什么
    这个是最关键的部分,就是如果是真的类似literal的temporary变量传递的绝对是by value
    这个也是值的关注的,就是不会自动转化,因为我们是模板所以传递的是数组常量,这个也是告诉我字符串literal的类型是怎么样的
    这个也是值的关注的,我曾经试图把s6声明成rvalue reference指向另一个rvalue reference,编译器不允许,就是说指向rvalue reference的只能是一个lvalue reference
    我把一个rvalue reference指向一个新常量,没有问题,这个也是稍稍的出乎我的意料,那么rvalue reference本身实际上是一个lvalue,对吗?我实验了s5=s1;这个是不成立的,编译器说rvalue reference 不能绑定lvalue reference
    这个是比较颠倒我的认知的,就是说一个rvalue reference变量本身其实就是一个lvalue reference,它本身没有任何限制,而“头上”顶着"const"的常引用变量反而不能再指向任何变量。比如这个也是被禁止了(难道这个基本的我也要质疑吗?我发现引入rvalue reference之后我对于一切都开始怀疑了)s5="world";所以,结论就是string&&变量没有什么特别的,就是聪明而已

七月二十六日等待变化等待机会

  1. 身体不适感觉体力不济。总结一下,目前的precompiled.h已经是一个很庞大的部分了,我没有想到它的.gch的预编译的结果文件居然接近一个G!这个实在是让我大吃一惊。同时我把前辈的VarTypeDict的代码下载在这里,一方面是我要多多学习,另一方面我自己的实现有什么问题编译不成功。要比较一下。 运行结果是这样子的

七月二十七日等待变化等待机会

  1. 元编程的debug实在是太困难了,我几乎就是照抄前辈的代码,自己做作业实现了一遍。结果一直卡在string literal的实现部分报错,我的几个utility函数的实现都是自己瞎捉摸的和前辈的有所不同但是测试也都是ok的,但是我的输出的字符串就不再是const char*而是变成了原始的const char[6],看上去好像我传递字符串作参数没有使用数组的方式传递,这个让我一度怀疑c++的move constructor是否有什么玄机,而且现在的新玩意居然可以把一个成员函数定义为&&的签名,类似与定义成员函数const这样的cv qualifier,让我感到非常的陌生,在参数类型上定义不就是达到指示使用move constructor来传递参数吗?成员函数有什么意义呢?c++11的move实在是太深奥了。最后我没有别的办法用眼睛比较代码始终看不出有什么区别,应该承认我的眼睛非常的差,最后没有办法就只好一个函数一个函数拷贝嗫呫前辈的大麦来替换我的代码这才找出了根源。我一直没有真正理解为什么在处理传入参数的类型的时候首先使用std::decay_t获得传入参数的类型的真正用意。这个就是围棋的所谓手筋,也就是最关键的而又往往不为初学者所重视的厉害招数。对于普通的类型往往问题不大,所谓的decay_t几乎是不变的,这个有一点点类似于我始终不理解的std::forward,到底什么时候需要引用,什么时候不需要。我的理解你要存储一个传入参数的拷贝的时候必须把它的引用去除掉,然后调用constructor制作一个拷贝,那么这两行代码就是手筋 之前我一直不确定是不是我的实现的这个小函数有问题,因为前辈的代码我有些看不太懂,当然他做了很多预防输入参数无效的验证,我没有 我简单的测试了一下是可以的。 最后我发现了我的一个简简单单的错误导致了我差不多两天的debug 而我因为没有理解decay传入参数类型的意义,结果是这样子 对于很多传入参数来说RawType和TVale是一样的,可是对于一个string literal传入的是一个数组的引用const char (&)[12]是不能在我们的内部重新创建拷贝的,要退化为const char*,这个处理如果没有decay_t是难以想象要怎么获得。而在调用new的时候使用std::forward来传递实参的奥妙我至今还无法完全窥究。 这里就是基本功,我很少真正的领悟shared_ptr的delleter的用法,而这里就是一个最好的实例,我通常就是使用reset但是从官方的定义来看我不确定是否这个是充分的。
    U* shall be implicitly convertible to T* (where T is shared_ptr's template parameter).
  2. 感觉太累了。
  3. policy部分非常的抽象,虽然理解起来不难,但是我感觉其中的应用比较抽象,我准备后面再学习。先复习一下虚拟继承的问题。这个我已经忘记了,以前只是记得有一个钻石继承其他就不知道了。 结果是什么呢?答案是B的方法,而且这个多重继承必须是virtual才行,否则编译器会报错。

七月二十八日等待变化等待机会

  1. 很多时候我以为我明白了,实际上我还是不懂。今天我认为我还是对于pch有误解,就是说如果我的编译指令仅仅是间接用到这个pch,有可能是不会触发吧。比如我的cpp文件里如果include了一个wrapper.h,而在这个wrapper.h里面再去include我的precompile.h那么在编译的指令里面没有明确的include到的头文件,gcc是不会去检查是否有.gch的,于是就达不到自动使用pch的目的。这个时候唯一的办法就是在编译命令里强迫使用-include precompiled.h这样子的“越俎代庖”式的做法。可是这样子在我的工作中似乎并不能减少多少编译时间,也许是头文件的使用部分并不是瓶颈吧。
  2. 我一直期望着前辈的“异形字典”能够自由的增加减少或者替换键值对子,后来才明白是不可能的,因为这个更准确的是一个参数传递器,就是说一次性的创建反复使用但是不可更改,因为返回的对象是一个在编译期的rvalue,这里我开始有些明白很多时候rvalue和编译期的类型有着千丝万缕的关系,因为他们是没有地址的,所以,constexpr的真正含义我之前一直很不理解,其实都是rvalue的意思。无地址的东西就是编译期啊!
  3. 今天心情很不好,不知道为什么常常的黯然神伤。youtube上的令人勾起伤心回忆的老歌。头条上一个视频突然问了一个我从来没有意识到的问题,为什么康熙没有直接把皇位传递给胤禩?唯一的解释就是老人对于权力的贪恋是到死的。任你多么的英明神武,是不是老了都会如此的贪恋权位不肯放手?可是我一个无用的蚂蚁为什么会为这八杆子打不着的事情操心?人的一生应该怎样度过呢?
  4. 读别人的博客也是一种不同的体验,天下之大,有多少聪明才俊人士,你甚至都不一定有时间去发现别人的聪明睿智的部分你就累垮了,人类的精力是太有限了。像这位大侠也有着从2003年积累到如今的历史,这些也许都可以成为将来的计算机考古程序的素材来了解我们经历过的时代,一个渺小而无足轻重的hobbit,一个并没有那么悲惨的命运去承载着拯救中土世界命运的魔戒,只不过生活在并非是什么乐土的霍比特人的遐迩之地度过一个悲摧的人生。作者介绍了另一个批判c++的网站什么FQA,我也没有什么精力去看。
  5. 生活在这个大瘟疫纪元爆发之年的人们是幸还是不幸呢?

七月二十九日等待变化等待机会

  1. Makefile里面有一个变量${MAKEFILE_LIST}代表文件名。所以这个是获得makefile路径的方法我对于firstword的作用不是很理解。

七月三十日等待变化等待机会

  1. 我觉得这个就是一个非常好的灵活运用的例子,我觉得我真的应该努力一下看看自己i能不能想到这个巧妙办法。起源是在于大量的例子里面人们开始把工作交给编译器而使用auto,那么到底一个函数的返回类型是什么呢?似乎没有人关心了,人们关心的就是一个形式逻辑的最终结果,彷佛脚本语言之类的“弱”类型的编程中人们不关心也不愿意深究是否有数据类型的区别。c++语言的创始大师之所以引入类型就是为了改进c语言的不安全性,因为c语言是如此的接近汇编机器语言以至于你可以访问任何地址而不需要明白它指向的是什么,所以大师认为类型是防止这种过于自由导致错误的办法,可是类型在c++里可以变得如此复杂,以至于人们使用过程中产生了过渡的困扰,好像当初制订法律是为了约束少数不法分子的危害大多数人利益的工具,但是随着法律条文变得如此复杂繁复最后成为庇护犯罪分子的手段。人们需要反本朔源到底类型是为了帮助人们编程还是束缚人们编程呢?原本可以让编译器去做的类型推理工作为什么让人去作,比如很多的和最终结果无关的中间变量的类型真的需要知道吗?这个是auto的强大,也是decltype的伟大。如今我希望抽丝帛兼学习想还原auto代表的函数类型究竟是什么?这个纯粹是一个虚假的问题,原因是为什么我需要知道?我只是想知道,结果遇到了一个decltype无法确定有重载的函数的地址的问题,本身我需要的是函数的返回类型,可是因为函数参数有重载阻止了我得到函数的返回类型是不是很好笑,因为大多数情况下重载的函数的返回类型是相同的,当然这个是惯例,谁也没法阻止二货写个奇怪的重载返回完全无关的类型,那么重载的逻辑就是个问题,为什么要重载而不是使用另外的函数名字?这个问题好解决吗?似乎吧,我很懊恼我没有努力去解决。 这位大侠使用这个巧妙的方法来“测试”函数的参数类型得到它的返回值,可以称之为“元函数”。原来的例子是使用了实参调用函数,而我仅仅需要返回值,所以只要参数型参就好了。 我之所以有这么奇怪的需求是源于学习使用chrono的时间函数,其中的概念挺复杂的,我懒得理解所以就想拷贝一些例子,但是想改改代码就遇到这个问题: 结果是挺有意思的
  2. 再次复习一遍decltyperesult_of的不同。前者对于函数或者表达式是比较好用的,当然这个需要编译器的版本支持。后者总是有一个callable的要求让我无所适从,更容易对于functor来使用。 声明一个functor,重载operator()返回值不同。不过const函数也许并不能称作重载吧? 那么使用decltype和result_of的情况略有不同
  3. 晚上又重看了一部老片子《Mr. Bean's Holiday》它的搞笑的剧情给了我新的认识,也许其中就是对目前国际局势的一种搞笑的预演,一个英国人在欧洲大陆的搅屎经历,完全偶然的事件早就了一个幻想中的圆满结局是不可能在现实中发生的,Bean作为一个搞笑的英国人到底为什么来到欧洲,仅仅是因为抽奖的幸运,英国并没有加入欧盟的动机,他的文化和目标和欧盟格格不入,甚至还不如同样是客人的俄罗斯还不受欧盟的欢迎。Bean就凭着一个法语字Oui和一个西班牙字gracias在欧洲游历,同样的一个美国导演只会一句法语Vive La France来到法国作导演,还有俄罗斯的电影评审,他们的习俗是如此的奇怪,和人热情交往的动作就是扇别人的耳光,这样的电影节会出什么样的作品呢?这个似乎和当前的国际局势有几分相似。一群不称职的领导者在不该担任的职位上瞎搞期望碰运气走出一条道路。英法俄美在欧陆相杀相斗的结局是什么呢?(电影里没有德国什么事是因为作为欧洲的主任他还没有到出头的时候)其中的一段法国乡下的桥段对于我们了解真实的法国有很多帮助,他就是一个农业国,可是照样过着不错的日子,将来就算美国从世界的舞台中央退下来作为农业国他也照样可以过着很好的日子。你还记得在英国绅士豆豆阴差阳错的搞笑播放非传统摄像机拍摄的片段的随机剪裁之后美国导演的对观众告白吗?每个人都对我说这条路走不通,可是各种因素交织在一起居然就成就了我,这个低能白痴靠着瞎搞糊弄观众的导演不就是川普吗?他明明就是瞎撞而观众以为他在创新,当然这种事情只能成功一次。

八月一日等待变化等待机会

  1. 对于一个小问题我做了一个简单的测试,就是说如果你声明一个模板类,那么调用他的成员函数你不得不使用模板型参来创建类的实例然后调用成员函数,他的缺点是什么呢?如果你把成员函数声明为模板函数的话,完全可以通过模板实参让编译器自己推理型参省却了规定参数类型的繁琐的步骤方便了调用,很多时候用户根本意识不到他在使用模板。 这个是声明的两个类Test1是一个普通的类只是成员函数是模板函数。 这个Test2类是一个模板类有一个普通的成员函数 但是调用就体现了区别,Test1比Test2好用的多了!注意这个常量字符串数组它的类型是多么的不容易啊:const char(&)[6] 结果是类似的

八月一日等待变化等待机会

  1. 其实工作中也早就遇到过类似的问题,就是前辈所阐述的两个类别的方式,一个是面向对象式的继承派生使用虚方法表可以实现动态多态。而另一个方式式使用所谓的tag或者traits等的标签化,这个范式是一种静态多态或者编译期多态。STL里有大量的例子,但是遇到真正的实际代码里,就遇到一个我反复遇到过的小细节,你要特殊化的时候往往需要一个无用的模板类型参数来“陪衬”,这个做法我始终比较困难理解,因为判断怎么才是模板“特殊化”我还是偶尔有些犯糊涂。 你一定需要一个typename TDummy因为期它的模板参数都不是一个真正的类型,有点类似常量的意思 然后我们才能把模板常参数来作“特殊化”,否则编译器会说不是一个合法的模板,因为我们没有模板“类型”参数 这个是测试

八月四日等待变化等待机会

  1. 就要踏上一段不明的路途。重温一下伟人的战斗檄文。
    小小寰球,有几个苍蝇碰壁。嗡嗡叫,几声凄厉,几声抽泣。蚂蚁缘槐夸大国,蚍蜉撼树谈何易。正西风落叶下长安,飞鸣镝。
    
    多少事,从来急;天地转,光阴迫。一万年太久,只争朝夕。四海翻腾云水怒,五洲震荡风雷激。要扫除一切害人虫,全无敌。
    拳法云:强不能久,弱不能守。中国要往何处去?

八月八日等待变化等待机会

  1. 打卡签到。早上偶然看到头条一篇关于efi/mbr的入门视频,在马桶上听了两分钟算是学习。我对于uefi一向不甚了了,因为工作中有用到但是始终不是很愿意了解(我不知道从什么时候建立的莫名其妙的印象它是微软扶持起来的就一直心怀抵触,不知道这个对不对,我连google验证的兴趣都没有),看来也许需要将来多学习一下,因为很客观的是更新bios也许可以有帮助,目前我的老笔记本从惠普下载的bios的是windows的可执行程序,也许用freedos可以吧?总之,我还没有找到在linux下更新bios的好办法。
  2. 我的自信心开始膨胀于是决定尝试专家类型的archlinux,的确,它的安装与配置都是面向有经验的用户,我自信是够格的。这里的介绍是很有帮助的,好就好在它让你意识到其实,安装archlinux完全不需要制作什么usb启动盘实际重启安装,因为如果你本来就有linux而想要在其他partition安装archlinux,完全可以使用chroot的方式安装。
    1. 首先,我下载了一个archlinux的文件系统,也就是所谓的"bootstrap",这个做法我原来在ubuntu也尝试过,实际上相当于ubuntu里所谓的自己重新“配置”或者“编译”,这里的编译当然不是编译源代码,只不过是所谓的bootstrap式的完全从零开始从服务器下载你的库而已。
    2. 所谓的arch-chroot实际上和chroot区别在于它帮你做了几个重要的mount
    3. 在bootstrap里的pacman的/etc/pacman.conf里有一个checkspace会阻止你安装库,要先注释掉。
    4. 那么在chroot以后网络不通怎么办呢?其实就是/etc/resolve.conf没有配置的原因,所以,我就先把它拷贝过去再chroot。
    5. 配置mirrorlist其实很必要,我自己手动修改默认的国家所在的服务器列表并不能检查其有效性,所以,pacman -Syy更新之后立即安装reflector是很必要的
    6. 这里忘记了一点就是我在这个bootstrap里chroot,很明显的需要设定pacman-key --init
    7. 接下去就是我把我想要安装的partition mount在mnt后开始安装:pacstrap /mnt base linux linux-firmware vim nano
    8. 设置archlinux需要设置什么呢?首先就是fstab,否则根本就没办法mount根目录,对吧?那么做了什么呢?其实就是/和swap的设置。
    9. 以下部分实际上必须要再次chroot才行!what?我已经在bootstrap的chroot里了,现在要再次进入我安装的archlinux的分区里及去作配置!
    10. locale的设置很重要,我一开始以为locale-gen会自动完成,结果后来在boot进入archlinux的图形界面后居然无法启动terminal,google才知道/etc/locale-gen里默认是所有的语言都被注释的,所以,要手动设定(也许我需要加参数--lang en_US.UTF-8吧?)所以,如果这一步没有成功,虽然默认locale设置了也无法正确启动terminal啊
    11. 下一步需要的是网络的设置,一个是/etc/hostname一个是/etc/hosts,前者在ubuntu里不让你设置,是其他程序帮你设置,在archlinux一切都要自己手动设置,所以,我在这里把自己的Hostname写下。后者是什么作用我其实一直不是很清楚
    12. 设定用户密码是必须的passwd
    13. 既然我的启动是在其他的linux(ubuntu)里控制的,比如第一个分区的mbr是在ubuntu分区里,其实我没有必要把grub安装在archlinux的分区里了。
    14. 图形系统的安装是一个大问题,首先是xorg,然后是gnome archlinux使用systemctl配置相关的服务
    15. 到此我就完成了archlinux的安装。的确使用archlinux绝对不适合于linux的初学者。而其中的学习曲线相当的陡峭,我一开始使用连pacman都不熟悉,比如搜索库的名字,pacman -Qs是当作regex,不然生硬的返回让我摸不着头脑。

八月九日等待变化等待机会

  1. 这么久以来我都一直避免学习基本的autoconf的原理,因为总是期待着一个项目自带的configure让我运行,今天在阅读Debian的bootstrap的时候才意识到这是一个很困难的事情,这个就是金庸小说里登天的绝技,要一只脚踩着另一只脚然后交替攀登直达天际。这是多么神奇的武功啊。所以,debootstrap是Debian的bootstrap,那么同样的的如何产生configure也是一种bootstrap,而autoconf/automake也是一个如此的困难工作(也许容易多了?)总之,我从一个困难的领域跌入另一个困难的领域还没有搞明白什么是什么?cdebootstrap难道是cross-build的意思?这个岂不是更难?也许在debian系统内“自举”还是要容易一些吧?我以前在ubuntu里的那个"debootstrap"似乎还是别人编译好的库的堆积,可能还不算是从源码开始?我还没有明白这个cdebootstrap究竟from scratch是真的从无到有吗?
  2. 我下载了不明所以然的cdebootstrap的源代码,结果不知道要怎样产生configure,这里的autoconf讲解非常的有价值
    libtoolize --force
    aclocal
    autoheader
    automake --force-missing --add-missing
    autoconf
    ./configure
  3. 这是一个相当复杂的过程,我需要手动编译真的吗?,实际上在ubuntu里有着几乎所有的debian的库,所以,我安装标准的cdebootstrap或者它的所谓的cdebootstrap-static我还没有看到两者的区别,因为下载后者的源代码似乎和前者没有区别?总之,我接下去先按照manpage的说明下载代码 这个时候我遇到了这个错误 我找到这位大侠的论断,可是没有人去修改这个明显的错误,我只好自己手动编译libdebian-installer的源代码。
  4. 结果确实是惊人的!我使用这个“bootstrap”工具就基本上生成了Debian,当然我还没有chroot进入去作进一步的编译配置,但是,基本上能够作cross-build还有什么你不能指望的呢?

八月十日等待变化等待机会

  1. 感觉还是比较混乱,我现在是正确的设定了我的git的hooks/post-update可是为什么在remote我会看到status显示我刚刚才commit的change呢?这个是我的git一个流程
    1. 首先把我的remote指向我的待上传的remote: git remote set-url origin /BigDisk/diabloforum/public_html/myprojects/myLibS3/repo.git
    2. staging/commit之后push到remote:git push origin
    3. 这里我就不是很清楚是否我需要在remote作:mygit update-server-info
    4. 结果我在remote发现之前推送的还是显示出来?mygit status为什么我需要取消remote的,为什么我需要reset HEAD?
    5. 我不去理会它自己去clone确实看到remote是反映了我最新的更新,可是为什么remote会表现出来这个现象呢?
  2. 这里的例子我还无法真正领悟。比如我是使用cdebootstrap创建了rootfs,那么我在chroot之前似乎不需要再mount /proc,/sys,/dev啦?当然啊我仅仅是使用apt来配置,似乎不太需要?不过openssh还是对于openjdk和runlevel有要求,所以,我想还是真正boot进Debian来配置吧。看来还是需要配置三个主力文件夹/proc/sys/dev才能chroot来配置openssh-server
  3. archlinux据说是专家的玩意,可是,我感觉我有些不习惯,主要是这么多年都是用ubuntu,感觉还是Debian比较熟悉。所以,我还是选择debian吧。
  4. 在chroot前mount的三大主力要如何安全撤退呢?当我umount dev sys的时候总是抱怨busy,怎么办?这位大侠给出了正确的解答!
  5. 在安装kernel的过程中抱怨firmware的缺失。这个很简单的,我在http://ftp.debian.org/debian/dists/buster/看到它只有contrib和non-free,这个和Ubuntu不太一样,于是就添加了然后安装了一大堆的固件。

八月十一日等待变化等待机会

  1. 我习惯于放一个live iso image以便在系统无法启动时候作备份,比如我昨天在我的debian的rootfs里chroot并安装grub2,我认为我把它安装在另一个分区不会影响我的主系统的grub2安装,我的概念里grub就是写一个mbr在第一个设备的第一个分区的第一磁道的512大小,那么我的主分区已有的mbr不会被grub2去费心的抹去吧?何况我还是在chroot里似乎不一定能够访问所有的设备?否则chroot不是太可怕了吗?还有什么sandbox的安全性?我一直认为本质上kubernete之类的时髦玩意不过是chroot的升级版因此公司叫我去学习我始终没有看一分钟。现在看来至少在chroot里我也把整个的grub2系统都改变了,我猜想一定有一个类似devmap这类的东西能够导引启动区到我安装grub的分区而不是盲目的寻找第一个分区。否则就没有意义了。总之,我感觉需要一个debian的livecd iso来能够保证启动救助。这个是改动似乎成功的grub menu,但是我等待很久没有进入菜单,至少loopback是正确的。 不过话说回来,这个说不定是iso的本身的问题,因为我尝试使用qemu模拟也是黑屏,也许能够说明问题本身吧?
  2. 另一个问题是在debian设定wifi的问题,如果没有图形界面就要使用network-manager的nmcli,这里我摘抄下命令。
  3. 我折腾了快一天的debian。我的看法是,这个似乎不值得,因为ubuntu就是脱胎于debian,两者本质上没有区别,而前者修改了很多后者的明显的问题,比如,我一开始震惊的发现默认的shell是dash而不是bash,折腾了我很久才改过来 可是修改locale语言发现始终不成功才发现有在/etc/profile里有定义LANG。这类问题多如牛毛,比如原本我以为是gnome-terminal的问题因为completion和arrow key/tab key功能都没有了,结果发现原来是因为bash没有成为默认的shell,因为/bin/sh指向了dash,可是/etc/passwd里明确说我的shell是/bin/bash,这个怎么说?再比如我发现很多命令都在which里找不到,也就是说tab key的completion不起作用,这个是在设定bash之后的部分命令的问题,结果发现他们都是/usr/sbin里的命令,之后才明白这个和$PATH环境变量息息相关,可是这个变量在/etc/profile里针对root和非root用户区别设定,后者统统不包含/usr/sbin和/usr/local/sbin,这里面的设计我不明白,贸然修改是问题,我虽然也可以在~/.bashrc里面添加,可是毕竟是一个问题。再比如firefox居然不能启动说是profile不能访问,让我很困惑,后来才发现是~/.mozilla下是profile存储的地方,可是访问权限就是我自己,难道firefox是以另一个用户运行?着了好久才发现原来还有一个~/.cache的目录存储临时的信息,而我也许在开始的时候以root运行过firefox吧,结果我自己的.cache居然是root的,难怪!而设定中文输入法更加的复杂,居然导致死机,我彻底放弃了,因为googlepinyin使用fcitx,而这个步骤居然有问题。总而言之,我觉得重新折腾debian完全没有意义,再比如我的无线网卡8821ce需要自己编译dkms的驱动,这一点并没有在debian上有什么比ubuntu更好的地方。我获得的帮助比ubuntu更少,得到什么呢?所谓稳定是否理解为不愿意编程改代码的非开发人员的评价?同出一个源头的ubuntu的稳定依靠的也是debian啊,更新的慢就代表稳定吗?你如果需要用到那个软件,如果debian认为不稳定,那么就不提供,也就是说你自己去作ubuntu的工作,而debian的任何改进同样会立刻被ubuntu采用,我看不出自己使用debian究竟有什么优越之处。所以,我还是用Ubuntu吧,遇到软件版本不匹配的问题,最好的办法是自己编译源码,因为我遇到的大部分版本需要降级的问题大多数是开发头文件的部分。 这个设置googlepinyin的办法我在ubuntu上有成功,但是在debian有问题,而且还导致死机。我实在是不想再折腾了。
    1. Login as root (This is important, don’t do it with non-root user)
    2. Run the following command, scroll all the way down to add Chinese locales, e.g. zh_CN.UTF-8 etc.
      dpkg-reconfigure locales
      
    3. Run the following command to install the required packages
      apt install fcitx fcitx-pinyin fcitx-googlepinyin
      
    4. Run “im-config” and change it to “fcitx”, reboot the server
    5. Run “fcitx-configtool” after login, click “+” button to add new input method. In the popup selection box uncheck “Only Show Current Language” and then search for “google” to add Google Pinyin”.
    6. In “Global Config” tab, choose the shortcut keys for switching input methods and also add Google Pinyin input method
  4. 现在回过头来看ubuntu就明白我刚才遇到的问题是怎么解决的,/etc/environment里统一设定$PATH:是啊,有什么必要区别root用户设定特殊的PATH?

八月十二日等待变化等待机会

  1. 我的启动很慢查看log看到所谓的"FS-Cache: Duplicate cookie detected"的信息,google才看到可能是和nfs-client的错误有关,我想把无效的/etc/fstab里的nfs mount去掉也许可以消除吧?
  2. 读我几年前的笔记依然收获良多,因为人的记忆力太差了,我总是屡屡犯同样的错误。比如我前几天编译一个库然后安装在了/usr/local/lib下居然无法链接,我就是忘记了ldconfig要重新生成cache,这种错误反反复复的犯。

八月十三日等待变化等待机会

  1. 我的update-manager总是crash,而我把能够重新安装的包全都装了一遍依然如此,这里的情形和我一模一样,我看到那位有经验有耐心的大侠一步一步排查直到最后发现是自己编译的openssl是罪魁祸首,我真的是好感动啊!
  2. 这里有一位大侠的脚本重新安装所有的库文件值的收藏。
  3. 我因为一怒之下搞坏了wine,结果重新安装总是有依赖问题。找了很久才明白要使用古老的发行版里的库,就是说在bionic里不行要在xenial

八月十七日等待变化等待机会

  1. 整天要用到rysnc遇到文件夹名字有空格的问题,可以使用--proect-args: rsync --protect-args --size-only 192.168.2.20:"/home/nick/.wine/drive_c/GOG Games/HoMM 3 Complete/Maps/*" ./
  2. 我时常的要发现另一台笔记本的ip,于是扫描ssh端口也许是最快的:其他的比如ip neighbour show或者nslookup或者arp似乎都是从之前的arp cache里提取的。那么我怎样更新我的arp cache呢?所以使用arping -U myIp同时通过tcpdump来观察

八月十九日等待变化等待机会

  1. 关于ubuntu的菜单我被折磨了很久,以前的unity的菜单很好理解基本上就是自己去编辑/usr/share/applications/下的.desktop,当然有一个每次都运行的程序去读取并分类这些菜单文件把他们显示在菜单里,但是现在改成了launcher我就不明白它是怎么个机制了,因为windows下的菜单似乎还包含了桌面的shortcut之类的,总之在我下载了menulibre一切才变得容易。
  2. 这个时候遇到了另外一个问题,就是menulibre有时候要编辑的菜单是系统的需要root权限,而我启动它的时候是我自己的用户,那么怎样能够提示我升级为root来运行呢?这个在以前也是很清楚的就是使用gksudo,可是ubuntu18.04把这个又改掉了要使用什么pkexec,这个让我感到很困惑,我一开始对此将信将疑因为gparted的desktop里没有调用pkexec,我错误的以为有什么玄机,于是用strace /usr/sbin/gparted然后才知道自己很愚蠢居然没有意识到/usr/sbin下面大部分是脚本,只不过检查了一下当前用户不是root就去呼叫pkexec而已。啥也不是!
  3. 但是pkexec之后是root用户很多环境变量都和我的用户不一致,所以,要编辑一个简单的文件都需要给出全路径,以及设定display等等
  4. 但是,我又一次错了,这一次这个所谓的menulibre居然又是一个脚本而且似乎是python吧,似乎需要更多的设定。在pkexec里看不到menulibre的运行,我决定直接使用sudo才发现它拒绝在root下运行:sudo menulibre -b -v
  5. 我要尝试一个最最简单的asio的server/client我尝试了几个都因为嫌麻烦不愿意制作证书和私钥文件而注释了server端代码,结果始终都出错。最后只好这样制作证书 这个nghttp2库似乎可以试一下,其中还有一个细节就是对于context的set_password_callback如果不使用boost::bind的话编译有问题。这里的get_password是一个简单的返回密码为string的自由函数。

八月二十二日等待变化等待机会

  1. 工作中用到wsdl但是始终不理解其中的namespace的问题。

八月二十九日等待变化等待机会

  1. 人的头脑中时常有一些莫名其妙产生的误区,比如,对于老版本的Linux如centos/rhel的较低版本是工作中常用的,如果我想尝试一些新版本的boost的一些新特性,那些老版本的编译器是否支持呢?比如我的ubuntu18.04上默认的boost是1.65,gcc是7.5,这个是一个很高的版本了,因为centos6.8上才gcc4.8,那么我突然想尝试boost1.72上的beast会怎么样呢?这里就是误区了。一个是boost的版本问题,一个是gcc的版本问题,两者有什么会限制你在老的linux版本的应用呢?实际上boost的出现就是对于gcc新功能的推动,很多新标准都是在编译器固化之前的尝试,很多心功能不一定需要编译器新版本的支持,而使用老版本的gcc来编译新版本的boost原本就是一个支持的选项,因为boost并不是依赖于编译器新功能的支持的,(至少很多新功能是首先在boost上实践的),所以,我的理解是尽量使用boost而不是使用原生的编译器的新功能是最有利于移植的。这个表可以经常性的访问看看是否有些语法上的“硬”支持必须用编译器来解决的而不是某些“feature”可以依靠boost的库来解决的。
  2. 在上面那个表里有一个c++11的所谓Right angle brackets的条目吸引了我,之后就看到了这个令人泪目的测试,说是测试的原因是我想在我的笔记本电量只有11%状态下解决它,不靠编译器判断结果是对于阅读代码的最高级检验的方法之一。 以下就是我摘抄的代码测试
    #include <iostream>
    template<int I> struct X {
      static int const c = 2;
    };
    template<> struct X<0> {
      typedef int c;
    };
    template<typename T> struct Y {
      static int const c = 3;
    };
    static int const c = 4;
    int main() {
      std::cout << (Y<X<1> >::c >::c>::c) << '\n';
      std::cout << (Y<X< 1>>::c >::c>::c) << '\n';
    }
    
    这段代码的解读的确是一个考验人的难题:
    最最核心的问题在于界定Y的边界,就是说它的型参是什么?
    第一个是明显的因为它的型参是X<1>
    那么随后的第一个c就是Y的成员变量是3,随后的两个::c指的都是全局变量4,那么他们之间的<只不过是比较操作符:3<4返回0;0<4返回0。
    第二个要注意到1>>::c是右移操作符所以是0
    那么Y的型参是什么呢?是int因为X的特殊化在参数为零的时候的成员类型c是int,所以,第三个c是Y的成员变量3
    这个例子的核心教育意义是什么?就是说我们要特别防止某些c程序员,尤其是图形底层开发的程序员写c++代码的坏习惯把他们用惯了left/right-shift带到各个地方的毛病,(这个从效率的角度看是优越的,因为它比乘法/除法来的效率高,可是从人情出发究竟是在写模板类型里出现模板参数的双bracket的机会多还是某些为了作乘除法而使用left/right-shift的机会多?)教训也就是说不要去考虑操作符的优先级对于left/right-shift一律统统加挂号不与其他操作符合并解释!换言之,新版编译器会“无视”你的右移操作符而尝试把它当作两个连续的尖挂号,所以,写图形引擎代码的程序员学习c++的要小心了。

八月三十日等待变化等待机会

  1. c++支持的表真的是一个很大的资源库,因为很多精微奥妙的问题都是在这里被提出并建议解决的,这个过程就是一个实践到理论,又从理论到实践的过程。首先,探路者在boost上分享自己的心得新作,大家评点应用学习提高反过来又返哺贡献并发现其中的不足并提供相应的解决办法。这个过程中有些是制度(就是c++语法绕不过去的坎,或者能workaround但是耗费很大并有局限等等)上的桎捁,反抗就此产生,激进的分子有些反出c++语言另创门派,而改革派则提出相应的建议让建制派讨论采纳形成增补条例成为法律,然后gcc/objective c的实现者们等等实权派则相应的根据法律有选择的实现增补的条例。这样的过程像极了人类的各种各样的政治体制,只不过在人类的各种政治架构中权力的集中程度不同,有所谓的全民投票选举制度,而这个相应的如果要在c++社会里实现一人一票每隔四年选举所谓总统或者每隔两年选举众议员,或者6年选举参议员是根本行不通的,原因很简单,在一个专业的社会里需要的是稳定与秩序,民意如流水,而且真理始终掌握在少数人手里,那种靠暴民投票决定前途的社会是没有前途的。想像看吧,c++社会的底层是金字塔的基座是大多数的初学者,难道要以初学者在还没有接触过那么复杂问题之前就以他们的好恶理解来决定语言的走向吗?小团体如此,国家社会更是如此,单凭这一点我们就可以预言美国和西方的衰落是不可避免的。语言和预言在中文拼音里是谐音,似乎预示了其中的潜在的相关性。
  2. 每次写boost::regex的代码我都几乎把之前的忘得干干净净,所以,
    我想在这里存一个范例
    首先,最好是把文件都读取到string里,这个当然不是必须的,同样的使用ifstream之类的iterator应该也可以,可是总是有些麻烦的地方,因为match_results可能的指针不一定能够回朔吧?
    所以,这个和regex毫无关系的东西,可是我也每次都要重新google
    其次,是关于使用regex_search的搜索的模式可以成为一个“模式”
    而关于regex的lazy的问题,我每次都忘记,其实,对于literal的match不存在什么模糊概念,这个lazy一定是你使用了.+这类的万能表达式,那么如何防止"longest match"这类“追求完美”的行为发生呢?在.+后面加上?就是提醒regex“你可以偷懒啊,找到一个就给我返回啊!”
  3. 我本来是依靠看我的日记文件大小来看我那一年的勤奋程度,结果我被骗了因为2017年的日记里都是html的无用的tag我花了不少时间把其中的无意义的tag都清理了一下。就是使用boost::regex来匹配,同时顺便学习到了boost::format的使用,据说是比sprintf优越的东西。

九月二日等待变化等待机会

  1. 对于服务器的证书的制作是一个反反复复的遇到的问题,我总是忘记这one-liner(实际上这个也是boost::beast的做法,beast是一个比较新的库,)而制作dh的证书是我所不熟悉的也许不是必须的? 检查证书: openssl x509 -in ./cert.pem -text
  2. 对于获取服务器的thumbprint这个也是一个有用的工具,工作中注册一个vcenter的plugin需要用到这个,文档中用了一些windows上的工具很烦人,我只能用openssl

九月三日等待变化等待机会

  1. 工作中遇到一个无法解析的hostname的问题,大概是一个vcenter的证书里设定了它的hostname但是并没有在相应的dns服务器上注册,这个都是我的擦想,总之,我使用浏览器用ip登陆它总要返回它的hostname的url,所以,最后在/etc/hosts里设定dns解析服务器是没有用的只能把它的实际的ip和hostname并列设定。

九月四日等待变化等待机会

  1. 对于旧的日记因为以前使用有毛病的编辑器有大量的垃圾html tag,现在使用regex来去除,仿佛是一种考古的修复。 对于嵌套的regex我还是比较疑惑,比如我不小心做了这个糟糕的表达式 那么对于这样子的输入结果是什么呢? 注意到外围的挂号+是一个greedy的做法,那么内层的表达式是灵活的或者1或者2个。我做了一个测试来检验结果 结果是这样子的 注意到了吗,究竟为什么内外两层的表达式结果不一样呢?因为外层是greedy的+所以总是得到最的结果,所以是两个,(注意后面的两组是因为空格导致比对结束所以才会有三次结果)。而内层是长度1或者2我觉得这个等同于non-greedy,就是立刻返回的意思。

九月七日等待变化等待机会

  1. 有时候这种老问题(old new story)依然值得再次强调,比如我需要编译新版的boost的库,链接的时候默认当然是动态库,我不愿意安装新版和旧版的boost,那么必须要指定链接和运行期的路径,于是我想使用静态库链接,但是我以前使用链接选项似乎不成功,我的猜想是这个必须在链接的时候才有用吧,比如这个就不行 也许我应该先把所有的.cpp文件编译成.o然后再来链接也许就可以,只不过我实在是失去了耐性,于是直接把.a当作.o文件一起编译更省事

九月九日等待变化等待机会

  1. 对于继承自enable_shared_from_this的类,我感到无所适从, 注意到了这个=这个是经过了重载, 而我自己一开始使用new去自己分配而不是使用make_shared似乎有问题。我已经开始混乱了,原则就是不要随意拷贝吧?另一个低级错误就是我居然忘了异步调用必须等待线程join后才有结果,结果在gdb里看到有数据,实际运行总是空让我发狂了好久,看来功夫一天撂下就生疏了。
  2. 似乎我自己编译的版本里并没有any_io_executor.hpp这个文件,范例代码里总是引用它,我也不想去深究了,就采用了system_executor的例子,总之beast是一个相当不容易驾驭的野兽,我的轻敌导致了我的痛苦。

九月十日等待变化等待机会

  1. 在使用property_tree转化json的时候遇到了不少的问题,这些都是boost实现不完美的地方,比如对于非string的类型也加了引号,这个问题解决起来很麻烦。再比如top-level的array它也不支持,让人很烦恼。

九月十一日等待变化等待机会

  1. 对于property_tree有很多的学习的地方,尽管工作中已经使用了很多,可是我依旧是一知半解,或许也是记忆的缺失。总之学而时习之不亦乐乎?
  2. 这篇stackoverflow上的组文堪称经典,非细品不足以明白其中的精微奥妙。
    其实很多是json的write的不足的问题,这个和ptree的追求效率有关,我以为ptree没有能力记录数组类型,或者说这个不是它的义务。
    对于ptree::json_parser::write_json的改造,我以为是可行的。
    我觉得这个translator是只有你在填充数据才会起作用,对于我们操作数据比如比较来说应该不会有影响,因为都会经过转换,所以除了字符串之外额外并不需要添加引号。不过我做了一点点改进,就是说完全忽略escape quote不是一个好办法,因为如果字符串里有quote的话那是一定要escape的,只不过我是在put的时候就先对它进行了escape
    其次是关于ptree不支持顶层array的问题,其实这个是伪命题,因为真正不支持的又是write_json
    同样的,按照大侠说的

    This only happens when the array is the root element. The boost ptree writer treats all root elements as objects - never arrays or values. This is caused by the following line in boost/propert_tree/detail/json_parser_writer.hpp

    else if (indent > 0 && pt.count(Str()) == pt.size())

    Getting rid of the "indent > 0 &&" will allow it to write arrays correctly.


九月二十五日等待变化等待机会

  1. 我有上联求对:中心为忠,中兴无忠心难中兴。
  2. 在《Warcraft》里,人类国王和蛮族首领的对话在我的解读下成了中美两国的元首的心灵对答:
    习:你是否打算让中美两国回到过去和平相处的状态。
    川:中国的崛起使得美国正在失去对世界的霸权统治。
    习:中国的崛起不是美国衰落的原因,战争不能解决美国自身的问题。
    川:对于美国来说,战争能够解决美国所有的问题。

九月三十日等待变化等待机会

  1. 关于boost里使用gzip进行压缩与解压缩的例子是这样子的
  2. beast归档,其中有很多可以参考的地方就是使用boost/beast来创建http server,以及client,对于ptree转换json是我对于boost的一个大的遗憾,因此参考网络的很多修改,只好自己作了一点点workaround。

十月七日等待变化等待机会

  1. 我想实验一下ext2fs的superblock结构,但是又不想在debug中使用sudo,看到这个setfacl似乎稳妥一些,至少比使用setcap更好一些,因为我以前使用它感到太复杂了,尽管是更好的选择。 查看facl使用getfacl

十月八日等待变化等待机会

  1. 很多时候都是一个基本常识,我居然去读/dev/sda来查看superblock,这个是明显的错误,因为文件系统是建立在一个个分区的,sda是一个设备而已,所以,我应该去读sda1。
  2. 反反复复的我需要使用cout输出hex,我始终忘记了cout对于char是在基类里重载过了,所以,cout<<hex是影响不到输出参数是byte/char的类型的,也就是为什么我始终需要把byte强制转换为int的原因。这个是cout当初设计使然无法逾越的。至于说hex的大小写输出是另外的showcase/uppercase/nouppercase来控制的。
  3. 对于c++的for loop我觉得如此的陌生,战战兢兢的把一个array当作一个range来使用,心里惴惴不安 为什么可以这样子呢?似乎这个是一个how to program c++的hello world,难道我学了快二十年竟然又要回幼儿园重新牙牙学语不成?这是因为range,对于支持成员函数begin/end的(我依稀记得这个是meta programming里一个高级的enable_if去查看参数是否是一个类并且有成员函数begin/end的问题,就是说编译期的判断),当然std::begin/std::end本来就是作这个尝试参数是否支持成员函数begin/end的作用。那么对于数组怎么办呢?你需要元编程吗?当然不用!你使用模板参数特例化处理数组啊!这个是多么浅显的道理!在我没有阅读meta programming之前对于这个hello world的for loop我肯定认为这个是编译器语法的创新,但是天知道c++11/17之类的是否真的有去修改语法?当然修改语法是最简单的,可以加快编译速度。可是使用boost就是获得兼容性的优势,你可以尽量的依靠低版本的编译器享受高级的语法功能。
  4. 想到这里我记起来最近看到的youtube上c++大牛的关于meta programming的讲座,其中一个经典的问题是:不依靠编译器你能否判断一个类型是否为class/struct?答案是肯定的,因为可以使用成员函数指针,但是无解的问题是你是否能够区分class和union呢?答案是否定的,不依赖编译器的is_union之类的支持,这个是不可能的任务。但是单单靠成员函数指针来判断已经是高难度的并非普通程序员所能熟练掌握的吧?还是使用编译器的is_class之类的吧。
  5. boost的crc是这样子的,我想验证一下ext2文件系统的superblock里的checksum,不过看样子这个是ext3/4才支持的,因为我实验的puppylinux的分区,我有意识的使用纯粹的ext2所以得不到的。 其中crc的成员函数operator()会返回结果,这个很贴心。
  6. 而我尝试练习的是dumpe2fs,所以,我完全可以去参考它的代码因为打印输出是一个很繁琐的事情,虽然是学习文件系统的最佳途径。

十月九日等待变化等待机会

虽然我觉得我对于ext2文件系统并不是一无所知,可是是到临头依然感到不明所以然,其中的细节也许从来就没有了解过。学习一下是有好处的,而结合dumpe2fs来学习是最有效的途径。
  1. 看dumpe2fs的代码还是有些难度的,因为你对于文件系统的规范的不熟悉,等于是通过代码来学习的过程,比如superblock如果是sparse的情况它是由一个superblock里两个byte的一个field来决定的,这个在ext2当然是扩展的不用的部分。那么不是sparse的呢?看代码就比较困惑了,反而是规范一句话就明白了。
    The first version of ext2 (revision 0) stores a copy at the start of every block group, along with backups of the group descriptor block(s). Because this can consume a considerable amount of space for large filesystems, later revisions can optionally reduce the number of backup copies by only putting backups in specific groups (this is the sparse superblock feature). The groups chosen are 0, 1 and powers of 3, 5 and 7.
    但是不明究理的我误以为sparse和sparse2是一回事,以上讲的是sparse而不是sparse2,sparse是在一个superblock的s_feature_ro_compat里设定的:
    Constant Name Value Description
    EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER 0x0001 Sparse Superblock
    EXT2_FEATURE_RO_COMPAT_LARGE_FILE 0x0002 Large file support, 64-bit file size
    EXT2_FEATURE_RO_COMPAT_BTREE_DIR 0x0004 Binary tree sorted directory files
    而这个sparse2是只有ext4版本里使用ext2里旧的s_feature_compat定义的#define EXT4_FEATURE_COMPAT_SPARSE_SUPER2 0x0200。我可以自问的是这个是“兼容”的吗?这个兼容的意思更多的是哲学问题而不是程序员的问题。就是说你首先要考虑它是否是ext4否则你按照ext2你会发现你找不到superblock的backup,也就是说文件系统corrupted后ext2的程序无法恢复。当然这个兼容是一个哲学问题。我更关心的是一个细枝末节的问题,就是如何判断一个数是3,5,7的power?这一小段代码应该不难想到吧?int test_root(unsigned int a, unsigned int b),其中a是要检验的数b是3或者5或者7。但是当我没有读过以上的规范时候这个代码还是让我迷惑了,因为从寻找power3,5,7的角度来看反而立刻就明白。反倒是从代码本身我被吓了一跳还以为是什么欧几里得算法的什么变种。(真是荒唐)
  2. 想要说你真正理解了ext2文件系统,那么一个简单的试金石就是这张表,请解释为什么如此?
    Upper Limits 1KiB 2KiB 4KiB 8KiB
    file system blocks 2,147,483,647 2,147,483,647 2,147,483,647 2,147,483,647
    blocks per block group 8,192 16,384 32,768 65,536
    inodes per block group 8,192 16,384 32,768 65,536
    bytes per block group 8,388,608 (8MiB) 33,554,432 (32MiB) 134,217,728 (128MiB) 536,870,912 (512MiB)
    file system size (real) 4,398,046,509,056 (4TiB) 8,796,093,018,112 (8TiB) 17,592,186,036,224 (16TiB) 35,184,372,080,640 (32TiB)
    file system size (Linux) 2,199,023,254,528 (2TiB) [a] 8,796,093,018,112 (8TiB) 17,592,186,036,224 (16TiB) 35,184,372,080,640 (32TiB)
    blocks per file 16,843,020 134,217,728 1,074,791,436 8,594,130,956
    file size (real) 17,247,252,480 (16GiB) 274,877,906,944 (256GiB) 2,199,023,255,552 (2TiB) 2,199,023,255,552 (2TiB)
    file size (Linux 2.6.28) 17,247,252,480 (16GiB) 274,877,906,944 (256GiB) 2,199,023,255,552 (2TiB) 2,199,023,255,552 (2TiB)
    • 为什么总的block number就是2,147,483,647呢?你当然可以很快回答因为superblock里表达s_blocks_count是一个32位的整数只能如此,除非你修改文件系统的ext2的定义?比如使用64位的整数?
    • 那么blocksize由什么决定的呢?难道是由操作系统的内存块大小决定的吗?swap是怎么工作的?虚拟内存是如何把内存页存在磁盘中的?这和/kernel的pagesize是无直接联系的,磁盘对齐应该只是性能问题并非必须吧?我想起来工作中HNAS的blocksize纯粹是一个人为选择的参数,其优劣仅仅在于文件系统最大支持的磁盘大小以及适应小文件的磁盘浪费的问题。难道这里不就是云存储需要定义自己的文件系统支持file system size (real)file system size (real)巨大文件系统的原因吗?我觉得如果使用64位数字来代表blocknumber也许就够用了?而blocksize的影响是比较小的。当然磁盘寻址不知道要怎么支持64位寻址?也许早就支持了?我脑子又糊涂了,block number和寻址无关。
    • blocks per block group的最大值我觉得是由于使用一个block来存储blockbitmap决定的。比如1k block的最多的bitmap就是1024*8=8192,类似的inodes per block group也是这个原因。
    • bytes per block group应该是最好理解的,因为blocks per block group和blocksize的乘积就是这么得来的
    • file system size (real)实际上就是blocksize和总的blocknumber的乘积。所以,这个也没有什么可奇怪。
    • blocks per file是由三级指针决定的,这个是上计算机课必讲的内容。不过如果文件系统使用64位来作block的指针的话这个数字也会不同,比如一级指针原本是32位作block定位,1k block的容纳的blocknumber是1024/4=256,二级指针256*256=65536,三级指针256*256*256=16777216,那么i_block计算公式: 12+256+65536+16777216=16843020。问题是假如我们希望使用64位整数来表达文件系统容纳的blocknumber那么文件的最大blocknumber是多少呢?我懒得计算了。似乎统统要减半=12+128+128*128+128*128*128=2113676。所以,我的观点是不能简单的改变64位整数,inode必须也要改变否则单个文件的最大尺寸会变小。
    • 那么file size (real)是不是很直截了当的就是blocks per file*block size。对于1k blocksize=1k*16843020=17247252480
    [a] This limit comes from the maximum size of a block device in Linux 2.4; it is unclear whether a Linux 2.6 kernel using a 1KiB block size could properly format and mount a Ext2 partition larger than 2TiB.

十月十日等待变化等待机会

  1. 那么每一个blockgroup是怎么决定的呢?首先,什么因素决定了blockgroup的个数呢?这个问题其实很简单。因为每个bg都只有一个block来作为blockbitmap,那么它就决定了bg里的block上限。对于4k block来说就是4*1024*8=32768。相应的它也决定了一个分区需要多少bg,比如一个分区的大小是219G,由以上我们知道一个bg的大小是32768*4k=128M所以共有219G/128M=1752我为了验证这一点发现你不能使用df得到的分区大小219G,因为实际上dumpe2fs显示bg有1922个。原来我如果使用fdisk /dev/sda1我看到raw disk有240.1G(258100690944),然后我门计算就对了:258100690944/128/1024/1024=1923,那么这个计算方法没有考虑superblock及其backup,不要忘记了superblock还要连带block group descriptor部分。这个计算其实还是有些复杂,等我以后看看mke2fs看看怎么计算吧。
  2. 每个bg的blocknumber是有上限的,那么inode个数呢?很明显的似乎不是问题,因为比如每个bg的block数是32768从而我们计算bg数是1922,那么总共inode是32位整数所能代表的数字个数2,147,483,647除以1922是1117317这个和superblock的8192差距很大!我想起了祖师爷的RTFC!在你看不到前方的光明的时候代码是你唯一的指路明灯。看看mke2fs的算法吧:#define EXT2_MAX_INODES_PER_GROUP(s) (((unsigned) 1 << 16) - EXT2_INODES_PER_BLOCK(s))其中EXT2_INODES_PER_BLOCK的计算很容易就是blocksize(4k)/inodetablesize(128)=32。这里我懒得去强调inode大小可变的情况,都是按照good old ext2的定义。但是32768-32和8192差距很远!那么linux是否真的需要那么多的inode呢? df -i /dev/sda1看到实际的inode是15753216这个数字应该是1922*8192计算的来的。有些误差。。。问题是8192是怎么决定的呢?这里我完全迷惑了,我只能说隐隐约约的决定一个文件系统需要多少inode是要由磁盘大小来决定的,比如一个文件最小也要占用一个block因为文件是用blockbitmap来表达的,所以磁盘的block数字就是Inode的理论上限,当然还要扣掉很多,并且很难想像所有的文件都是4k的小文件,这是不可能的,所以,这个一定是一个经验公式,不妨这么考虑每个blockgroup有32768个block,平均一个文件需要4个block,所以,32768/4=8192。这个是我的猜想,但是我要怎么解释mke2fs的代码呢?我可能看到的不是正确的地方。不过我们已经知道inode并不是真的需要32位整数来决定的理论值而是由实际的磁盘大小来决定,这一点我觉得就足够了。
  3. 看武直十研制的艰辛过程:事到无可奈何,方显英雄本色。境遇不堪回首,乃知志士胸襟。横批“争气必胜”

十月十一日等待变化等待机会

  1. 重新温习一下boot sector的基础知识。
    1. 比如我要验证一下我的磁盘设备/dev/sda是否有MBR(我猜想是GPT的protective MBR sector)这个证明了确实有一个MBR,但是它是真的其作用吗?
    2. 很多时候专家对于普通人是蔑视的,撰写wiki的期待着读者是专家,或者已经跨越了基本门槛的,所以无视的抛出一些名词。对于GPT如何包容MBR原理是有了,就是设置partition type为0xee让MBR reader误以为这个partition整个都无法使用?可是这里让我迷惑的是partition type究竟定义在MBR的什么地方呢?甚至我在MBR也没有看到定义,难道大家都不屑于这个简单的问题吗?最后我在google里看到了实做家在详细的指南里的指导,原来是在partition entry里的定义,那么GPT就要针对每一个MBR的entry都要这么做吗?我们首先来验证一下吧:
      • 能否启动? 80是能启动。
      • 再验证一下它的partition type:说明它是linux。我能说什么呢?我是使用的MBR而不是GPT。
      • chs数值如何呢?根据head=32,sector=33,cylinder=0 根据公式LBA = (C × HPC + H) × SPT + (S − 1)其中
        • C, H and S are the cylinder number, the head number, and the sector number
        • LBA is the logical block address
        • HPC is the maximum number of heads per cylinder (reported by disk drive, typically 16 for 28-bit LBA)
        • SPT is the maximum number of sectors per track (reported by disk drive, typically 63 for 28-bit LBA)
        所以,我的这个puppylinux的分区应该在LBA=(0*16+32)*63+(33-1)=2048 注意了所谓的LBA实际上就是总共有多少个cylindersector我犯这么可笑的错误是我根本没有拆过硬盘压根不知道cylinder/head/sector是怎么回事。纸上得来终觉浅,绝知此事要躬行。我无意中看到google对于这句话的英文翻译,实在是搞笑的:It's on the paper, and I finally feel that I know this thing to do.也就是512byte的大小。所以,我的这个分区的偏移是2048*512=1048576。我能证明这个是正确的吗?
      • 我们来使用superblock的volumename来证明吧。首先superblock在分区开始的偏移的1024,其次,superblock里的结构的偏移120的s_volume_name是一个16byte的array是volumename。所以,我们要读偏移1048576+1024+120=1049720的16bytes来看看是什么: 为了更加的验证一下我们直接去读/dev/sda1分区的superblock的volumename这个是等效的当然这个时候偏移是1024+120=1144
    3. 你是否也像我一样迷惑怎样用dd来读取partition table?其实,稍一凝思就明白其中缘由,在磁盘开头部分并没有什么额外的“分区表”,它本身就在MBR里,通过阅读MBR的partition entry你就知道了所有的分区信息
  2. 我的未来不是梦的歌词

    你是不是像我在太阳下低头

    流着汗水默默辛苦地工作

    你是不是像我就算受了冷漠

    也不放弃自己想要的生活

    你是不是像我整天忙着追求

    追求一种意想不到的温柔

    你是不是像我曾经茫然失措

    一次一次徘徊在十字街头

    因为我不在乎别人怎么说

    我从来没有忘记我

    对自己的承诺对爱的执著

    我知道我的未来不是梦

    我认真地过每一分钟

    我的未来不是梦

    我的心跟着希望在动

    我的未来不是梦

    我认真地过每一分钟

    我的未来不是梦

    我的心跟着希望在动

    跟着希望在动

    你是不是像我整天忙着追求

    追求一种意想不到的温柔

    你是不是像我曾经茫然失措

    一次一次徘徊在十字街头

    因为我不在乎别人怎么说

    我从来没有忘记我

    对自己的承诺对爱的执著

    我知道我的未来不是梦

    我认真地过每一分钟

    我的未来不是梦

    我的心跟着希望在动

    我的未来不是梦

    我认真地过每一分钟

    我的未来不是梦

    我的心跟着希望在动

    跟着希望在动

  3. 经天纬地曰文,照临四方为明。这就是“文明”的定义。
  4. 我突然想到一个问题,对于mount的文件系统它的inode和本文件系统相冲突怎么办呢?使用ls -i查看的确mount的文件系统的inode不会做修改的确和本文件系统的Inode冲突。对于inode的分配这一点我之前已经自己领悟到了这一点,现在看到更加的好
    The amount of inodes available on a system is decided upon creation of the partition. For instance, a default partition of EXT3/EXT4 has a bytes-per-inode ratio of one inode every 16384 bytes (16 Kb).
  5. 连带的看到一些人遇到罕见的inode用光的问题,比如df -i可以看到。那么从各种解决方案我们可以确定的就是mounted filesystem不会占用本文件系统的inode限度,那么操作系统并非使用inode作为一个全局唯一标识量来确定磁盘位置,我的想像是也许处理文件的时候他的blockgroupdescriptortable是和inode一起来使用的?其实对于网络文件系统也许根本就是一个伪命题,它的背后的文件系统是使用什么样的机制和呈现的nfs也许都是无关的?

十月十二日等待变化等待机会

  1. 听cppcon的讲座每次都感觉有醍醐灌顶的感觉。
    • 当然这个是我对于震撼的感受,对于其他方家来说也许是司空见惯了,原因在于刀锋的钝锐的差别,我学了这么多年依然是一个入门的小学生,实在是惭愧不已,的确,很多人自认为在写c++实际上大部分都是c/c++混合代码,而且说不定c的代码居多,甚至有简单化的说法就是把c的函数加一个class成为成员函数就算是c++。这个说法我以前也是赞同的,后来听大师讲盲人摸象的典故才知道我就是一个盲人。
    • 这里是一个简单的常见的问题关于一个类如果无法在其constructor里完成全部的初始化那么怎么强制要求用户必须先初始化然后再去使用? 也许有人争议这本身就是一个伪命题,你需要两步初始化吗?为什么不能直接完成呢?我不想过多争辩,第一讲座者是为了突出使用friend的优越来对照其他语言说明c++作对的地方。它的意义更在于相对于factory以及static的成员创建函数使用free function结合friend的运用,上下文里是有在批评c##/java否定free function人为任何函数都必须是类的成员函数的狭隘做法,我个人也是对于连main都要被包装在一个类里的static member function的做法是比较可笑的极端。所以,在这个上下文来看,使用friend free function是一个不错的选择。
    • 更何况在动态库里这个可能是必然的选择,因为dlsym的函数签名是只能使用free function也就是factory模式的根本原因。(当然我也看到过,包括我自己也干过使用hardcoded的c++函数签名来调用类的成员函数,包括static的办法,那一长串的c++ mangle是不可靠的,也是非人类所能阅读的字符)所以,这个做法是有可取之处的。有人会说把factory定义为静态成员函数是一个通行的做法,我的argument就是作为动态库里的分发者考虑到factory是给c或者其他调用者,自由函数是更加的friendly,所以,使用friend更好,因为friendly:)
    结果如此
  2. 几个月不用脑子就生锈,事实上是几天不用就是如此,或者原来就生锈了。讲座里提到一个长期被人诟病的缺陷就是怎么删除range里的数据。我还真的有些诚惶诚恐。因为这个是在c++20之前的做法 据说c++20引进了std::erase支持predicate参数来删除。我要怎么下载最新的编译器看看呢?而且是否最新的编译器就支持c++20呢?或者我可以从boost里尝鲜体验一下而已?的确,我其实早就应该知道boost的实现,需要一个boost/range/algorithm_ext/erase.hpp,而你的代码可以简化使用range并不需要range.hpp,因为前者已经包含了。
  3. 如何在ubuntu 18.04安装g++ 10呢?首先,要明白为什么我需要g++10因为这里。看起来不少的feature都是在10里,至少我目前默认的7.5版本是远远不够的,绝大多数都是g++8以后才支持的。这里指明你怎么安装g++10 但是我一看到要升级库就心里发毛,还是编译源码吧。这里是我小小改动的编译gcc的工具
  4. 这个就是我从讲座里听到的关于c++20的最新的一些小功能。比如删除vector里的偶数这个简单的问题都是很困扰人的,之前使用remove_if返回指针兵部左真正的删除的做法被人们诟病了很多年,现在终于得到了改进。 这个是货真价实的c++20,首先,编译器如果不是gcc-10.x估计都不行,因为其中的stl的头文件就缺失ranges。其次,这个是真正的c++20才有的feature,如果编译指令不加上-std=c++20也是编译出错的。对于不愿意安装或编译gcc-10的人来说就只能使用boost来体验一下了,不过源码有些微的差别,除了namespace外。

十月十三日等待变化等待机会

  1. 对于讲座里提到的大量的string_view以及其他各种view/span的例子我相当的不熟悉,在最近的beast里被动的用到string_view,对于很多细节不明所以,不过这个正是这个库的强大的地方对于使用者来说是拿来就能用没有什么奇怪的surprise,大师曾经介绍他创建c++的当年的一个思想就是不要让语言成为expert-friendly,因为在计算机界有一种不好的风气就是有一部分领域喜欢把域外人看一眼就吓跑,因为别人看到他们的眼神里是一种充满了舍我其谁的对于所有人的蔑视,那个眼神是一种叫做专家的眼神。一种计算机语言的生命力有时候在于他更加的接近人类的自然思考的自然语言,或者说用英语写成的伪代码。这样子在平凡中隐藏着高深的伟大才是真的伟大。 我找不到太多的string_view的文档,似乎我常常访问的cplusplus网站都还没有准备好。于是我转而求助于boost的文档,找到了一个string_ref难道是他的早期版本?讲座里对于view的概括是constructor/destructor/copy的三大件必须是constant的时间,你没有想到这个深刻的定义吧?当然还有一条我忘了,就是轻量拷贝的意思,其实原本basic_string的设计思想不就是这样子吗?不过很不幸的因为关于引用者的声明周期如何保证不超过被引用者的困难,所谓的引用似乎都变成了拷贝?我的感觉如此,以前看过智能string里的设计是好像smart_ptr一样能够做到只维护一个拷贝,但是前提是动态分配一个内存拷贝,但是对于常量这个还是要动态分配一个拷贝就完全打败了当初的设计思想,所以,string_ref的使用是有着非常明确的环境的
    A string_ref is a read-only reference to a contiguous sequence of characters, and provides much of the functionality of std::string. A string_ref is cheap to create, copy and pass by value, because it does not actually own the storage that it points to. A string_ref is implemented as a small struct that contains a pointer to the start of the character data and a count. A string_ref is cheap to create and cheap to copy.
    为了加深理解我做了这么个小小的实验
    • str是一个动态拷贝吗?根据定义不是,这里调用的是它的参数为const char*的constructor
      你是否会像我一样的犯错误以为traits::length(str)是一个类似于cstring的strlen之类的实现?如果是那样子的话,这个库就完全没有必要写了,小学生都能做到的何必要读大学?知识改变命运啊。让我们看看traits这个模板参数类的length是怎么实现的。 这里看到玄机了吗?难道这个是strlen的拷贝粘贴代码吗?如果不注意到_GLIBCXX14_CONSTEXPR是constexpr你是完全无法体会到其中的奥妙的。这个是编译期的计算。 为了证明给我自己看我做了这么一个愚蠢的实验使用gdb来证明constexpr是一个编译期的计算。我在我自己的修改的constexpr的函数里设定断点然后gdb是跟不到那个断点的,这个几乎愚不可及的实验是为了给患有妄想症的人看的。其中的使用built-in函数__builtin_is_constant_evaluated是一个我还不太明白的概念。
    • string_ref的substr同样是创建一个string_ref的对象他们都是只包含指针和长度的这么一个及其轻量级的结构。所以基本都是满足constructor/destructor/copy的constant time的要求。你也许说这个c程序员整天使用指针和这个有什么区别,可是你不要忘了貌似很多c程序员动不动就是调用strlen反反复复的获得指针的长度,这个就是差别,虽然是如此的简单设计,可是似乎不屑于被纳入标准库以至于一直都被忽略。我感觉如果没有编译器的constexpr的助力很多思想是实现不了的。
    • 我随手写的代码如果使用传统的string的话至少有四次的string的constructor的调用做了四个大小不一的拷贝。这些看似平凡的代码里有多少的不应该啊!
  2. c++一个对于我来说最难以理解的地方就是所谓的user defined literature,究竟是什么原因导致的这个决定?

十月十四日等待变化等待机会

  1. 这个是一个很好的补课的机会:究竟c++11有哪些新feature呢?这个总结相当的完善,很有教育意义。
  2. 我对于c++11的thread居然一无所知让我心里忐忑不安,虽然pthread似乎更加的具有portable(我猜的),可是这个依然不能开脱我的无知。只好借用三体的名言来解脱:
    无知和弱小不是生存的最大障碍,傲慢才是。
    心里默念一百遍我不傲慢,我一点都不傲慢,我压根儿一点都不傲慢。
  3. 在当今中美对抗的年代,中国不会因为弱小和无知而遭到毁灭,但是美国的傲慢却能导致它自我毁灭。
  4. 听大师们的讲座简直就是让我的眼睛看到新的光明,我对于他们展示的代码感到很惊奇,原来还可以这么写代码,我真的是开眼了。这里并不能展示完整dangling的ranges的问题,我被我能够任意定义functor为constexpr而感到惊奇。

十月十五日等待变化等待机会

  1. 我使用的eclipse for c++不能正确的识别c++20的代码是一个不奇怪的问题,首先内置的默认c++编译器是官方的gcc7.x,那么我创建的是一个makefile project因为我自己编写makefile来生成precompiled header,这一方面似乎默认的managed c++ project有些问题
    • 那么我怎么样才能让indexer也得到类似的编译器的信息呢?我在project->property->C/C++ General->Preprocessor include Paths, Macros etc.->CDT User Settings里添加了我的gcc-10.2.0的安装的include目录,以及/usr/include目录。
    • 不过我怀疑以上部分并不是最主要的,因为我首先要把CDT GCC Builtin Compiler Setting下的都clear掉。然后在"providers" tab里的CDT GCC Builtin Compiler Setting里修改成正确的gcc-10的编译器c++的绝对路径以及正确的-std=c++20因为即便你使用gcc10也可能并不包含c++20的feature如果不设定-std的话。
    • 然后我对于indexer进行rebuild发现它自动正确的添加了这些搜索目录(我怀疑这些是从c++ -print-search-dirs的输出目录里得到的。
    • 然后我发现对于std::ranges::copy之类的函数依旧不能解析,我尝试手动搜索居然无法定位到函数的定义,同样的是copy_if,似乎他们的定义是一种很复杂的宏的替换,我觉得既然我手动都无法找到ranges里的各种算法的函数定义,那么IDE找不到也是正常的了。
    • 我费了九牛二虎之力在bits/ranges_algobase.h找到了这样子的定义: __copy_fn是什么呢?它是一个functor,而它的operator()是模板函数,可是这样子的定义的用意是什么呢?为了把一个函数体定义为一个常量的目的是什么呢?
  2. 可是这个声明constexpr的做法有何深意呢?比如我把to_string包装成为一个functor 这样子的做法似乎就是一个alias的作用?我可以节省一个MyFunctor的constructor的费用?可是它也没有constructor呀。另外,身边看了一下c++11就有了的to_string,我其实应该早就使用它了。它内部使用的是vsnprintf,所以,效率上来说和我自己调用没有什么区别。而其中一个小技巧是:假如传入参数是const char* fmt,...你要怎么再去调用参数呢?我原来就遇到过这个问题结果放弃了。这里的例子很好。我一开始还想使用__builtin_va_list,看了这里才意识到这里还有一个编译器操作系统平台的问题,正确的应该使用cstdarg里的库的做法,其实这个我也经常用但是用完了就忘记了
    总之,关键是要自觉使用to_string
  3. 那么类似的boost::lexical_cast是怎么实现的呢?首先,我又学习到了一个小技巧,通常我们把这类问题都转化为模板函数来实现,因为模板参数来自动匹配不需要我们专门的处理因为很可能的都有了现成的实现。但是函数的参数匹配是针对函数参数并不包含返回值啊!返回值本身也不是函数签名的一部分,重载也不针对函数返回值,那么对于lexical_cast这样子的返回值类型至关重要的怎么办呢?这里的技巧就是把返回值当作内部模板参数传递给实现函数,而最大限度的利用模板的类型推理省却使用者明显的指定转化类型。比如我这里不指定转化类型,那么模板函数要自己推导转化类型我理解错误,并不是没有指定转化类型,这个是一定要的模板参数,但是要把函数返回类型作为函数签名的一部分当然是要把它作为模板参数再传递给实现函数,当然这个就是没有什么特别神奇的了。我的误解!而lexical_cast是怎么做到的呢?实际上对应的模板函数是这个 这里的Target类型会作为参数转入实现模板的参数。我注意到它的实现部分代码大部分是mpl的类型推导说明效率是很高的不占用运行期的计算,但是难度是很大的。我决定放弃阅读了。

十月十六日等待变化等待机会

  1. 我常常需要写一些debug的code,比如把一个结构打印成string输出帮助debug,那么我现在想这么做一个通用的做法,就是这些类都去实现一个conversion operator然后我再定义一个通用的to_string的函数接受string为参数要求类对象转化为string来输出。 就是说任何一个类或者结构都要继承并定义一个operator string()的成员函数,那么将来调用to_string的时候就调用到了它的转化函数从而输出打印结果用来debug。
  2. 关于std::decay是一个很好的学习的点,就是什么是兼容的类型的一个例子
  3. 我刚刚看到std::array这个包装了原本c的array(它让我联想到了之前的std::ranges里的实现view似乎也是这么一个做法),这个其实看起来是有意义的因为从类型来说,一个c-array的大问题就是传递参数的时候会退化为指针导致失去类型的监督,通常这个问题只能明显的定义函数参数为c-array类型或者使用模板。不过反过来想我对于这个问题又模糊了,这个有什么不对呢?

十月十九日等待变化等待机会

  1. 很多时候无论多么的微不足道的东西都是学习的一部分,比如,我一直想用最简单的方式把一个文件的字串tokenize,那么怎么做才是最简单的呢?
  2. 这里我遇到一个使用gcc-10.2的小问题,我一直避免系统默认的编译器的升级因为需要更新系统运行库,于是仅仅使用自己编译的gcc-10.2来编译,那么似乎很多情况下可以正常运行无需把它安装替代默认的系统编译器gcc-7.5,但是如果把以上部分的代码修改为copy到stringstream于是运行期会抱怨"/usr/lib/x86_64-linux-gnu/libstdc++.so.6: version `GLIBCXX_3.4.26' not found",如果你ldd查看依赖确实显示需要这个版本的动态库,而查看系统c++库"objdump -p /usr/lib/x86_64-linux-gnu/libstdc++.so.6 | grep GLIBCXX_3.4.2"也的确缺失了3.4.26,那么再查看我的gcc-10.2自带的库: 那么解决办法当然是运行期设定LD_LIBRARY_PATH=/home/nick/opt/gcc-10.2.0/lib64/来避免升级了。因为这种不兼容的问题非常的复杂。
  3. 其实我更加希望tokenize能够存储在vector里,所以,我的改进是

十月二十日等待变化等待机会

  1. 我似乎不明白hexdump和hd的区别,我一直认为前者是真正的"hexdecimal"的输出,没想到它的意思居然是“数字”而不是字符?总之hd输出的是byte order,因为我被这里的输出结果吓坏了,我以为我对于byteorder的理解都有问题了。比如究竟谁输出的是byteorder? 我最后求助dd和mc来帮我树立信心。
  2. 关于elf的type我的理解是relocatable就是静态库,当然obj也算。
  3. 我一开始困惑为什么dumpelf的代码有那么多个文件,后来看了说明才明白它把elf输出成为c的数据结构,这个实在是很趁手的兵器。我之所以有些意外是因为我记得readelf就是一个源文件,这个是在binutils里的。我又一次看到elfcpp我一直想利用这个header-only的工具做些什么。是否boost有类似的东西呢?
  4. 关于libstdc++.so的问题,我好奇的去下载源码,在ubuntu里,apt-get source libstdc++6选择下载gcc-8提示文件里告诉你git clone https://salsa.debian.org/toolchain-team/gcc.git -b gcc-8-debian,我基本上放弃了单独编译libstdc++的努力,这个几乎是很难的,因为粗略的看gcc的编译过程不是那么容易的。然后我看到了文档里说的这个关于c++的QA的网站。这个网站有很多的学习的东西,比如这个关于cin的输入检查的问题。 也就是说要使用或者我觉得判断cin.good()也可以。
  5. 类似的这个也是我长久一来经常忘记的问题不正确的做法而应该是这里对于这个cin问题的解释是比较好的一个,其实就是这个explicit operator bool() const;的应用。当然了我以前的c++98的知识是这样子的operator void*() const;这个是需要编译器强制cast的,所以在c++11以后while这个语句会调用operator bool()吧?这个简单的实验可以证明while会显式的调用operator bool这个运行结果是死循环就证明了while是会匹配最合适的函数的。
  6. 汉语是很有意思的语言,比如“北斗”总工的就是学习创造两个过程,我的总结就是“抄”和“超”的过程。
  7. 对于convertion operator的模板函数调用是怎样的呢? 我想不出有什么别的调用办法读自己的笔记真是一种愉悦与惴惴不安的混合的感觉,一方面发现自己的昨天居然比今天懂的多,另一方面又经常发现昨天怎么犯这么低级的错误。比如我单单注意到了模板返回值类型我忘记改为T但是瑕不掩瑜的是整个思路是正确的就是对于模板函数编译期的调用是不明确的。这里的解释也许更好。
    operator()() is a template member function that takes () as parameters. So, its name is operator(), its parameter list is (). Therefore, to refer to it, you need to use apple.operator() (its name), followed by <int> (template parameter), then followed by () (parameter list).
  8. 再一次看到讲座里的用模板实现的继承,似乎叫做静态继承?官方名称 curiously recurring template pattern (CRTP) 使用是这样子的 结果是这样的
  9. 我对于这个atomic的理解不太准确,猜想好像是教科书里使用的所谓test_and_set,但是具体概念很模糊了为了加深记忆把例子改了一下实验一下。读自己的笔记真是一种享受,我发现如果笔记做的乱七八糟连自己也看不懂,你就很难温故而知新。相反的,笔记写的漂亮详细,回忆起来是一个很美妙的重温老电影的感觉。这里我发现我居然忘记了atomic的全局变量,这个才是这个实验的核心,所有的焦点都是放在如何存取这个被严格保护的全局变量上的!而这个模板类实现了安全的fetch_add等等的方法。

十月二十一日等待变化等待机会

  1. 对于atomic_flag的理解主要基于以前课堂里的概念。我唯一有印象的就是教授说这个test_and_set是一个软件能够解决的问题在一开始工程师却依靠硬件来解决。 我做了一个小小的练习顺便理解并能形象化的展示一个基本的浅显道理:有时候一件任务并不是很多人同时做就能加快的,比如有的工作有瓶颈不管多少人同时工作都要最终请示一个尸位素餐的领导审批以至于不管有多少工程师同时工作却因为要排队审批导致整体的工作效率反而越来越低。我对于这个事情有着亲身的体会,当时的BMC是一个处理器很有限的小型系统,可是为了加快数据产出的速度客户端使用很多客户端同时查询导致最后输出速度还不如一个客户端一个一个的查询快。可是很多大公司秉持增大资源就可以增加产出的理念拼命增加投入。我觉得中国目前对于芯片半导体也许也是遇到了类似的瓶颈,有些工作很难通过增加并发提高产出的,不如缓一缓一步一步的前进更加的稳妥。
    • 首先这代表着你获得了任务,这个fetech_add也是典型的教科书里的概念,我的理解这种可能是没有lock实现的非常快的类似一个指令实现的概念。
    • high_resolution_clock是一个nanosecond级别的钟,chrono里的内容也是从boost里来的,两个概念time_point和duration是人们生活中常常弄混的概念。
    这里是一些运行结果是为了说明线程数量增加和工作效率降低的趋势,一个线程独自工作当然最好,这个结果说明了那句成语:一个和尚挑水吃,两个和尚抬水吃,三个和尚没水吃。而且以下参数配置显示因为两个线程和一个没有区别,第二个线程压根就没有机会拿到任务就结束了个线程是最佳配置,这个是基于等待时间100nanoseconds以内不算等待的判断的。
  2. 我一开始想着tuple可以实现一个iterator因为使用index可以访问不就是好像rangdom iterator一样吗?可是后来一想iterator往往是指向一个同类型的对象,那么tuple的不同质导致iterator的想法不现实,是吗?后来我google才发现我居然没有注意到apply这个方法。之前我在mpl里看到对于type这样子操作,为什么不能对于值操作呢?这个应该是hana的思想吧?我之前停下来的学习看样子需要继续才行。
  3. 对于这个例子为什么模板函数不能作为apply的参数是非常的难以理解的,我google到这个大侠的解释,可是很难看懂。我尝试了以下这个做法,它可以替代generic lambda 然后这个作为predicate是可以的。 作为比较可以使用lambda

十月二十二日等待变化等待机会

  1. 参考这里制作了一个所谓的万能结构,其实毫无用途,只是展示可以作为一个类似于tuple的结构,当然那,你可以使用stringstream来记录输入参数,在这个意义上,可以作为一个logger来使用。 使用它可以直接用make_from_tuple 效果就是作为一个logger来使用[1, hello world, 2.5, a]
  2. 我也尝试如果使用operator()而不是constructor的话可以作为make_tuple的一个wrapper,比如预处理一些事情好像检查数据类型等等的工作 比如这样子创建tuple
  3. 其实我是想好好练习一下这个所谓的parameter pack,这个语法对我来说实在是有些困难。因为它有好几种不同的应用,作为模板参数我就已经很头疼了,作为函数参数虽然和c的似乎一样,可是原来使用variadic我就发憷,加上还有这么个新的类似于apply的做法可以把一个unary的操作符释放在每一个元素上。比如我改造了一个取得所有tuple的size的数值的一个tuple,这里我就想要牢牢记住的是可以这样子取得variadic的参数的大小sizeof args...这个和取得整个参数个数的语法我现在混淆在一起了sizeof...(args)实际上我始终头脑里无法树立sizeof...一个operator的概念 所以,你可以检验一下这个tuple的每个元素的大小 结果还有点意思
  4. 对于initializer_list我始终不理解它的用途和原理,而且这个东西似乎也不能拷贝,那么它的用途是什么呢?比如这个warning -Winit-list-lifetime是什么意思呢?是说明它不能拷贝造成的吗? 我想要查询所有的warning结果找不到,这个是gcc-10才有的吧?至少gcc-7.5是没有的。

    我找到了帮助果然它是不能被拷贝的。

    Initializer lists may be implemented as a pair of pointers or pointer and length. Copying a std::initializer_list does not copy the underlying objects.

    The underlying array is not guaranteed to exist after the lifetime of the original initializer list object has ended. The storage for std::initializer_list is unspecified (i.e. it could be automatic, temporary, or static read-only memory, depending on the situation). (until C++14)
    The underlying array is a temporary array of type const T[N], in which each element is copy-initialized (except that narrowing conversions are invalid) from the corresponding element of the original initializer list. The lifetime of the underlying array is the same as any other temporary object, except that initializing an initializer_list object from the array extends the lifetime of the array exactly like binding a reference to a temporary (with the same exceptions, such as for initializing a non-static class member). The underlying array may be allocated in read-only memory. (since C++14)

    The program is ill-formed if an explicit or partial specialization of std::initializer_list is declared.

    (since C++17)
  5. 所以, intializer_list是不能被拷贝的,应该使用std::array,而且它会自动推导模板参数。 此外auto作为参数类型似乎不允许我定义结构为函数内部的局部类型,所以我只能开一个namespace把结构定义为其中的全局经过这一段的学习我知道了这个auto存在于整个lambda的形式实际上是一个generic lambda的类似于模板的结构,所以,按照模板不能定义在一个block的原则,它是不能被声明为局部结构的。这个就是我进步的体现。
  6. static_assert是及其强大的,因为传统的程序员心目中assert是一个简陋的宏,而这个宏对于模板参数的解析是错误百出,这个就是mpl里的assert要远远强过于它的原因,mpl里如果没有BOOST_MPL_ASSERT的话简直没法前进一步。比如static_assert让我吃惊的是不带message的形式居然是c++17才新添的,而我对于c++11就支持居然一无所知!不过回想一下我以前从来没有meta programming的练习,原始的cassert其实也差不多。是依赖于编译器来解析类型的,所以要远远比库自己判断const char*和char const*是否一样来的有效的多。在tuple_element里返回的类型是无法很困难使用普通assert的,因为第一要注意多一对挂号,第二这个是运行期检查,而对于类型往往我们需要编译期的结果,运行期出错就太迟了。 比如这个assert编译没有问题的 而使用static_assert根本就编译不过。作为比较以下这个是正确的 作为一个实验我想说明remove_cv_t的实现是多么的不容易,比如这个是讲座里提到过让我惊讶不已的现实你能想象只有在指针*之后出现的const才被认为是top-most的cv!也就是说对于指针类型我要怎么remove_cv呢
  7. 这里你和编译器较真类型你肯定要疯掉,请问"hello"的类型是不是和const char*兼容呢?标准答案什么呢? 居然正确的答案还要依赖于c++20才有的remove_cvref来还原当然我依旧不知道要怎样还原指针类型的原本类型。正确的做法是使用decay_t来还原类型的本来面目我之前一直不理解decay_t的用法,现在才豁然开朗!
  8. forwarding reference是及其的晦涩难懂,我觉得我肯定是之前的链条里的move概念有问题,或者非常的不清楚。
    • function parameter of a function template declared as rvalue reference to cv-unqualified type template parameter of that same function template
    • auto&& except when deduced from a brace-enclosed initializer list
    首先我们还是先学习重温一下reference:
    • A reference is required to be initialized to refer to a valid object or function
    • There are no references to void and no references to references.
    • Reference types cannot be cv-qualified at the top level我对于这一点感到困惑,难道const int&不算是cv-qualified at top吗?; there is no syntax for that in declaration, and if a qualification is added to a typedef-name or decltype specifier, or type template parameter, it is ignored. 这里解释了一些我似乎有些明白:

      the type of const_pointer is actually T* const, not const T*.这一点我已经证明了 The constness attaches to the pointer, not to the type pointed to.

      Now references obey the same logic: if you make a typedef for the reference type, and try to attach const to it, it will try to apply the const to the reference type, not the referenced type. But references, unlike pointers, are not allowed to have top-level cv-qualification. If you try to mention T& const, it's a compilation error. But if you try to attach the cv-qualification through a typedef, it's just ignored.

    • Because references are not objects, there are no arrays of references, no pointers to references, and no references to references。 这个例子帮助理解:
    • Reference collapsing有着如下简单的规则:rvalue reference to rvalue reference collapses to rvalue reference, all other combinations form lvalue reference.
  9. Rvalue references can be used to extend the lifetimes of temporary objects。这句话要怎么理解呢?

十月二十三日等待变化等待机会

  1. 我的核心问题是真正理解rvalue reference,我google到了这篇看起来是最好的解释文章,应该值得好好阅读与收藏。首先明确要解决的问题,就是why的部分,这两个题目我以前都是孤立的零零碎碎的学习的,现在是把他们串起来的时候了: 其实我喜欢作者的风格就是怎样把一个复杂的问题用简单的方式来解释,因为把一件简单的事情用及其复杂的方式来完成是一件及其简单的办法,而相反的,把一个及其复杂的事物用通俗易懂的方式来处理是及其困难复杂的方式。说白啦lvalue是有内存地址支撑的并且是可修改的内存,而rvalue正好相反,或许没有内存地址,比如编译器创建的指令里的literal,或者可执行程序里的只读内存部分,或者就是一个表达式的evaluation的计算结果,和临时中间变量一样比如一个函数的返回值,又比如栈里的临时内存地址有一定的生命周期等等,我想还应该有其它的情况,否则没有理由这么复杂。
    An lvalue is an expression that refers to a memory location and allows us to take the address of that memory location via the & operator. An rvalue is an expression that is not an lvalue.
  2. 我们的move闪亮登场了,他要解决的是长期困扰的效率问题就是c++最精华的一部分带来的问题,constructor/destructor自动创建释放资源本来是解决资源回收把程序员从繁琐的重复代码解放出来的由编译器自动产生的代码的效率问题。这个很类似的可以联想到java最大的优势和劣势之一就是资源的自动回收,它解放了程序员但是代价在很多时候又是不可接受的,move要做的很类似就是在一个既定的框架上来优化:
    when there is a choice between two overloads where one is an ordinary reference and the other is the mystery type, then rvalues must prefer the mystery type, while lvalues must prefer the ordinary reference.
    深入的体会可以想像c/c++的三种传递参数的方式指针是最高效率的,也是最为人诟病的,引用是c++对于c的变革的创造,但是在解决效率和防错的同时也需要进一步的提高,因为对于没有内存地址的临时变量如何作为参数来传递而提高效率这个在c++98是没有选择的只能是传值创建额外的拷贝,这就是问题的本质。在c++创立之初c++98是没有办法解决这个问题的,因为lvalue/rlvalue是井水不犯河水的泾渭分明,没有什么中间地带,于是,人们开始在原本对立的概念上挖潜,创造性的使用"swap"来提高效率避免资源的重复分配与释放,这就是move的本质。要利用move来解决参数传递效率的问题,只有引进新的不同于指针引用和值的第四种参数传递方式:&&
  3. 最核心的就是只有引进了新的rvalue reference才能导致函数的overloading,没有overloading没有办法利用move的方式来提高效率,所以,即便发明的move却无用武之处,因为假如不能够让使用者轻松应用的发明都不会赢得用户的接受,对于产品服务如此,语言同样如此。这就是核心的原则:
    The most important one is that when it comes to function overload resolution, lvalues prefer old-style lvalue references, whereas rvalues prefer the new rvalue references:
    我完全同意这个是核心部分:
    Rvalue references allow a function to branch at compile time (via overload resolution) on the condition "Am I being called on an lvalue or an rvalue?"
    一个人可以轻易的说他理解了,但是实践才是检验真理的唯一标准。相应的实践才是检验一个人真正掌握的唯一可靠法则。具体运用中我其实并没有意识到一个根深蒂固的矛盾,为了应用rvalue reference,那么你的constructor要做怎样的处理才能够让overloading正确区分rvalue/lvalue reference呢?我们传统的c++经典big three要求copy constructor的参数是T const&,他导致他同时接受lvalue/rvalue reference,完全达不到overloading区分两者的目的。所以,唯一的办法就是必须同时实现T&T&&的overloading。

    这就是武功心法口诀以外真正起作用的使用法门,一个人熟读兵书倒背如流武功心法,可是没有学习具体演兵部阵的诀窍和与人搏击的具体手法掌法,其所学的一切仿佛纸上谈兵毫无用处。

  4. c/c++为什么如此的威力强大而又如此的被人诟病?这个著名的原则值得反复玩味体会。
    As we all know, the First Amendment to the C++ Standard states: "The committee shall make no rule that prevents C++ programmers from shooting themselves in the foot."
    成也萧何,败也萧何!它的强大与危险正如Java的流行与绝望一样并行:天之道损有余而补不足!世界是公平的,天下没有白吃的午餐。有一利必有一弊,世界上那有完美,有的只是不为人知的负重潜行。
  5. 我一次次问自己是否真的明白,每一次都那么似乎充满信心的回答是,可是真正遇到问题依然发现答案是不是精微奥妙之处往往被没有准备的头脑所错过,有准备只是因为你曾经经历过,很少有人能第一次做对,也许除了Linus?。这就是学习的过程。既然你已经明白了之前的原理与技巧,你能想像出std::move的实现吗?一个幼稚的儿童心智的会振振有词的回答,那还不简单我的move就是实现swap式的交换不就是吗?是吗?不是吗?曾几何时我就是从英文字面来理解代码的意义,我还嘲笑以前的同事是English Programmer,一方面是嘲笑有着先天本土优势的白人程序员的夸夸其谈,另一方面就是令我厌烦的只会看懂代码里的英文注释。可是今天,我也快成为只能看懂代码字里行间的英文注释的"English Programmer"了吗?如果提高效率是依靠增加另一重的代码的包装这样子的效率提高的方法只需要傻瓜都做得到。难道你就不能意识到在编译期选择的威力吗?当年我看不懂《Matrix》的后面三部曲就是对于其中大段大段的哲学说教,选择是在你作出选择那一刻作出的吗?如果是这样子你就不是选择者,因为你只是被选择者,你以为你生活在matrix以外可以来去自如,任意在matrix里呼风唤雨作出自己的选择,却不知道,你原来生活在一个被更高等的matrix所操纵的matrix 2.0,你的选择看似你按照当前的条件作出,却不知道这些条件在你诞生之前就已经写好,你的选择仅仅是机械的执行即设的条件的机器代码而已。Who is the ONE?难道你没有想到std::move是一个不占用丝毫运行期资源的编译期的函数吗?它只是帮助你作出了选择,或者说强迫你作出了你自认为是自己的选择。这就是forced choice 让我们看看什么是move: 这就是meta programming的威力: Welcome to Matrix!欢迎自以为所向无敌,紧扣自己命运韁绳的c++程序员来到在更高层以上帝视角俯瞰你们的“原始天尊”的国度。

    Devils resides in details!在我看来一个资源由于引进了move的效率的提高似乎没有什么不好,可是这里的分析让你感到后怕,因为资源释放的延迟是你意想不到的副作用,我没有想到这一点真的令人有些不安。这个实在是精深细微的地方,实践中似乎很难掌握。

  6. 在这部分碰壁,似乎完全摸不着头脑。那么休息一下吧。

十月二十四日等待变化等待机会

  1. 这个部分是很微妙的。我之所以糊涂部分因为要实现一个“无名”的rvalue reference相当的不容易,我创建了一个返回临时变量的函数, 这里会和所谓的RVO(Return Value Optimization)混合起来让我很迷惑。我的感觉编译器可以进行优化压根不需要调用特别的constructor,而是直接在目的地里创建?这一切让我看得头疼。这里是这部分的要点:
    Things that are declared as rvalue reference can be lvalues or rvalues. The distinguishing criterion is: if it has a name, then it is an lvalue. Otherwise, it is an rvalue.
  2. 实际上是在这后一章出现了我之前困惑的问题的解答,就是关于RVO的阐述,要点是不要自作聪明在返回临时值的函数去人为使用move因为编译器本来使用RVO比你的move还要优化。不过copy elison这里作为英语为母语确实有优势,因为elison是什么意思我都要google,看起来是“连读”或者是“省读”反正是cutoff的意思和RVO是一回事吗?我一google就捅出了一大堆的东西,知识结构是如此的繁密层峦叠嶂,一环套一环,首先,我要明白value categories,这个是一个泛泛的概念:prvalue, xvalue, and lvalue。这个和平常说的rvalue/lvalue似乎是不同维度来讨论的问题?似乎是的,好像是更加的细分,我觉得这里的prvalue似乎更加的有用吧,因为lvalue虽然更广泛但是对于我来说rvalue上出错误的机会似乎更多?原因在这里:
    When used as a function argument and when two overloads of the function are available, one taking rvalue reference parameter and the other taking lvalue reference to const parameter, an rvalue binds to the rvalue reference overload (thus, if both copy and move constructors are available, an rvalue argument invokes the move constructor, and likewise with copy and move assignment operators).
  3. 什么是prvalue呢?这部分关于value categories的分类太专业了一些,感觉不去研发编译器普通人用不到如此的精细概念吧?
    a prvalue (“pure” rvalue) is an expression whose evaluation either
    • computes a value that is not associated with an object
    • creates a temporary object and denotes it
    (until C++17)
    • computes the value of the operand of an operator or is a void expression (such prvalue has no result object), or
    • initializes an object or a bit-field (such prvalue is said to have a result object). With the exception of decltype, all class and array prvalues have a result object even if it is discarded. The result object may be a variable, an object created by new-expression, a temporary created by temporary materialization, or a member thereof;
    (since C++17)
  4. 让我吃惊的是string literal居然不算rvalue,它是lvalue!我的理解是它是只读的内存地址,难道这个是否有地址的原则要扩展到只读地址吗?另一个人人皆知的事实是前加加(++1)是lvalue,后加加(i++)是rvalue,我一直苦恼于我写循环习惯于后者被认为是效率低下的象征因为有返回值多了一个指令吧?但是这样看来我使用rvalue似乎比前者的lvalue来的逻辑上清晰。这个是手顺习惯难改只好自己给自己找理由,毕竟每个循环多一个指令也不是什么绝对大不了的,不是吗?当年阿基米德也不过就是要了一个象棋盘上的麦粒也没有多少啊!
  5. lvalue和rvalue reference都可以延长一个rvalue的生命周期,但是差别是什么呢?
  6. 我还没有消化这些"prvalue"结果就又牵扯到了"volatile"的疑惑,怎么到这里的呢?原因是我看到endl是一个变量,或者说lvalue让我很吃惊,天天用但是不明其所以然,然后例子里有volatile的变量,可是它作为lvalue/rvalue的深奥之处太复杂了。这个领域是硬件工程师更关心的,我似乎用不到吧?只不过好奇而已,实在是学不过来了。先保存一下吧。我实在不是很清楚为什么下载了这个c语言的标准,不过也很有价值的。
  7. 意外的看到了char8_t的定义,我老早就看到这个是c++20的一个new feature,望文生义以为是老先生们无事生非要把char16_t,char32_t补齐成对称,没想到它是utf-8的code unit,这个让我大吃一惊!不过仔细一读发现我的第一感觉确实没有错,它就好像一个unsigned char的一个alias,的确没有什么副作用,因为它的array的大小是character的大小和编码后的一个unit的大小无关,并不是说有什么神奇的力量要帮你计算zh_CN.UTF-8有多少个汉字啊,这个太夸张了吧?
    char8_t - type for UTF-8 character representation, required to be large enough to represent any UTF-8 code unit (8 bits). It has the same size, signedness, and alignment as unsigned char (and therefore, the same size and alignment as char and signed char), but is a distinct type.
  8. 战线拉的太长了,我已经回不去当初在查找什么了。只能休息休息。
  9. 我回过头来先来问问自己我们究竟为什么要研究这么多的value categories?不忘初心的是因为我们出发的一个目的是需要实现所谓的perfect forwarding。(我似乎也听过类似universal forwarding等等),要真正理解它首先来理解问题本身,那么我们总共可以有几种可能的overloading呢? 在每个函数里我只是打印函数名字以便确定谁被调用了 这个是在不考虑意义的语法允许的前提下的所有的重载可能,这里我不打算引入volatile因为它和const在这个意义上是类似的,但是增加它要增加很多的讨论,原理是一样的。那么现在考虑作为函数调用来看,以上是否都是有意义的呢?以下这个是从std::forward摘抄的例子来说明我们有什么样的调用需求 这里就是所谓的典型的要怎样保持perfect forwarding的场景,我们有一个传入参数要保持原汁原味的把它传递下去怎么办?我原来并不是非常清楚问题的本质,现在有一点点的理解了,因为纸上得来终觉浅,当我编译wrapper的时候编译器再警告它对于以上五种重载形式有“歧义”(ambiguious),编译器不知所措的原因其实是这些重载并不是都有用的,就是说实际上wrapper仿佛一个过滤层,能留下的并不多,要想原汁原味并不现实。我们要去掉哪一个呢?我首先从逻辑上去掉了最后一个“传值”,因为从效率上讲它是最差的而且和其他想必并没有什么优点,因为传值的唯一目的是不愿意更改源头,而我们使用const就可以做到了,所以,只保留四个看看,至少编译器认可我们使用四个重载。那么实际用途上我们会用到四个吗?
    1. named lvalue 结果当然是
    2. nameless lvalue这个其实意义不大对于一个ref来说有名或者无名都是一样的lvalue
    3. 标准的rvalue之一是一个temp variable结果当然是rvalue ref
    4. 标准的rvalue之二是一个表达式无意外的是一个调用rvalue ref的结果
    5. rvalue的另一个形式是函数返回值结果当然也是rvalue ref
    6. 对于const的值或者引用的调用实际上看起来是一样的,反正你都不能修改源头,你会感觉到其中的不同吗? 结果都是const ref的调用
    综上所述,我们唯一用不到的就是const rvalue ref其实这个从逻辑上似乎就没有什么用,就然是rvalue何必要const,我们使用ref仅仅是提高效率,减少参数传递过程的重复拷贝但并没有改变value categories的意思啊。所以结论是只需要这三种重载就足够应付所有的参数传递。

十月二十五日等待变化等待机会

  1. 这个部分是我比较清楚的理解perfect forwarding的问题所在,难道我自始至终都没有明白究竟什么是perfect forwarding的问题吗?有可能。首先,这个是我们的应用场景 核心就是factory的参数Arg??? arg究竟是什么的问题
    1. 传值行不行?当然不行!我花了一个上午来体会实验才明白为什么不行,我都快吐了。
    2. 如果强制传引用固然能够避免constructor需要引用而不接受传值的问题,但是却忽略了constructor接受传值或者const引用做拷贝的部分。所以,这个防止了糟糕的情况发生是以牺牲效率和易用性为代价的。这个做法的例子是boost::bind作者继而指出这个等于是排除了所有的使用move的可能性
    3. 因为很多的constructor可以接受copy,所以为了解决临时变量传值的问题,引入const 引用的形式配合引用的确解决了以上两个问题,这里我的疑惑是针对const引用使用move是否合法合理?但是这个是依靠重载实现的,所以,作者指出它的缺陷是你要为多个参数时候实现所有组合的重载,这个实在是不实际的做法。
  2. 这就是我一个上午的折腾:为了回答传值为什么不行我花了一上午在温习/学习汇编/GDB的基础。我的问题其实是很简单的。对于constructor只接受lvalue的参数你这样子传递临时变量编译器都是不答应的 这个是毫不含糊的。可是当你使用了如factory这样的一个wrapper 来传参数情况就不同了,因为你掩盖了你传递的是临时变量的问题,编译器受之泰然了:。为什么不行呢?原因是这个很危险!对于factory来说你传递的是一个value,或者说是一个可以作为lvalue的变量,它实际上是在上一层frame里的stack里的临时变量。我的目的是想让程序crash,想要把这个frame里的临时变量在下一次调用时候抹去,这就是我追踪stack想要做的原因,可是我对于汇编以及程序运行的常识的薄弱,这样一个简单的问题还想不明白。我基本上放弃了试图改变内存地址的做法,我唯一能做到的就是让程序crash,肯定是我把临时变量的内容加以改变了,但是要达到随心所欲修改还差得很远。 这里我调用test36_wrapper的时候在它的栈里面的临时变量,因为string("hello")是在test36_wrapper。紧接着调用foo,它的临时变量corrupt_memory我故意让他覆盖之前的函数栈里面的临时变量。但是具体怎么定位我却不太成功,因为我费了很大力气改变地址似乎也不能任意展示我需要的内容,因为string里面的operator+=似乎调用了很多其他部分。但是我能够成功的crash程序,应该是临时变量string("hello")作为struct T的成员函数的引用的可怕做法导致的。这个就是为什么我们不能接受传值的根本原因,因为T的constructor明确要求传入参数是一个lvalue,因为它要把它作为引用来保存,可是我们的wrapper函数factory转达的时候允许传值就把临时变量也包含进来了,编译器如果直接面对T的constructor比如T t(string("hello");这类调用可以发现,可是wrapper函数就不能发现了。这个就是perfect forwarding的问题。

十月二十六日等待变化等待机会

  1. 我以为我明白了,可是回过头来又全都模糊了。究竟move的semantic的实现是自动的吗?我们使用std::move仅仅是提供一个明确重载的信号而并没有真正实现吧?换句话说我的理解是增加了rvalue reference的目的是为了能够区分lvalue/rvalue以方便重载来安全的实现"swap"之类的“偷取”临时变量减少拷贝,而这个是需要“显式”的由程序员调用swap来实现还是说编译器自动实现?天下没有白吃的午餐,这个当然需要程序员去重载对于参数是rvalue reference的来实现。首先,如果rvalue reference本身不是reference那么在函数调用之初对于传值就已经做了拷贝什么都无法挽回了。正是因为它是reference才有可能在判断安全后来实现"swap"一样的神操作。其次,这个是切实需要单独实现的,编译器没有做任何事情,仅仅是根据函数参数是否是rvalue reference以及value categories调用相对应的函数重载。这一点可以在string的rvalue reference的实现上看出来注意对于string的通常的constructor它是调用_M_constructl来实际分配内存拷贝的。所以,以上可以看作是swap的实现。尽管它仅仅是把传入参数置为类似于“空”的状态
  2. 另一个相关的问题我又一次犯糊涂了,我原本认为我必须同时实现lvalue/rvalue reference/const referece三种才能满足所有的调用情况,可是再次阅读这里的评述我才意识到我的理解错误。
    实现的constructor形式适应的参数value categories
    lvalue only
    lvalue and rvalue but not const reference. temp goes to rvalue reference
    rvalue only
    lvalue and rvalue also const reference. both of them goes to const reference; temp goes to rvalue reference. 这里是我的疑惑:const reference原本在我心目中应该是rvalue的,现在和lvalue混在一起不是难以区分了吗?
    rvalue only
  3. 我现在总结一下我这两天的学习心得:为了提高参数传递效率人们发明了传引用而不是传值,但是这个对于临时变量不成立,这就是为什么需要引入rvalue reference的原因,首先它是reference才有了优化的可能。但是要正确的实现类似swap的机制的优化这类temp的引用传递比须要有一种机制来区分rvalue reference和lvalue reference。那么怎么才能区分两种引用呢?理论上在c++98的框架下是不可能的,这就是c++11引入rvalue reference的原因,因为只有这样子才能通过引入新的函数签名才能实现重载,也才能安全的实现swap而不干扰影响现有的lvalue reference的引用机制。所以,rvalue reference是一种依赖编译器的内在逻辑在const reference和rvalue reference之前作出正确选择的机制,因为后者可以接受rvalue and lvalue,只有当两者都在的时候rvalue可以优先选择前者,这一点完全是新编译器带来的。而perfect forwarding是围绕着这个问题的一个外围问题,就是在一个wrapper函数掩盖了rvalue和lvalue的区别,比如在wrapper函数传递的临时变量会被错误的当作lvalue的危险的问题。关于forward的机制我还需要进一步学习才能这正理解。联系之前的学习应该要意识到这个引入的rvalue reference不是那么容易的,因为它是一个重大的语法及其语义的增添,虽然不一定影响之前的既有的部分,但是新机制要求就决的问题很多很多。
  4. 比如这个部分就是关于reference的collapsing的问题
    • A& & becomes A&
    • A& && becomes A&
    • A&& & becomes A&
    • A&& && becomes A&&
  5. 为什么说模板的特殊化会导致参数被解决为引用呢?
    template<typename T, typename Arg> 
    shared_ptr<T> factory(Arg&& arg)
    { 
      return shared_ptr<T>(new T(std::forward<Arg>(arg)));
    } 
    Let A and X be types. Suppose first that factory<A> is called on an lvalue of type X:
    X x;
    factory<A>(x);
    
    Then, by the special template deduction rule stated above, factory's template argument Arg resolves to X&为什么?.
    我现在的理解就是有名的变量就是lvalue,那么它当然就是被&&解析为lvalue就是reference。似乎这里根本不需要解释啊? 我不明白为什么,但是我自己做实验可以验证这一点,比如我做了一个结构不论它的constructor采用什么类型的参数,可是结果都是Arg被解析为string& 换句话来说不论MyStr(const string&);或者MyStr(string&);还是MyStr(string);这个机制是模板的推导能力还是编译器对于rvalue reference的机制?我google了一个可能相关的答案,不过要怎么理解呢?
    • && can bind to non-const rvalues (prvalues and xvalues)
    • const && can bind to rvalues (const and non-const)
    • & can bind to non-const lvalues
    • const & can bind to rvalues (prvalues and xvalues) and lvalues (const and non-const for each). A.k.a. to anything.
    可以记住的原则是const&是适合所有类型,const&&可以作为模板specialization的推导为rvalue。
  6. 作为对照我实验了如果不使用rvalue reference作为factory的参数类型的话,比如使用引用的话,参数Arg居然是使用传值的方式,这是为什么?我觉得我那个时候肯定是头昏眼花了,怎么可能型参是引用传递的是传值呢?我的assert错了,应该是这样子才对
  7. 现在回过头来看如何使用std::move自然要比第一次看清楚的多了,毕竟探究汽车引擎的原理相比掌握驾驶汽车的技巧是不可同日而语的。但是这是非常有增益的过程,因为只有实践或者使用才能真正理解问题的起源,也正是从解决方案的角度才能真正理解其中的核心关键。作为使用者我们在享受stl里大多数container已经实现了rvalue reference的优化的事实,我们需要做的就是怎样告诉容器我们什么时候已经准备好了应用它,因为stl设计的是不去影响那些对于move一无所知的传统代码。这里我终于比较理解了forwarding的真正的意义。首先是为什么?因为我们有了强大的move而且stl库里有很多的容器等等都已经实现了就等着你去用了,可是你要怎么使用呢?一个最最直接的问题就是我们从应用的最高层比如用户那里拿到了要存储的数据,也许就是temp的数据要存储必须通过函数参数的传递来存储在各种容器中,可是对于需要经过应用层逻辑包装的函数再调用容器的各种存储生成方法的时候,我们是否还能保留当初temp的意义让容器懂得我们要调用rvalue reference的那个重载类型呢?这就是forward的意义。讲座作者非常深入的展示了这个过程,并且非常重要的指出了forwarding发生的三个条件 这个才叫做“完美”perfect forwarding。这其中的深刻不是简单的阅读就能理解的,因为这个对于c++来说是至关重要的革命,因为效率的提升是如此的重要我觉得怎么说都不为过,想想看参数传递是如此的广泛的发生的事情任何一丁点的提升都是巨大的,而对于如此最最基本的任何一种语言都在精心研究实现的课题能够作出如此大的改进是极其苦难的。我回想起当初刚学习编程的语言是pascal, 对于当初和c语言的调用约定的不同是很令人烦恼的事情,现在想想参数传递从左到右还是从右到左,或者是所谓快速调用在三个以内参数使用寄存器传递和三个以上使用栈传递都是各个语言对于参数传递的敲骨榨髓般的想法设法来提高。所以,我个人以为move的这个想法是非常非常重大的改进,而令人感叹的是我至今不知道准确的称呼这个机制,因为所谓的std::move根本就是这个庞大的体系中的一个及其小小的一步。我原来以为move是内部在做着swap的实现,那时候实在是不理解这个认识的浅薄的原因是对于整个过程的庞大复杂的缺乏认识!

十月二十七日等待变化等待机会

  1. 很久没有磨刀了都生锈了。记忆也模糊了,我对于adjacent_find有一个误解以为是找到第一个不同,实际上是寻找第一个pair相同的,那么对于parsing的需求就得反过来,结果对于equal和equal_to也忘记了区别,更不要说很少用到的not1/not2了。
  2. 有时候脑子短路就会引发一些奇怪的想法,比如adjacent_find其实可以是lower_bound的一个特例:
  3. 我有一个疑问如果按照作者的建议是不是说以后程序员不要再使用const reference了呢?就是说如果是一个lvalue我又不想让你改变我不是通常都传const ref吗?你不是还要再重载这个形式吗?而我之前的实验是说rvalue reference和const ref可以应对所有的类型,事实上const ref本身就可以接受所有的lvalue/rvalue,只不过rvalue reference现在可以被编译器选择所有的rvalue,变成所有的lvalue只能被const ref来接受。所以,我人为作者这里的结论不是很完备。
  4. 感觉leetcode的问题比topcoder要容易不少,毕竟不是什么专业网站吧?挑选最高难度来挑战,不过速度依然是比较慢的,那个需要专业训练。
    You will be given a 2-dimensional grid of letters. Write a method to find the length of the longest path of consecutive letters, starting at 'A'. Paths can step from one letter in the grid to any adjacent letter (horizontally, vertically, or diagonally).
    代码在这里。

十月二十八日等待变化等待机会

  1. 这个又是一个颠倒三观的问题,我一直印象中long int和int是一样的32bits,可是现在看来这个是过去的标准了,到底这个LP64是什么时候为分界线的?
  2. 对于atomic和condition variable我想尝试的做些比较,并不是很明白其中的关键。

十月二十九日等待变化等待机会

  1. 我做了一个简单的对比,就是如果你只有最简单的逻辑比如一对producer/consumer,那么使用condition variable肯定是一个overkill,因为最有效率的是yield,这个合作的同步模式是早期的“和谐社会”的合作共赢模式,两者的综合耗时比较是
    using condition variable using atomic/yield
    • consumer total:502649
    • producer total:492544
    • consumer total:135056
    • producer total:115097
  2. 看到关于RAID的很好的文章,收藏一下

    RAID

    RAID is a technology that is used to increase the performance and/or reliability of data storage. The abbreviation stands for either Redundant Array of Independent Drives or Redundant Array of Inexpensive Disks, which is older and less used. A RAID system consists of two or more drives working in parallel. These can be hard discs, but there is a trend to also use the technology for SSD (Solid State Drives). There are different RAID levels, each optimized for a specific situation. These are not standardized by an industry group or standardization committee. This explains why companies sometimes come up with their own unique numbers and implementations. This article covers the following RAID levels:

    The software to perform the RAID-functionality and control the drives can either be located on a separate controller card (a hardware RAID controller) or it can simply be a driver. Some versions of Windows, such as Windows Server 2012 as well as Mac OS X, include software RAID functionality. Hardware RAID controllers cost more than pure software, but they also offer better performance, especially with RAID 5 and 6.

    RAID-systems can be used with a number of interfaces, including SATA, SCSI, IDE, or FC (fiber channel.) There are systems that use SATA disks internally, but that have a FireWire or SCSI-interface for the host system.

    Sometimes disks in a storage system are defined as JBOD, which stands for Just a Bunch Of Disks. This means that those disks do not use a specific RAID level and acts as stand-alone disks. This is often done for drives that contain swap files or spooling data.

    Below is an overview of the most popular RAID levels:

    RAID level 0 – Striping

    In a RAID 0 system data are split up into blocks that get written across all the drives in the array. By using multiple disks (at least 2) at the same time, this offers superior I/O performance. This performance can be enhanced further by using multiple controllers, ideally one controller per disk.

    Disk storage using RAID 0 striping
    RAID 0 – Striping

    Advantages of RAID 0

    • RAID 0 offers great performance, both in read and write operations. There is no overhead caused by parity controls.
    • All storage capacity is used, there is no overhead.
    • The technology is easy to implement.

    Disadvantages of RAID 0

    • RAID 0 is not fault-tolerant. If one drive fails, all data in the RAID 0 array are lost. It should not be used for mission-critical systems.

    Ideal use

    RAID 0 is ideal for non-critical storage of data that have to be read/written at a high speed, such as on an image retouching or video editing station.

    If you want to use RAID 0 purely to combine the storage capacity of twee drives in a single volume, consider mounting one drive in the folder path of the other drive. This is supported in Linux, OS X as well as Windows and has the advantage that a single drive failure has no impact on the data of the second disk or SSD drive.

    RAID level 1 – Mirroring

    Data are stored twice by writing them to both the data drive (or set of data drives) and a mirror drive (or set of drives). If a drive fails, the controller uses either the data drive or the mirror drive for data recovery and continuous operation. You need at least 2 drives for a RAID 1 array.

    Disk storage using RAID 0 striping
    RAID 1 – Mirroring

    Advantages of RAID 1

    • RAID 1 offers excellent read speed and a write-speed that is comparable to that of a single drive.
    • In case a drive fails, data do not have to be rebuild, they just have to be copied to the replacement drive.
    • RAID 1 is a very simple technology.

    Disadvantages of RAID 1

    • The main disadvantage is that the effective storage capacity is only half of the total drive capacity because all data get written twice.
    • Software RAID 1 solutions do not always allow a hot swap of a failed drive. That means the failed drive can only be replaced after powering down the computer it is attached to. For servers that are used simultaneously by many people, this may not be acceptable. Such systems typically use hardware controllers that do support hot swapping.

    Ideal use

    RAID-1 is ideal for mission critical storage, for instance for accounting systems. It is also suitable for small servers in which only two data drives will be used.

    RAID level 5 – Striping with parity

    RAID 5 is the most common secure RAID level. It requires at least 3 drives but can work with up to 16. Data blocks are striped across the drives and on one drive a parity checksum of all the block data is written. The parity data are not written to a fixed drive, they are spread across all drives, as the drawing below shows. Using the parity data, the computer can recalculate the data of one of the other data blocks, should those data no longer be available. That means a RAID 5 array can withstand a single drive failure without losing data or access to data. Although RAID 5 can be achieved in software, a hardware controller is recommended. Often extra cache memory is used on these controllers to improve the write performance.

    Disk storage using RAID 5 striping with parity across drives
    RAID 5 – Striping with parity

    Advantages of RAID 5

    • Read data transactions are very fast while write data transactions are somewhat slower (due to the parity that has to be calculated).
    • If a drive fails, you still have access to all data, even while the failed drive is being replaced and the storage controller rebuilds the data on the new drive.

    Disadvantages of RAID 5

    • Drive failures have an effect on throughput, although this is still acceptable.
    • This is complex technology. If one of the disks in an array using 4TB disks fails and is replaced, restoring the data (the rebuild time) may take a day or longer, depending on the load on the array and the speed of the controller. If another disk goes bad during that time, data are lost forever.

    Ideal use

    RAID 5 is a good all-round system that combines efficient storage with excellent security and decent performance. It is ideal for file and application servers that have a limited number of data drives.

    RAID level 6 – Striping with double parity

    RAID 6 is like RAID 5, but the parity data are written to two drives. That means it requires at least 4 drives and can withstand 2 drives dying simultaneously. The chances that two drives break down at exactly the same moment are of course very small. However, if a drive in a RAID 5 systems dies and is replaced by a new drive, it takes hours or even more than a day to rebuild the swapped drive. If another drive dies during that time, you still lose all of your data. With RAID 6, the RAID array will even survive that second failure.

    Disk storage using RAID 6 stripingwith double parity across drives
    RAID 6 – Striping with double parity

    Advantages of RAID 6

    • Like with RAID 5, read data transactions are very fast.
    • If two drives fail, you still have access to all data, even while the failed drives are being replaced. So RAID 6 is more secure than RAID 5.

    Disadvantages of RAID 6

    • Write data transactions are slower than RAID 5 due to the additional parity data that have to be calculated. In one report I read the write performance was 20% lower.
    • Drive failures have an effect on throughput, although this is still acceptable.
    • This is complex technology. Rebuilding an array in which one drive failed can take a long time.

    Ideal use

    RAID 6 is a good all-round system that combines efficient storage with excellent security and decent performance. It is preferable over RAID 5 in file and application servers that use many large drives for data storage.

    RAID level 10 – combining RAID 1 & RAID 0

    It is possible to combine the advantages (and disadvantages) of RAID 0 and RAID 1 in one single system. This is a nested or hybrid RAID configuration. It provides security by mirroring all data on secondary drives while using striping across each set of drives to speed up data transfers.

    Disk storage using RAID 1 + 0, combining spriping with mirroring
    RAID 10 – Striping and mirroring

    Advantages of RAID 10

    • If something goes wrong with one of the disks in a RAID 10 configuration, the rebuild time is very fast since all that is needed is copying all the data from the surviving mirror to a new drive. This can take as little as 30 minutes for drives of  1 TB.

    Disadvantages of RAID 10

    • Half of the storage capacity goes to mirroring, so compared to large RAID 5  or RAID 6 arrays, this is an expensive way to have redundancy.

    What about RAID levels 2, 3, 4 and 7?

    These levels do exist but are not that common (RAID 3 is essentially like RAID 5 but with the parity data always written to the same drive). This is just a simple introduction to RAID-systems. You can find more in-depth information on the pages of Wikipedia or ACNC.

    RAID is no substitute for back-ups!

    All RAID levels except RAID 0 offer protection from a single drive failure. A RAID 6 system even survives 2 disks dying simultaneously. For complete security, you do still need to back-up the data stored on a RAID system.

    • That back-up will come in handy if all drives fail simultaneously because of a power spike.
    • It is a safeguard when the storage system gets stolen.
    • Back-ups can be kept off-site at a different location. This can come in handy if a natural disaster or fire destroys your workplace.
    • The most important reason to back-up multiple generations of data is user error. If someone accidentally deletes some important data and this goes unnoticed for several hours, days, or weeks, a good set of back-ups ensure you can still retrieve those files.

    To learn more, read the page on the best back-up policy.


十月三十日等待变化等待机会

  1. topcoder上的一个题目是计算各种彩票的中彩的概率,它把彩票一般化为这样子的一个通用模式,就是总共可以填几个数字(choice),总共有几个格子(blank),是否允许重复数字(unique),是否按照递增排序(sorted)。那么计算彩票的中彩率就是简单计算每种彩票的可能的形式的倒数。这个总结挺不错的,可是遇到排序的彩票的计算还真有点儿麻烦因为我写不出来通项公式,不排序不论允许还是不允许重复数字的是很简单用普通的排列与组合公式就得到了。问题是排序的呢?最后我只能写程序去“数”个数,不知道这个方法是否能够在要求的时间范围内得到,不过我实在是想不出数学公式是怎样的。
    • 有一点值得记忆的是如果我使用lambda写递归要怎么办?这个问题其实我以前就遇到过但是没有真正的体会印象不深。使用auto定义函数是不行的,要使用std::function声明它的函数原型,同时必须在lambda的capture里捕捉自己。
    • 实际上如果没有高中数学的排列组合基础也可以用这个方法计算,因此我就写了一个通用的函数来计算,这样子肯定效率更低了,不过我喜欢一个generic的解决方案。
    为了压缩版面我只好违背风格尽可能减少行数。 这里是一些结果:
  2. 昨天一个aws的S3的metadata的问题困扰了我半天也没有结果。我上传的.svg文件的metadata属性的mime被改成了binary的,导致浏览器无法正确显示,我debug还以为我编译有问题,结果反复检查确实是我的程序有问题,可能是以前我意识到libmagic对于mimetype的支持不是非常完全所以我暂时屏蔽了吧,我现在是首先使用表然后使用libmagic,因为对于没有文件后缀名这个是唯一的办法。
  3. 我对于一个简单的stoi/stol之类的函数似乎从来没有印象,平时习惯于atoi之类的或者stringstream去输出,现在遇到了有些不知所错。因为看代码归根结底还是使用cstring的strtol,但是它进行了包装检查了outofrange的异常和无任何转化的问题,但是我即便自己也要检查输入指针停在哪里吗?(也许不用吧,忽略whitespace应该不需要?)总之我决定以后就紧盯这个不再使用其他的了,但是会抛出异常是让人头疼的,比如我如果习惯于使用stringstream虽然效率低一些但是安全啊 作为对比使用stoi我要防止抛出异常啊!这两种异常都会有查看代码内部使用nerror来记录错误,检查range的确是只针对int,这个道理在哪里呢?我曾经尝试使用short结果溢出并不会有异常,这个就是解释。 这个能不能作为我不使用它的理由呢?作为比较boost::lexical_cast的行为和它类似,不知道是不是有更多种的异常的抛出?

十一月二日等待变化等待机会

  1. 福尔摩斯探案里的难题对于大侦探就是几管烟斗的问题。对于我这个问题是几个小时的散步与调试的问题,实际上一开始就明白算法,但是对于backtrack的细节有误。
  2. c++里包含了当初我在boost里看到的filesystem但是我对于它的成熟度有些疑惑比如cplusplus.com就没有包含它,比如一个文件的last_write_time我就无法搞明白。 比如以下这个oneliner是输出未定义的垃圾的 为什么呢?很显然的是这个所谓的file_time_type的"duration"实际上是未初始化的,可是为什么呢?我的粗浅的理解是file_clock是基于nanoseconds的高精度的clock,所以。。。总之我找到了这个办法 就是说file_clock提供了一个to_sys和from_sys的静态方法,转化为system_clock之后的time_point才能有epoch的输出。这里的技巧是运用decltype来省略了filesystem::file_time_type::clock的累赘,否则要这么写是很烦人的

十一月五日等待变化等待机会

  1. 重新检视vcmi,我实际编译一下。源码是开发branch才行,其实主要是一些依赖的库的自动下载,我对于git的这个submodule还是不熟悉

十一月六日等待变化等待机会

  1. 需要补课,最基本的unique_ptr其实是很简单的,要这么理解:
    1. default constructor居然是constexpr这个我不是很理解,这个其实挺复杂的。这两个难道是等价的?
    2. 它的operator bool()检验其中的pointer是否为nullptr所以使用operator!的话是和裸指针行为一样的。这个必须是explicit的
    3. 他的copy constructor被禁止了(delete了)。同样的assignment operator也是被禁止了。
    4. 它的conversion constructor是要求指针类型可以安全转化,那么conversion constructor是怎样的呢? 这个是的: 那么这个呢? 首先这个在我看来应该是调用move constructor,可是定义里是default,这个要怎么理解它实际上是调用了move constructor呢?至少我在gdb里看不到。这个实际上仅仅是调用了Base的default的copy constructor,因为它没有explicit所以参数可以进行转化,至于unique_ptr实际上move constructor,我可以证明如下 这个编译不过的错误是因为copy constructor是被禁用的。可是如果是调用了move constructor应该是member-wise的move constructor,可是我的程序并没有显示它调用了Base/Second的move constructor,所以,我对于这个不是很确定。
    5. 我遇到了一个有趣的错误,对于我来说这个是化了半个小时才明白的,多亏有static_assert可以检查类型否则我真的不知道要怎么才能发现原因: 这个是我为了测试move constructor的拙劣的做法,不过它是ok的 然后我发现这个样子是不行的编译有错误 问题是为什么?原来a是函数不是变量!怎么知道是什么类型的呢?我找到这个办法,因为第一如果不使用decltype(a)的话直接调用typeid(a)作为参数的a就会暴露编译器抱怨函数有声明没有定义,而使用typeid(a).name()是一串无法辨识的字符,所以使用显示它的类型是Base (Base (*)())这个是什么玩意呢?返回值是什么?Base吗?参数呢?我的结论它不是一个自由函数,只能是Base的oonstructor接受一个函数指针作为参数的原型,但是这个根本定义就是错误的,因为是成员函数所以不能正确的invoke,我的证明是这个编译错误说名result_of不能识别它的返回值类型 更简单的证明是这样子的或者更直接的 结论是一个傻瓜的错误能够难到任何聪明人。另外说一下需要include cxxabi.h这个领域我是有些害怕的因为单单编译libstdcxx库我就遇到前所未有的困难。
    6. 在我使用最新版的编译器gcc-11-20201115发现编译器聪明了很多可以发现这个ambiguous的定义Base a(Base());报出了错误parentheses were disambiguated as a function declaration,所以,这个需要新版的ctor语法来避免这类警告Base a(Base{});
  2. 我原本是研究unique_ptr的各个属性结果扯到无限远,现在回来看看两个有趣的函数 reset为什么返回是void,release返回指针所以,你可以使用release作为新的unique_ptr的constructor的参数,而绝不可能使用reset。 为什么reset不能返回呢?因为。。。根据定义reset是owner,release是放弃ownership,这个就是原因。至于swap是彼此交换ownership,不存在放弃所以也必须是void。
  3. 老问题是你是否人为在这个例子里违反了copy constructor/assignment operator被禁止的事实呢? 我可以证明ptr的类型 事实上这里发生的是老调重谈的问题RVO就是说copy elision。
  4. 那么这个例子有没有违反呢? 注意这里只有普通的constructor被调用这里的move constructor被合并了吧?如果你传参数使用是编译不过的。
  5. 我重新设计了一个实验,我觉得是有意义的,首先如果参数形式是rvalue reference的形式是否会调用参数的move constructor呢?我的实验说明不一定,因为只有lvalue才会调用吧,对于一个rvalue似乎是直接的跳过了,不知道这个是不是编译器的优化? 执行结果说明了我的观点,首先的constructor是作为参数的临时变量,可是它不是lvalue并不会调用move constructor,接下来当然是lambda和lambda返回的时候,这个时候我们发现调用了move constructor为什么? 如果我不使用return std::move(b);编译是通不过的,因为auto 解释返回值是一个lvalue,调用copy constructor是非法的,所以,我只能强制使用move constructor.我的static_assert证明了返回值b是一个rvalue所以需要move。这个例子好不好呢?
  6. 能够得到result_of_t的必须是invocable的,所以,我得不到result_of的类型是这个不是一个合格的函数。这一点其实很重要。而invoke_result是一个更加强大的函数它能够检验函数的不同参数的返回值类型!而且result_of的缺陷导致我们应该使用std::invoke_result
    F(Args...) is a function type with Args... being the argument types and F being the return type. As such, std::result_of suffers from several quirks that led to its deprecation in favor of std::invoke_result in C++17:

    • F cannot be a function type or an array type (but can be a reference to them);
    • if any of the Args has type "array of T" or a function type T, it is automatically adjusted to T*;
    • neither F nor any of Args... can be an abstract class type;
    • if any of Args... has a top-level cv-qualifier, it is discarded;
    • none of Args... may be of type void.

    To avoid these quirks, result_of is often used with reference types as F and Args...

  7. 回过头来看这个result_of的问题,我隐隐约约记得我遇到过这个问题,可是印象不深也不理解就忘了。比如对于一个简单的自由函数float f(int a){ return 3.14;}如果要使用result_of应该怎样做呢?难道这个会有任何疑问吗?实际上result_of对于自由函数很不友好,只适合functor。注意到decltype(f)&(int)作为result_of的参数了吗? 而作为提醒decltype(f)的类型是这样子的float(int)
  8. 相反的使用invoke_result就好多了,这个是我的实验,我只是定义了一个函数的原型没有定义,那么它是否是invocable的呢?应该是的。 可是问题是对于参数为空怎么办?这个似乎是有些悖论,因为invoke_result的用意是测试不同类型的参数的调用结果类型,那么对于无参数似乎是容易的了吗???总之我找到这个帖子让我有些信服,因为void是不能够有reference的所以行不通。

    and declval is specified as:

    template<class T> add_rvalue_reference_t<T> declval() noexcept;
    

    So there's no difference between std::invoke_result_t<F&&, Args&&...> and std::invoke_result_t<F, Args...>. Well, the latter is 4 characters shorter, but they mean exactly the same thing (since neither F nor Args... could be void).

    于是在这个函数原型void f();面前invoke_result只能让位给result_of因为后者需要一个模板参数永远不可能为void,而前者对于参数为void就无能为力,因为它需要函数类型以及参数类型是两个模板参数 这个是编译不了的 结论是result_of尽管不好用却也不是完全无用,invoke_result却有着一个无法克服的软肋。
  9. 在重新认真学习shared_ptr之前我想先回顾一下auto_ptr,读了没几下我就意识到以前我读过类似的陈述:unique_ptrauto_ptr的升级版,改掉了很多后者令人诟病的部分,所以,我在编译中又看到了这个警告说auto_ptr已经是deprecated那么这个警告是怎么来的呢?看到了这个pragma 我去gcc的帮助看到了解说觉得非常的受用。我为了实验auto_ptr就想去除讨厌的warning,就这么做了,这样子就不影响其他的代码了。
  10. 回到auto_ptr它允许copy constructor,这个是否是一个大问题呢? 你在传递参数的时候就转移了所有权,这个不正是你所需要的吗?
  11. 再次读一下高手的评论

十一月七日等待变化等待机会

  1. 关于shared_ptr的这个constructor我原本一直不理解其中的解说
    Additionally, shared_ptr objects can share ownership over a pointer while at the same time pointing to another object. This ability is known as aliasing (see constructors), and is commonly used to point to member objects while owning the object they belong to. Because of this, a shared_ptr may relate to two pointers:

    • A stored pointer, which is the pointer it is said to point to, and the one it dereferences with operator*.
    • An owned pointer (possibly shared), which is the pointer the ownership group is in charge of deleting at some point, and for which it counts as a use.
    直到看源码这里的注释 我才有些理解,可是这里我需要own那个pair里的整数指针吗?它并不是动态分配的啊?“皮之不存,毛之焉附”? 其实核心不在于怎么样释放资源。我对于这段代码深感迷惑,对于pi,它是owner并且要负责pii->first这样一个指针的delete,可是它并不是一个动态分派的指针,我对于aliasing的设计没有意见,可是这个例子让我怀疑delete这样一个宾非new出来的指针有多么大的问题!当然你可以argue这个是注释又不是文档,可是我看到stackoverflow上面大家都是用这样子的例子来解说的,你能说程序员的注释不是最好的文档吗?对于大多数不相信technique writer写的文档的程序员对于作者的注释是奉为葵花宝典般的文档的。 其实我更担心的是我的误解,难道说deleter不会作出这样子的事情吗?比如这个前提是pipii增加了一个引用计数不让pii这个owner提前销毁pair可是,即便如此pi也会负责销毁它指向的first,可是这样子可以吗? 学习的过程是这么的不容易,我觉得我又一次正确认识到自己的认识是不正确的了!根本就是胡思乱想,完全是驴唇不对马嘴的思维混乱,deleter存在于作为参数传递进来的另一个shared_ptr,这个才是ownership aliasing的关键,就是说我们要维护的对象是__r包含的,而不是我们指向的__p,也就是说只有前者是有引用计数的,后者我们仅仅是使用而已,只要是指针并不会释放,为什么呢?释放的机制全部隐藏在计数器里,你不会去对于一个指针指望能够作出这么复杂的动作,也就是说__r里面藏着负责人,所以根本不会是简单的检查一个整数然后调用delete __p这么简单,我的想法还是太幼稚了还停留在很多年前微软实现interface指针的那么简单的add_ref/delete_ref之类的原始人的想法,首先,这个能够全局共享的引用计数本身就是一个复杂的unique_ptr一样的家伙。
    我做了一个简单的实验来部分验证我的错误 运行的结果能够说明在p2释放对于#42的所有权后Container被释放,所以,根本不是释放它使用的指针本身,而是它接受的传入的它控制引用计数的对象。
    这里我终于明白alias constructor是不允许你设定deleter的,这个是封死了你出错的可能,因为它一定是传入的参数自带的,你作为aliasing你只能呼叫前人设定好的deleter。这就是理解问题的关键!
    

十一月八日等待变化等待机会

  1. 昨天写这个简单的二分法搜索居然导致我的eclipse for c++崩溃,今天发现我确实花了不少的力气完成这么一个看似简单的搜索,没办法脑子锈死了。
  2. 关于shared_ptr它不但是赋值就算是传值都是会共享指针所有权的,这个不应该有什么疑惑的。那么move constructor呢?这个似乎也是多余的问题,基本上实现了move constructor的机制要求它内部实现一个swap的机制,这个通常都是在最下面一层的实现类做的,也就是说composition的机制下,外面的wrapper调用内部的move constructor这个就是典型的 效果是怎么样子的呢?仿佛就是swap而不需要引用计数的增减,那么这个是否包含了其中的deleter呢?答案当然是肯定的。比不我的原始的shared_ptr是这样子的 我把它传递给另一个 然后我是否还需要p1.reset();释放自己呢?其实都是不必要的因为p1.reset();也是安全的。 结果就是我们的无名的deleter也被swap给了p2,所以,最后p2仿佛就是p1令魂附体一般。
  3. 这个是简单的,假如我不是使用move constructor来转移,而是用copy constructor来传递,仿佛传递函数参数一样的方式而且我还要保持deleter,这个似乎有些曲折肯定是我的代码有问题,这个deleter是内部shared_ptr的实现类的一部分,所以,如果我头脑中有一个意识就是这个一定要是一个全局共享的类似于unique_ptr的对象我就不会发出这样的疑问了。比如deleter一定会在p1。reset();的时候被调用的。
  4. 我的疑惑其实是来自于std::get_deleter令我吃惊的它不是一个成员函数而是一个全局函数,更加令我吃惊的是它这个模板函数不能够通过参数shared_ptr来推导它的另一个关键的模板参数,就是deleter的类型。就是说模板参数D是必须指定的因为它和T毫无联系,我本来期待着shared_ptr里面能够定义一个它所附带的deleter的类型,比如typedef一个deleter_type之类的,这一切居然没有,转念一想实际上shared_ptr对于内部composition的类是一无所知的,这个反而好,因为对于实现类知道的越少越好。那么,get_deleter要怎么实现呢?我后来看到使用typeid猜想这个就是一个函数指针的话是可以在编译期使用typeid获得地址吧?我有些糊涂是rtti吗?应该不是的,typeid应该是编译期而不是运行期的?所以,我费了很大劲做了一个无意义的工作,把deleter取出来作为参数传递,这个和直接使用shared_ptr的copy constructor的效果一样,可是麻烦多了,完全的无意义。比如获得deleter要冒险把它当作这个不知名的类型 这个做法是可以的,因为我的无名的deleter实际上也没有什么特别的 可是假如我凭直觉认为我的deleter的类型是一个自由函数而这么做的话是失败的,很可能这个指针是一个非法的。 因为在随后的创建新的shared_ptr并释放的过程中我看不到我的deleter被调用,肯定是发生了内存的错误。
  5. 我开始怀疑自由函数的签名是否正确,比如函数void f(int);的函数指针的类型是什么呢?当然是void (*)(int)这个是毫无疑问的 那么为什么我使用自由函数的指针获得的deleter不能用,而使用std::default_deleter的一个functor就是可行的呢?难道lambda内部是存储为functor的吗?也许吧?
  6. 我做了一个小小的实验就是lambda的类型究竟是怎么样子的 结果是有些出人意料,它远比我想像的复杂test92()::{lambda(int*)#1},注意这里的lambda是有标号的,就是说我如果定义多个lambda他们是靠这个标号来确定的,那么这个绝对就是和一个自由函数扯不上边了,显然的使用lambda应该是类似于一个functor的调用而不是自由函数,至少我得到这个信心是因为我使用std::default_delete来获得的是可以调用我的lambda的,有意思的是编译器对于get_deleter的返回值是真真切切的一个函数指针类型,难道函数指针类型和functor指针类型是兼容的吗? 总之我决定放弃了,因为之所以引入lambda就是为了无名,而我现在又想把无名函数作为参数传递,那么它的类型是怎么样子的需要我关心吗?因为从实践的角度来看我应该不会反复使用一个无名函数,因为如果是这样的话我应该定义有名才对。所以,这个逻辑是不对的。总之我很怀疑lambda的内部是一个functor,所以,碰巧和default_delete是兼容的。

十一月九日等待变化等待机会

  1. 对于vcmi的所谓的chinese font的这个mod实际上完全不能工作,这个我花了好大的力气debug。
  2. 有时候,一个毫无道理的问题折磨你许久,最后发现完全是一个可笑的问题。针对vcmi的中文显示问题,我一直在怀疑各种程序的问题,最后我打算尝试truetype,因为这个基本上不用操心各种字体大小的问题吧,结果我使用了一个mod定义的libre office自带的ttf字体,可是尽管我在gdb里看到utf8的汉字编码被正确的取回来,但是显示就是乱麻,于是我决定做一个实验看看如果是SDL_ttf库到底能否正确的显示utf-8的汉字。结果出乎我的意料,居然有那么多的ttf库根本就没有汉字,这个时候我才恍然大悟,根本就是使用了非中文字库的ttf才导致显示汉字不成功的啊!当然了一开始ttf_open始终不工作是因为ttf_init没有被调用,这些细节都是小问题。现在我的vcmi可以使用我下载的免费的“方正黑体”字库显示中文了!

十一月十日等待变化等待机会

  1. 关于encoding转换的问题,我一直都没有找到什么好的办法,现在突然发现原来boost里面有现成的,我为什么以前没有注意到呢?而且他们都支持输入字符为string或者wstring这个看起来很不错。可惜我似乎在c++标准库里面没有看到,也许还有什么不足之处所以没有成为标准?毕竟这个过程存在很大的风险,也许不只是抛出异常就能防止的吧?
    转为utf
    boost::locale::conv::to_utf
    从utf转为multibyte encoding
    boost::locale::conv::from_utf
    标准库的std::codecvt
    似乎完全不是那么回事,我的理解是在utf的不同编码之间的转换,这个想对来说容易多了。也就没有了危险吧?
  2. 我很喜欢vcmi的这个bonus的设计图 留一个拷贝 https://wiki.vcmi.eu/Bonus_system
  3. 我想用bitset来表达Heroes3里7队士兵的厮杀分组,其实这个是一个很简单的集合计算的问题,我只不过想把这个分组进行事先的计算,比如使用模板作为参数,但是struct<N>必须是常数,这个途径看来不行,而且存储这么多的类型难道要用tuple吗?似乎没有意义,因为如果我能够事先生成算法这个类型也无需存储了,使用vector来存储还是运行期的做法,目前看起来好像也只能这样子。 也许我可以把这个Struct的参数来排序列出优先级? 我这个遍历的顺序是按照数字的顺序,如果按照next_permutation的顺序呢?

十一月十二日等待变化等待机会

  1. 感觉我应该学习MCT(Monte Carlo Tree)
  2. 找到一个很好的MCT例子,需要学习。
  3. 在学习任何的tree之前,能否使用一个标准化的库来做呢?这个是我的一个想法,使用标准化的库来减少后续的难度。所以,我不妨先学习一下boost graph library,我以前望文生义以为它是有关于“图形”的算法,中英文在这里有很大的差别,中文的图形是一个词,英文是graph/graphic的差别吧?
  4. 花了一些时间来实现一个寻找closest common ancester的小程序,总算出了一口气。为了给二叉树的产生增加点花絮,我随机停止树的产生。
  5. 我注意到返回的ancester正好是一个递减的序列,这个原因当然是因为我产生二叉树的时候序列号是递增的缘故,所以,可以把它看作是一个set,那么使用set_intersection就可以很容易的找出交集,按照从大到小的顺序来看,交集的第一个就是最近的共同祖先,其实最远的共同祖先是一个骗子问题,因为如果有共同祖先那么一定就是根节点了,这一点我居然没有意识到!

十一月十三日等待变化等待机会

  1. vcmi里的mod需要计算整个mod的CRC checksum,这个是boost::crc的文档。我一开始没有看清楚计算的checksum是哪一个文件,后来才发现vcmi是把各个文件甚至版本号都一并输入而且是反复的计算的,因此,我就懒得修改了反正mod不用计算checksum也是可以使用的。
  2. 有时候一些c++的新的feature你根本不知道要怎么去google,这里有一个不错的集合。,我认为这个是c++ new feature的更加的权威的列表,但是前者也有不少的例子。
  3. 最神奇不可思议的操作符就是fold,这个简直就是让我想断肠的神奇操作!这个是我几个月前看过的结果又忘了。
  4. fold十分的难以理解,我的感觉这个是典型的HANA的应用场景,就是说实参和型参的混合运用,这个近似于内功心法的最高境界,内外兼修方能掌握。 我参考这里的integer_sequence例子 结果是这样子
    0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,
  5. witness, explore, construct
  6. integer_sequence这么个名称是让我感到困惑的,它的实质是什么呢?我觉得是类型参数,我依稀记得boost::mpl里面有它的身影,但是好像名称不是这样子的吧?
  7. 如果你有大量的类似的类,而你不想用不同的名字来命名他们因为你想使用一个数列之类的方式来操作大量的类型,那么怎么产生这个序列的类呢? 这个就是我的类型 那么你怎么能够产生大量的这个类型呢?我现在能够产生但是类型这个东西就好像风儿一样抓不住,一溜烟就没有了,你看得到可是留不住啊!我产生的是类型的临时变量,而不是类型本身。 我产生了10个类,可是有什么用呢?我留不住啊! 这个就是证明
  8. 我把parameter packvariadic argument给弄混了。google到这个例子觉得不错,至少它解决了我的一个问题,就是如何存储类型,虽然我还没有办法自动产生大量的类,至少先存储起来,这个和mpl::list之类的异曲同工。究竟哪一个更好呢?感觉既然mpl已经有了现成的而且有大量的stl的algorithm-like的实现,何苦再自己重新造轮子?
    这里的调用怎么才能自动生成呢?
  9. parameter pack我的笔记里已经反反复复的记载,但是似乎都忘了,或者压根儿没有真正理解,它和variadic paramter很像,似乎是一样的吗?我不确定,这里摘抄一下定义加深记忆:
    • A non-type template parameter pack with an optional name
    • A type template parameter pack with an optional name
    • A template template parameter pack with an optional name
    • A function parameter pack with an optional name
    • Parameter pack expansion: expands to comma-separated list of zero or more patterns. Pattern must include at least one parameter pack.
  10. 我决定把这个常用的typename的小函数做成functor,一开始编译器抱怨不能使用auto作为参数类型,我只好写成了模板函数,可是后来转念一想其实可以把它声明成constexpr结果就可以了。这个是真的很神奇,我还捉摸不透其中的constexpr的原理。 然后我定义了一个小小的lambda来打印数据的类型,其中使用了fold这个最简单的形式我还能理解,但是binary left/right等等我就脑子晕了。 我做了一个测试,结果发现不论我怎么传递参数,对于literal不能使用long long,最多就是long,可是八个byte为什么也是long?也许这个是操作系统的设定吧? 结果是有意思的,我必须要使用"limits"里定义的longlong,对于传统的cstdint里的宏编译老是出错,看来对于这个很少接触的领域所知甚少。
  11. 另一个问题是我感到很疑惑的就是我之所以开始怀疑paramter pack的原因,就是说如果我在lambda里使用实际类型,比如int的话编译器总是抱怨paramter pack无法expand,逼得我被迫使用auto才行
  12. 对于fold的不同形式实在是很不容易,比如我的lambda要打印一组数列,使用第一种形式无法打印分隔符,因为默认的操作符<<必须是直接连续作用在数列上的。 那么就只能使用第二种形式 为了实现第一种形式,我不得不重载操作符使用一个functor 然后就可以使用这个重载了操作符的functor了,为了能够在结束的时候打印字符串我还必须重载了参数,也许我可以使用一个模板函数吧? 你的确可以使用模板函数来代理int和string类型的重载,有意思的是这里你想偷懒使用auto来避免模板函数是不行的,大概编译器的类型推理能力有极限吧?

十一月十三日等待变化等待机会

  1. 我看到有人抱怨c++的ctor有这么多种形式,当时体会不深,比如一个简单的问题,如果是一个initializer_list如何初始化vector,本来似乎很简单的问题,可是当我查询vector的ctor的时候找不到新重载的形式。后来在代码里看到的确是有 看来我要转而使用更加全面的https://en.cppreference.com网站了,古老的http://www.cplusplus.com基本上在c++98/11之后就停滞不前了,这个网站的维护的确很难,大概几年前或许更早一点,做这种网站是一个一劳永逸的工作,仿佛syscall一样几乎没有什么变化,可是如今我的感觉是c++的语法不是月新日异也可以说是每三年一小变,每九年一大变,十八年后你又是一条好汉因为十八年后你根本看不懂新的语法。我的感慨是比如你面对一个类似于initializer_list你是否可以直接使用vector的ctor转化为vector呢?比如你不能使用()这个通常的ctor而是要使用新版的{}的ctor,这就是为什么c++新标准鼓励大家转向新版的ctor的原因。 这里的深层次的原因也许是因为initializer_list不开放ctor的缘故,它是private的,导致你无法让编译器帮你转化参数。 比如这个是不可能发生的因为没有这样子的ctor。然而如果你坚持{}这样子的ctor的话,initializer_list也是可以的,就是这个做法是可以的 这里还有一个相关的有趣的例子比较深刻的揭示了一些我还不能完全理解的部分,对于一个模板类型的推导不是至关重要的,我的理解是如果类型能够被推导出来,你也许压根儿不需要模板了吧?你可以转而使用auto,在这个意义上auto是否可以看作是一个聪明的模板呢? 对于一个数列{1,2,3}这个普通的模板函数的T究竟是什么呢?据说它是无类型的,所以,不给类型的使用模板函数推导类型失败所以这个调用是不合法的你必须提示编译器它的转化类型,所以,以下都是可以的 这里的教训是什么呢?vector为了防止和古老的c++98的ctor不产生歧义不得已让重载的initializer_list不得拥有转化ctor,也就是禁止了它的ctor,而使用{}不算是ctor吧?难道我们称之为initializer?我记得c++98时代好像是这么称呼吧?
  2. 关于fold的unary left/right fold的应用我觉得是很强大的比如,比如我一开始在想操作符对于结合律无关的话是+*那么返回的对子结果是无差别的,可是对于结合律敏感的操作符-/的返回结果就是大大的不同了,甚至我心算都算不出来的。结果有关系吗?
  3. 这个网站我好几天都没有时间看,对于monte carlo tree还是缓一缓吧?
  4. 怎样把一个向量扩展为一个matrix呢?我不知道这个操作叫做什么,肯定是有意义的就是向量的每一个元素都和向量本身scale然后把结果向量作为最终的matrix的组成的一个个向量。这个扩展函数很简单的。之所以返回tuple,是因为我不确定能否轻易的使用vector的ctor,应该是可以的吧?注意这里使用的是函数参数的paramter pack的expansion,它实际上彷佛是一个apply的形式。 这里的确可以使用来替代tuple,同时你也可以直接使用新版本的ctor{},之前我还一直担心这个paramter pack expansion是仅仅限于函数参数,而{}看来也是本质上的ctor,所以当然是函数了。 但是其中用到了向量和整数的相乘,所以要先重载操作符* 现在我们可以调用来实验以下了 但是显示tuple是一个很麻烦的事情,我这才意识到遍历tuple最容易的做法是apply,那么为了打印向量我又不得不重载操作符<< 所以最后打印是这样子的 结果是这个向量被扩展了
  5. 我尝试了好几次想不出来怎么能够使用initializer_list来操作,原因是它基本上是一个“手写”的参数,你很难编程创建它,所以,这个就是我使用integer_sequnce的原因。但是这个必须要使用模板函数。 这个调用是有很大优势的 结果因为是从0开始所以略有不同 作为配套你依然需要实现operator *。至于说打印我就简单的实现了一下

十一月十五日等待变化等待机会

  1. 我感觉index_sequence/integer_sequence和tuple/array是一些有着某种联系的产物,首先,很多是模板和parameter pack才能操纵的物件,其中有不少的小技巧,单单靠个人捉摸是几乎不可能的,哪怕你直接看c++语法也未必能够领悟的到的。至少我是这么认为的,我是领会不到的。也许压根儿看不懂也不一定。所以,这个std::array创建一个std::tuple就是如此的例子,我为了加深记忆,“临摹”了一下在这里。首先这里是调用的方式和运用std::apply来打印tuple,这个也是挺不容易的,我一开始接触tuple就在想如何运用tuple_size和get来访问其中的元素,结果发现这个get是一个模板函数,而模板参数必须是常量你是无法从tuple_size得到的变量来调用的,结果发现只有使用apply才能操作tuple。 那么这个makeTuple要怎么实现呢?也许有一个幼稚的想法是使用tuple_cat把每一个array的元素使用make_tuple来组合起来,这个也许也是可以的这个思路是不可能的,因为tuple_cat本质上和make_tuple是类似的就是说tuple没有一个类似于append之类的增长的办法,它创建就是如此不能改变的。所以,查看make_from_tuple的实现代码就明白使用index_sequence也是tuple实现者访问tuple的工具。但是教科书上的方法是这样子的,就是使用index_seqeunce的一个应用,这里特别值得学习的就是如何进行类型推导,这里的IndexType就是一个很好的例子,我想你直接在函数里用typedef也是可以达到同样效果的吧。根本原因就是array和index_sequence两者的模板共有一个size的元素,这个是推导的核心所在! 虽然概念有了,但是对于paramter pack不熟悉,对于index_sequence在模板参数的表达不理解也是实现不了的,这里的核心就是所谓的index_sequence和当初我看到的boost::mpl::vector/list等等有一个类似的地方就是他们似乎都是一个编译期的东西,比如mpl的vector/list并不是stl里的相应的东西,他们更像是使用宏制作的,也就是模板制作的,并没有实际的存储地址,所以,在编译期可以使用但是运行期是无法访问的,我的感觉index_sequence更像是汇编里的literal,或者直接就编译成了代码,而不是变量,所以模板参数里要访问它就只能使用它的原形,也就是parameter pack,这也就是我们使用std::size_t...N的原因,而对于parameter pack不使用fold操作是难以想象的困难。当然这里我想“古代”C程序员习惯于variadic arguments的概念依靠va_list,va_start,va_end这样子转化为一个array来用传统的循环也是可以做到的,而且从本质上说va_arg的原理在我看来就是type cast的指针强制转化其中的风险是有的。可是那个怎么看都是overkill,因为那是为了对付各种不同类型的,而对于简单的同质index_sequence完全不需要。
  2. 这个问题我思考尝试了整整两天终于成功了,我的想法其实也是很简单就是我想定义一系列相似的结构,我为了能够大量的操作他们所以他们应该是无名的结构绝对无名是不可能的,但是如果用index来命名类型也可以达到类似无名的效果,比如以下这个就是一个“家族”的类型 那么问题是你有没有办法大量的产生这样子的类实例呢?这里的难点自然是模板类的原因,模板参数必须是常数意味着你无法动态的生成这些类。我曾经反复尝试能否通过ctor的传入参数来让编译期来“推导”出模板参数,不过这个似乎是徒劳的,因为逻辑上是相反的才能做到,之所以有模板参数就是说类型是不可能让编译器推理出来的,或者编译器不在乎的,没有任何模板参数输入而凭空推导是不可能的,而应该是反向的,即通过模板参数推理出ctor参数。或者干脆就直接像我这样在结构里定义constexpr static size_t id=N;当然存储的方式肯定只能是tuple,因为类型不一样是不能在vector里的,所以我的调用就是这样子的 要求是明确的结果就是这样子的 可是问题是要怎么实现这个makeStruct函数呢?这个让我想了两天,终于在“临摹”make_tuple_from的例子代码后想到的 而这里的doMakeStruct里的关键就是意识到这个(Struct<N>())Function argument lists和我之前尝试的fold有某种异曲同工的效果,但是又是很大的不同,总之两者的神奇各有千秋。而我一直对于他们有种混淆,实际上他们是大大的不同。我认为()就是函数,虽然无名但是它就是函数调用,这个也是functor的最根本原因,那么对于function argument lists的展开就遵循同样的规则,我的Struct的ctor也是函数所以才得以实现。而fold归根结底是一个binary operation,就是对于list里相邻成员间依靠不同的结合律来产生的“二元操作符”的调用,所以,这个和前者是有本质区别的。
  3. 我觉得对于function argument lists的展开非常的值得学习,因此摘抄这些个例子非常的必要
  4. 为什么要产生这么多的类实例呢?我认为至少这些操作很多是在编译期就完成了吧,是成本很低的初始化。
  5. 对于fold我还是心里没底,究竟结果是怎么样子的呢?我决定做一下实验。我决定使用一个vector 的string来做试验。这些string被初始化了就可以看出过程了
    初始化:
    调用方法:
    打印方式:
    并且我打算都使用+=操作符,让我们看看记录的轨迹是怎样子的。统一使用最简单的打印方式
    fold的类型 fold的代码 fold的结果
    unary right fold abcde,bcde,cde,de,e,
    unary left fold abcde,b,c,d,e,
    binary right fold a,bcdea,cdea,dea,ea,
    binary left fold abcde,b,c,d,e,
    这里是我的感悟
    fold的类型 fold的应用
    unary right fold 从这个结果来看abcde,bcde,cde,de,e,仿佛是一种链式反应爆炸的结果,从最右边开始每一个元素都被相邻元素“传染”,最后结束在最左边。
    unary left fold 这个结果abcde,b,c,d,e,仿佛只有最左边的元素在努力干活,而其他元素都是作壁上观,最左边的元素仿佛一个“收集者”。
    binary right fold 看看这个结果a,bcdea,cdea,dea,ea,这里的第一个元素'a'仿佛就是一个“添加剂”它参与到了所有右边的“链式反应”里,成为所有结果的“表面的附加品”。
    binary left fold 看这个结果abcde,b,c,d,e,它是比unary left fold更加确定的“收集者”
  6. fold的操作符是有限制的,我不确定functor是否可以?感觉不一定,可能元素是类的话要去重载这些操作符,所以,可能只接受这些操作符吧?
  7. unary right fold是比较有用的一个应用就是把一个uniform的数列{1,1,1,1,}变成等差数列: 结果是5,4,3,2,1,但是当我想尝试把等差数列还原却很困难,unary left fold配合-=也是无济于事的。看来这个是不可能的了。
  8. 所以使用unary left fold可以实现一个summation的功能。
  9. 关于怎样从一个index/integer_sequence创建一个array的问题,我参考这里的代码 注意这里return {Indices...};使用的是新版的ctor,而如果你用()编译器会认为你想使用fold却忘记了operator,所以,c++那么多的ctor或者initializer是一个很复杂的东西。 这个是测试代码 结果是0,1,2,3,4,5,
  10. HOMM3里关于数量的term是这样子的
    term number range
    Few 1 to 4
    Several 5 to 9
    Pack 10 to 19
    Lots 20 to 49
    Horde 50 to 99
    Throng 100 to 249
    Swarm 250 to 499
    Zounds 500 to 999
    Legion 1000+

十一月十六日等待变化等待机会

  1. 重新研读以前的笔记是会温故而知新的。看到这个boost::result的解说才想起来这个是在c++11引入decltype机制之前的做法,就是说对于想要支持这个result的functor,实现者要自己定义一个result的内部结构十样一个type的成员变量,否则boost::result就只能依赖于编译器的decltype的功能了,这个做法是当时为了能够在多种编译器的不同版本之间寻求最大的兼容性的做法。
  2. 看到以前的笔记里关于boost::typeindex::stl_type_index::type_id_runtime(t).pretty_name()和我目前使用abi::__cxa_demangle(typeid(t).name(), nullptr, nullptr, nullptr);两者是完全等效的。在我添加了ASAN之后发现前者有内存泄漏,所以,我现在倾向于使用后者,毕竟boost往往有更大的兼容性。
  3. 而我搜索comma也终于发现我的笔记里原来就对于fold和comma operator认识不清,所以,问题不是新出现的
  4. 昨天关于binary left fold的评价今天再一次应征它看起来就像是一个“收割机”,比如我要把所有的参数都收集一下,也许是为了debug或者什么原因吧? 这个是我调用过程和结果
    13.1415Ahello world
    这么做有何意义呢?我的辩解是遇到parameter pack无法正常使用va_arg之类的variadic argument的方法,原因是不知道类型,所以,只能使用这个fold办法。
  5. 我把以前的问题翻出来可是依然是有很多的疑惑。比如c++网站上解说的例子是
    f(h(args...) + args...); // expands to 
    // f(h(E1,E2,E3) + E1, h(E1,E2,E3) + E2, h(E1,E2,E3) + E3)
    
    那么如果我的函数只一个个调用参数呢?比如f(h(args) + args...); 而不是f(h(args...) + args...); 会怎么样呢? 如果我的调用是这样子的,结果是怎么样子的呢? 我发现doSomething是一个一个参数的调用,然后再对于参数的每一个都施加操作符后作为返回值。[begin:a;end]a,[begin:b;end]b,[begin:c;end]c,[begin:d;end]d, 需要说明的是这个情况是只有作为函数参数的情况下才成立的,因为否则会认为(h(args) + args...);是一个fold的不完整形式。 但是转念一想,其实对于parameter pack如果仅仅是简单的打印debug可以使用comma,operator直接达到效果,比如那么对于doSomethingForAll('a','b','c','d');调用的结果就是依赖于comma operator“side effect”我之所以说它是side effect因为,comma原本不是做这种用途的,更像是初始化因为过后就会被丢弃,而且顺序还不一定保证。
  6. 我看到以前对于binary left fold完全没有头绪,感同身受,这个语法是比较的tricky,对于大多数程序员来说应该是比较少接触parameter pack,因为这样子的函数定义就比较的少,最主要的是它不一定可以动态产生调用,更像是手动测试产生的调用,所以。。。
  7. 想尝试boost numberic library我对于大师的断言还没有办法完全领会
    It would be nice if every kind of numeric software could be written in C++ without loss of efficiency, but unless something can be found that achieves this without compromising the C++ type system it may be preferable to rely on Fortran, assembler or architecture-specific extensions (Bjarne Stroustrup).
    这个要怎么理解呢?我依稀记得大师的演讲他创建c++语言的初衷是不失灵活性与效率,并在c语言的基础上强化类型防止错误。那么。。。是类型阻碍了效率的提高吗?
  8. 读这个前言让我“心惊肉跳”因为其中谈到的高级技术都是我刚刚听到希望能够运用来提高的东西,比如使用The approach to solve this problem is to use lazy evaluation as known from modern functional programming languages.Eliminating Temporaries,使用'curiously defined recursive templates'来代替virtual function
  9. boost ublas的第一课是认识它的vector,猛一看和stl的一模一样,可是毕竟是不兼容的类型,不过内部机制相似吧?
  10. 我浪费了很长时间在检查为什么这个普通的ctor不工作,才发现这个好像是gcc-10的问题。 这个ctor我一点都不明白为什么不行
  11. 我突然想到可以把我之前的函数改一下能够产生一个从任意数字开始的array 这个是测试和结果10,11,12,13,14,15,,模板参数是数组的长度,函数参数是数组的起始数字 我现在重新审视这个函数他到底有什么应用呢?我原本期待这个能够依赖于编译期的代码计算生成的速度比动态分配一个vector的数列来的优越一些,可是结果我发现std::array是一个动态分配的数组,所以并不可能像initializer_list之类的是编译期产生的,所以,这么做毫无意义。我使用了这么一个简单的测试时间的办法它的运行还是有运行期的时间的180166nano seconds估计大部分是内存分配的时间吧?array的初始化和vector的初始化能有任何的优越之处吗?我很怀疑。所以,这个似乎完全无意义,而且当我把模板参数加大到超过一千万编译器似乎都崩溃了。 注意到这里用到的语法让我感到困惑{starting + Indices ...};这里不能使用()否则又会被编译器当作fold来解释,那么这个是什么操作符呢?至少我知道如果我不用提示返回值为std::array的话,编译器对于auto的解释是initializer_list并且会报错,因为它是不能被复制拷贝的,因为
  12. 关于这个著名的问题Why is list initialization (using curly braces) better than the alternatives?

    Basically copying and pasting from Bjarne Stroustrup's "The C++ Programming Language 4th Edition":

    List initialization does not allow narrowing (§iso.8.5.4). That is:

    • An integer cannot be converted to another integer that cannot hold its value. For example, char to int is allowed, but not int to char.
    • A floating-point value cannot be converted to another floating-point type that cannot hold its value. For example, float to double is allowed, but not double to float.
    • A floating-point value cannot be converted to an integer type.
    • An integer value cannot be converted to a floating-point type.

    这里作者特别点出了一个例外就是

    The only situation where = is preferred over {} is when using auto keyword to get the type determined by the initializer.

    
    auto z1 {99};   // z1 is an int
    auto z2 = {99}; // z2 is std::initializer_list
    auto z3 = 99;   // z3 is an int
    

十一月十七日等待变化等待机会

  1. 我感觉很恍惚,难道parameter pack不允许用在non-template function吗? 我以为我明白了variadic arguments和parameter pack的区别,可是实际上不是的,我还是不明白。首先我们来复习一下c就有的variadic function的定义

    In the C programming language, at least one named parameter must appear before the ellipsis parameter, so printz(...); is not valid. In C++, this form is allowed even though the arguments passed to such function are not accessible, and is commonly used as the fallback overload in SFINAE, exploiting the lowest priority of the ellipsis conversion in overload resolution.

    This syntax for variadic arguments was introduced in 1983 C++ without the comma before the ellipsis. When C89 adopted function prototypes from C++Interesting, I often think it is C++ who tries to be compatible with C. Now C89 tries to learn from C++., it replaced the syntax with one requiring the comma. For compatibility, C++98 accepts both C++-style f(int n...) and C-style f(int n, ...)

  2. 对于Variadic arguments的定义很简单的

    Allows a function to accept any number of arguments.

    Indicated by a trailing ... (other than one introducing a pack expansion) (since C++11) following the parameter-list of a function declaration.

    所以,这里官方的称呼是parameter-list而不是parameter pack,难道后者是特指模板函数的参数吗?我觉得这段话很重要
    
    // the function declared as follows
    int printx(const char* fmt...);
    // may be called with one or more arguments:
    printx("hello world");
    printx("a=%d b=%d", a, b); 
    int printx(const char* fmt, ...); // same as above (extraneous comma is allowed 
                                      // for C compatibility)
    int printy(..., const char* fmt); // error: ... cannot appear as a parameter
    int printz(...); // valid, but the arguments cannot be accessed portably
    Note: this is different from a function parameter pack expansion, which is indicated by an ellipsis that is a part of a parameter declarator, rather than an ellipsis that appears after all parameter declarations. 这里我的理解就是variadic arguments就是名副其实的参数声明而已,你无法使用parameter pack的功能来操作他,唯一的办法就是使用c语言的传统方式va_arg来访问他们。可是这句话让人费解就是function parameter pack expansion发生在函数调用,而这里是函数声明,两者似乎风马牛不相及,作者为什么要强调有混淆的可能呢?为什么不强调他和parameter pack本身的定义就不同呢?Both parameter pack expansion and the "variadic" ellipsis may appear in the declaration of a function template, as in the case of std::is_function.
    我能不能够得出结论就是parameter pack只是适用于模板函数?对于非模板函数就是variadic argument而已? 当我试图写下这个结论我就被自己打脸了,我刚刚做的实验不能使用parameter pack吗? 这个难道不能说明parameter pack也可以使用在非模板函数吗?何况还是lambda?我感觉要么我是头脑发昏出现幻觉要么是编译器不稳定?我刚刚编译可以在参数里直接使用auto expand=[](int&&...N)但是一转脸发现IDE崩溃了重启发现这个参数只能使用auto,难道说这个纯粹auto的lambda有模板函数的异曲同工的效果?
  3. 我又做了一个小实验就是要把这个lambda里的N的类型显示出来,比如总是报错说parameter pack不能expand,我对此只能解释是不是lambda的实现就是和inline一样,这里的auto...N实际上就是直接用调用的实参来替代,比如我的实参是一个数列int,int, int...那么这里直接就替换为了这个些实参?所以,我的结论是lambda里没有parameter pack?
  4. 这个例子里是否是parameter pack呢?我觉得make_tuple(34 + intseq...);是明显的function argument的行为 结果35,36,37,38,39,上看和创建array的效果是类似的,只不过tuple对于同类型的array来说是overkill

十一月十八日等待变化等待机会

  1. 一个初始化引出的“血案”。这个是现下流行的标题党的作风,我因为熟读头条也沾染了如此风气。事情是从这么一个小小的例子开始的。在那个遥远的boost王国有一个孤岛叫做numeric,孤岛上有一户人家叫做ublas,他们家祖上是有名的blas家族,一个世代研习线性代数计算的家族。不知何年何月他们的一个后代迁移到了boost王国在这么一个numeric的荒岛上定居下来。有一天一个从遥远的北方跋涉而来的探险者流落到这个荒岛上,饥渴难奈之中撞到了这户人家想讨碗水喝,主人家只有一个佣人在家就按照规矩给客人舀了一瓢井水,客人品尝过后大惊失色问道,你这井水为何有北方甘酿的味道。佣人粗鄙不知所措,只是按照主人吩咐让客人随意浏览欣赏院景。
  2. 客人看到这个vector_range和vector_slice很好用,其中slice是可以让你选择类似view的东西,有起始(starting),跨步(stride),和步数(step)来定义一个vector的局部 而且所有的对象都实现了ostream-friendly可以直接打印到cout,非常的趁手。
  3. 客人来到一个院落,佣人不无自豪的向客人展示虚数向量,客人从小对于虚数感觉很神秘,因为现实中并不存在这个稀罕之物,于是毕恭毕敬的准备实验一下结果发生了意外(以下为了省事决定都默认使用using namespace boost::numeric;原因是为了避免和std::vector冲突,已经默认了using namespace std;了,而大院的仆人的using namespace boost::numeric::ublas;会造成混淆。因此决定所有的ublas对象都添加ublas前缀namespace), 这么一个简单的初始化居然报错:class std::allocator >’ has no member named ‘construct’ 客人不明所以,仆人因为主人不在家并不回答任何问题,客人只能自己反复捉摸代码。发现如果你不分配vector自然就不发生错误,所以这个是和默认的allocator相关的,错误也明确指出了这个需要allocator来调用construct来分配对象。客人平时很少留意allocator的实现。现在不得不静下心来看文档。 找了很久才找到线索,使用最新版gcc-11或者最新版的boost都不能解决这个问题,从ublas的注解才明白可以不用ublas的默认的unbounded_array而是转而使用
  4. 什么是allocator?其实是不被大多数人所关注的细碎的东西。这个是他的construct的功能,平淡无奇的。为何平时创建任何对象客人使用默认的std::allocator就足够了,而今天遇到这个ublas家族的容器就出现了这个问题呢?客人看到boost::numeric::ublas家族的默认的allocator是一个所谓的(boost/numeric/ublas/storage.hpp)unbounded_array可是这个所谓的allocator的模板参数本身也是一个模板类,他只是一个傀儡,他也要别人传递一个真正的allocator来干活,而他仅仅是指挥而已,比如他会检查看看容器里的对象是否是一个trivial然后就决定调用allocator的construct来进行复杂的创建工作: 那么这个 has_trivial_constructor是个什么鬼?其实它不过是boost依靠SFINAE实现的std::is_trivial。 所以,逻辑上是对的因为Trivial default constructor决定了是否你可以把allocator.allocate的内存直接cast成对象。这里是一个常识,对于allocator来说allocate方法从来不是什么大问题,基本上就是传统的malloc之类的根据对象类型大小的依照参数个数来分配。关键是这个construct值得思考 这一段代码说明什么?所谓的construct就是调用类型_Up的默认的ctro然后把他的参数带进去给new就返回了。这个似乎是每一个学习c++的初学者都是这么做的,有问题吗?客人不禁疑惑了。这时候注意到一个奇怪的现象,编译器一直抱怨这个std::allocator的construct找不到的原因是这段宏给屏蔽了 客人目前使用的是gcc-10.2.0,是刚刚从“铸剑谷”里从gcc最新代码自行编译的来的,难怪google半天没有人有这个类似的问题,因为大多数人使用的编译器可能还是c++17或者以前的。在这个宏下有一行小字,想必是当年打造这个“封印”的前辈的给后人的告诫:2103. std::allocator propagate_on_container_move_assignment
  5. 看着这行魔咒propagate_on_container_move_assignment客人茫然不知所措,这个实在是超出了他的见识。回到“魔法公会图书馆”看到这些高深的内功心法客人无法领悟。只好去“江湖酒馆”去打听“江湖传言”,客人找到了这个“上古卷轴”,瞬间仿佛看到了黑暗中的一丝光亮。
  6. 研习这篇“上古卷轴”是否要先去看看这个“瑶琳仙境”呢?
  7. 所以propagate_on_container_move_assignment是一个c++的设置,它决定了如何应对move assignment的情况。大侠说的很明白这三种情况要怎么做,只不过这个相当的高深,没有这方面经验的人士还是放过吧?

    move assignment operator

    The container move assignment operator must deal with three separate possibilities:

    1. propagate_on_container_move_assignment is true.
    2. propagate_on_container_move_assignment is false, and the allocators from the lhs and rhs compare equal.
    3. propagate_on_container_move_assignment is false, and the allocators from the lhs and rhs compare unequal.
    那么我的编译器是如何设置propagate_on_container_move_assignment的呢?我在我的gcc-10.2.0里找到了这个设置(include/c++/10.2.0/bits/allocator.h) 这个是gcc-10的正确设置吗?难道c++20有什么解决不了的问题所以才延后了吗?
  8. 这位大侠的解释似乎让人有些理解吧?

    If the trait weren't true, then assignment operations would need to perform a runtime check on whether the allocators are equal. Yes, of course the allocators would always be equal, but the code doesn't know that and would still have to perform the check, and thus you cannot offer a noexcept guarantee. With POCMA = true, you can statically know that you will steal resources and thus won't throw.

    这个大侠指出的问题的根源是值得阅读的,尽管这个我认为我根本看不懂。这里我的疑惑是看起来本届的c++委员会已经给出了结论和修改的方案,那么是否最新版的gcc已经改了呢?我使用的是正式release版的10.2,看起来没有变化,于是我决定下载最新的snapshot来试试看。为此我不得不对于这个编译脚本稍微修改,理想的是引入参数,可是我懒得动脑筋学习脚本的参数,就拷贝了一个。趁着编译gcc我出去散散步吧。
  9. 新版编译器发现了之前的这个问题。这个再次印证了一个理念就是c++的ctor不是函数,也就是{}()更好的一个原因新编译的gcc-11并不能解决之前的问题。我觉得如果c++20不打算让std::allocator开放construct,也就是说所有使用者必须自己实现解决POCMA的自己的allocator,原因应该是要保证noexcept的承诺吧?毕竟在ctor发生异常是不能接受的吧?那么是不是ublas要自己在unbounded_array实现这么一个allocator呢?
  10. 其实tag dispatch在我看来就是使用模板函数的典型,为什么要特别强调呢?不过仔细体会就意识到这的确是一种技术,原本需要定义模板函数才能做到的派遣,现在通过传递不同参数的类型来重载函数做到的,这个是一种优越于动态虚函数的方式,可以传递一个无用的类型来改变函数原型,和模板函数有异曲同工之效,只不过你必须要实现它。好处是编译期就能够发现其中的错误吧?我不确定像例子里的所有的类型都必须定义?编译器是否会去检查呢?这个tag真的是编译期确定的呢?
  11. 在开始学习线性代数之前先把这个概念明确一下:Trivial default constructor,, 根据定义

    The default constructor for class T is trivial (i.e. performs no action) if all of the following is true:

    • The constructor is not user-provided (i.e., is implicitly-defined or defaulted on its first declaration)这就是为什么我们有了ctor=default的用意了,因为以前程序员写了一些空空的ctor,虽然只有一行注释也许编译器也不是很确定吧?
    • T has no virtual member functions
    • T has no virtual base classes
    • T has no non-static members with default initializers.
    (since C++11)
    • Every direct base of T has a trivial default constructor
    • Every non-static member of class type (or array thereof) has a trivial default constructor

    A trivial default constructor is a constructor that performs no action. All data types compatible with the C language (POD types) are trivially default-constructible.

  12. 相应的对于Deleted implicitly-declared default constructor这个概念一开始看名字还是挺难理解直到看了这个例子才明白其实挺直观的,如果你有reference或者const成员你没有初始化,那就只能等着ctor了,而你不定义那不是找碴吗? 对于default ctor被“删除”这个特性我觉得可以很好的利用,以前对于一个你不愿意使用者能够任意创建的类的方式是创建一个private ctor来防止被误创建对象,现在如果对于缺省的ctor,如果你不想编译器去替程序员创建的话,那么就定义一些const或者reference的成员变量,这样子就不能任意创建这个对象了。
  13. 常常读到这个ODR现在才明白是One Definition Rule
  14. c++层出不穷的新feature让人绝望:这个三方比较<=>就是让人心惊肉跳,结果又有一个什么default comparison operator,这个当然是非常非常的贴心的设计,简直感动的我说不出话了。比如我随便写一个类就不用操心怎么把它放在set里了。 当然对于这个只有一个static的成员变量比较大小是徒劳的,所以使用set总是只能存放一个元素
  15. 这个Complex conjugate vector space实在是不好理解。还是出去散散步吧。
  16. 今天听油管催眠在睡着之前听到大侠说到elf里的segment是只使用于executable,而section是只用于shared library的segment is for runtime, section is for linking time。大侠指出很多人都对此搞错了。

十一月十九日等待变化等待机会

  1. 我居然对于这么好用的span一无所知,实在是太不应该了!这个模板类我觉得设计的挺好的,特别是贴心的类型推导省却了模板参数,想想看吧,如果在使用的时候要我事先添加模板参数我估计这一辈子都不愿意再学习它了,这个就是我昨天还在想像中的vector_view之类的东西,这里的span名字对于我来说是有些误导的,我以前的概念里似乎view是一个更加贴近的名称?这里还有一个意外的收获,如果你要把一个vector初始化为一个序列并且在第一个位置返回长度要怎么写代码比较快? 要达到这个效果10,1,2,3,4,5,6,7,8,9,我以前还没有想到可以这么写。 结果一转头发现其实我为什么不利用std::iota这个函数我接触过可是始终不明白函数的名字是怎么起的,到底是什么缩写啊?这么难记!并不是我一个人有这种困惑。原来名字来源于希腊字母Ιι(Iota)?不过iota没有被收录到ranges里确实是一个遗憾。
  2. 那么针对span那么多的创建方法总有一款能够满足你的需求的。但是仔细阅读发现用view来形容span是不准确的,至少是不全面吧?因为它的内部机理是依赖于range的概念使用最少量的数据来描述一个对象range,通常可以是起始结束指针,也可以是起始和长度两种方式,所以,span这个词有着独特的视角,可以“扩展”的,这个概念也许是dynamic的由来?因为view通常引申为只读,而span可以修改的。并且从c程序员的角度来理解就是一个很轻量的指针对或者指针长度对子,根本没有什么生命周期的概念,所以也不要费心的想着原来对象被消灭span怎么失效的烦恼。
  3. 从面向对象设计出发的人往往认为std::array既然是一个类那么很自然的它的资源有可能在destructor里被删除,如果看定义它也有一个数据指针所以,很自然的我认为它是动态分配的内存,所以,我本来营造了一个例子想要说明std::span使用一个std::array的镜像的时候要小心不要跨越声明周期,很自然的我期待程序会crash,可是没有!为什么呢?难道是被释放的内存偏巧还能使用?显然不是的,这个揭示出了“望文生义”的危害,std::array has more than it meets your eyes. 这里使用to_array只是想增加一些花絮,其实并没有本质的改变,不过我们可以借此看一看如何做到的。首先一个出人意料的问题是我找不到array的ctor!它没有,因为。。。它是根据aggregate initialization,换句话说就是array是POD,所以,遵顼c语言的初始化,本来嘛array原本就不应该被误解为dynamically allocated,这个实在是我的莫名其妙的印象。那么我们去看to_array的代码和我之前使用index_sequence创建array是一样的做法,也是利用parameter pack的展开配合move来优化的,这个对于一个复杂对象数组是有很大的优势的。对于integer这种是看不出来的。明白了这一点我就意识到std::array是对于built-in array的一个很薄薄的包装,那么不小心的程序员会在栈随手声明一个大的array来导致栈的overflow的,比如 这个几乎注定超过了栈的大小设置。所以,之前我的测试程序没有导致无效指针的收益不是免费的,它是建立在本地栈里的临时内存使用的基础上的。有一利就有一弊
  4. 这里有一个小的诀窍,因为定义std::array需要知道长度,所以,你很难直接定义一个std::array<char,5>="hello";为什么?不要看错"hello"是一个6个字符的数组因为还包含了末尾的null,这个是基础的基础,可是难道我写代码前要去数一下字符吗? 我发现array不支持=作为constructor的做法,因为它牵涉到类型转换,比如 第三种形式总是报错这个要怎么理解呢?我只能说从aggregate initialization来理解
    T object = {arg1, arg2, ...};
    T object {arg1, arg2, ... };(2) (since C++11)
    T object = { .designator = arg1 , .designator { arg2 } ... }; (3) (since C++20)
    T object { .designator = arg1 , .designator { arg2 } ... }; (4) (since C++20)
    T object (arg1, arg2, ...);(5) (since C++20)
    得益于我使用最新版的gcc-11的最新的snapshot版本我可以使用以上所有的形式,所以,我们看到没有传统的ctor的=来作为这个aggregate initialization,所以,我们如果使用实际上是运行了类型转化。这就是报错的原因。
    所以,你可以这样子不用给出array的准确长度,这个要比随意给出一个很大的长度来的优越,因为我们已经知道std::array是栈里定义的临时变量,而且不像std里其他动态分配的容器那样本地仅仅是一个指针,它的全部数据就在栈里,所以和随手声明的builtin array一样可以导致栈的overflow。注意这里的to_array是没有运行期开销的,因为它纯粹是一个模板函数是编译期计算得到的。
  5. 回到最早的关于span生命周期的问题我是碰巧使用了array这个特殊的非dynamic的range,如果换成vector就暴露了span使用stale的指针的危害 运行的结果是不重要的因为我的系统比较不够幸运没有crash,但是还是看出来数据是corrupted
    1,2,3,4,5,6,7,8,9,10,
    1,1,4,5,6,7,8,9,10,11,
    这才是最不幸的,因为程序没有crash而数据是不准确的,你可能根本没有注意到!
  6. 偶然看到unlinkrm异同,这个的确有些是我以前没有意识到的,比如前者是不会默不作声的如果参数文件不存在,这个就是大家使用rm -f在makefile的原因,因为很可能什么obj文件都没有可是我并不关心这个,所以就闭嘴好了。联想到有问题是程序失败是因为删除文件的权限不够,可是这个时候如果你使用的是unlink也许就能避免这个问题,因为不会返回错误的,因为unlink本质上仅仅是减少引用计数,能否最终删除本来就不是你关心的。这个时候对于一些service程序来说是有好处的,因为往往因为删除一个文件被卡住导致整个程序卡壳是不值得的,所以,使用unlink不要使用rm。
  7. 看到cout代码专门针对nullptr有一个输出就是输出的结果就是nullptr
  8. 有一个小小的问题就是不要使用string literal的sizeof因为它会计算额外的terminated null,而如果你直接把literal作为参数给string,它是不计算的。这个我平常都是习以为常,可是仔细一想又有模糊了。看来是我记忆力的问题。 前者是12后者是13。
  9. 对于这个user defined literal operator我感觉比较难以掌握,还是以后再研究吧? 比如这个代码到底什么时候能够调用string_view的这个literal operator呢? 这个长度是怎么获得呢?难道是编译期计算的结果???
  10. 我对有std::variant和std::optional感觉费解,因为当时boost的optional我就不明白怎么回事。找到这个很不错的blog,理解一下。看了好久才结合代码理解了一些,所谓的std::in_place_type根本就是一个类似于placeholder或者flag之类的作为特别参数来调用非正常的ctor,这个和引进rvalue reference的&&的用意有些相像就是为了调用特定的函数的办法,那个in_place_type本身不起什么作用就是一个tag dispatch的效果。而variant有点点的像是tuple,它使用我们熟悉的type_traits里的基本方法靠fold来返回index_of, 这类东西我们已经做过很多次练习了吧?

十一月二十日等待变化等待机会

  1. user defined string literal的确是一个很神奇的东西,比如我们以前总是为了字符串里有特殊字符比如null在中间导致初始化出现问题而一筹莫展,现在完全可以解决。 这个结果是我们不希望看到的7:c-style要怎么解决呢? 同时我们在调用的字符串后面加上后缀_sstring str1("c-style\0string"_s); 结果就是我们需要的14:c-stylestring 这个实在是很神奇的一个实现!
  2. 我似乎是突然发现了一个“金矿”valarray因为我之前的印象中对它评价不高觉得毫无用处,还不如直接使用builtin array来的方便,现在看起来似乎非常的肤浅的看法,我以前对于文档说明一定有很大的理解的问题。那么对于怎样使用valarray作为二维数组呢?我脑子好像一下子转不过来看了这里才突然发现最近脑子生锈了这个通常的做法很奇怪吗?
  3. 这个对于matrix的样本范例是一个值得学习的,我做了一个小小的练习
  4. 这个mask_array是非常神奇的东西,它的default ctor是被删除了,那么它的创建是一个内部的array<bool>这个是比较容易理解的,用一个指针和长度描述原来的array,然后就是一个bool的array。现在看起来是比较容易懂的,可是第一次看到例子里居然可以依靠比较操作符就产生一个数组,并且它有可以作为原来数组的operator[]的操作数,这个实在是让人震撼
  5. 在众多的shift中,我当初最钦佩rotate,那个代码是在不分配新内存的情况下用时间换空间。
  6. gslice是一个很深奥的东西,我打算明天再看吧。

十一月二十一日等待变化等待机会

  1. 看一个关于bug的视频,其中一个有意义的是这个-fsanitize-address-use-after-scope根据这个gcc官方的说法我们应该使用-fsanitize=address来激活它,有趣的是这个编译选项会影响到我的pch的部分,很显然的gcc的编译选项必须保证pch和源代码部分编译是一致的,所以,需要重新编译pch。 主讲人提到一个这个是否可以编译的问题string(foo);的确这个是会被当作声明而通过编译,但是我的版本的gcc会报出警告unnecessary parentheses in declaration of ‘foo’ [-Wparentheses]所以,这是一个安慰。
  2. 所以,从这里引出了一个好东西是ASAN_OPTIONS
  3. 我想用valarray来操作帮助运算sudoku,可是我意识到gslice实际上是不允许你单独访问任何一个元素的,它的操作符都是视同所有为一个整体,这个的确是自然的,大部分的需求来自于整体的操作,如果你需要多维数组,最好就是使用多维数组。也就是说这里没有sub matrix的概念,我需要看看ublas是否有。现在回过头来学习valarray再来看ublas是一个很必须的补课。这里对于gslice的使用还是很生涩,需要多练习一下。而且我想把gslice_array存储在一个valarray里面是不行的,因为这种proxy类的default ctor都是被禁止了,感觉由一些类似于reference的概念,它没有依附于一个valarray是不能创建的。这些proxy类的实现相当的不简单,我感觉里面是一个什么expr的内部的类来实现的,难道这个和ublas的概念是一样的?只是表达式?
  4. 这个编译警告很有用-Wshadow-compatible-local
  5. 我对于unique_lock不熟悉。
  6. 这个视频里提到了c++coreguideline的重要性。
  7. 我看了这个nightmare of c++ move之后感觉自己好像从来没有学过c++一样无助。然后我看了这个nightmare of c++ initialization之后我差点要吐了,
    一个integer居然有十九种不同的initialization的方法,我以前读孔艺妓吹嘘“回”字有五种写法觉得可悲没想到我比他要可悲一千倍,因为十九种方法我还有好几种闻所未闻。我觉得我以后不要再跟人说我学过c++了,因为我压根儿不配。
  8. 这个应该是一个人人都应该知道的技巧,好像我还是一无所知,就是利用reference_wrapper可以储存在容器里,比如这个很好的例子,我们可以用 来创建一个类似于view的vector,因为很多的操作比如sort,random_shuffle需要使用random access的容器,而一个list无法做到,但是对于频繁在中间插入新数据又让我们不得不使用list,那么怎么排序使用他们呢?也许用key/value这样子的map是一个解决,但是map的insert其实是很昂贵的,set压根不允许你修改数据,只能删除添加,总之有很多时候存储最原始的数据list是足够胜任的,至于要排序我们可以创建一个它的元素的reference的vector的轻量级的view,所以,这个是一个很不错的选择,但是以前我从来没有想过要怎样把一个integer的reference存储起来。我看了一下reference_wrapper的实现是依赖于std::addressof的帮助。那么它有什么优势呢?比如它做了什么能够比&多了什么呢?这个例子是让人印象深刻的。就是说假如操作符&已经被重载了,这个时候我们似乎无法取得地址,在黑暗中有std::addressof还在闪闪发光,因为它使用的是内部的builtin的addressof不受操作符重载的影响。比如我不知出于什么原因不想让你取得我的类的地址作为指针,比如我讨厌c程序员那么霸道,我只希望你使用引用就这么干了 这个是结果
  9. 我对于标准库的thread非常的缺乏了解,工作中基本上满足于最简单的pthread的应用,对于同事的对于它们的包装代码从来就懒得研究,现在看来需要补课一个星期才能熟悉。

十一月二十二日等待变化等待机会

  1. 我想尝试一个奇怪的想法,就是能不能在递归函数里每次都递归创建一个jthread来递归调用。这个很可能是违反计算机常识的无用功,但是why not?结果我发现几乎不可能,首先就是我无法通过jthread返回任何的结果,因为它的invocable必须保证参数都是rvalue reference,结果我想通过返回参数进行递归调用就不行了。也许可以通过capture的全局变量来实现,可是这个太麻烦了,几乎不可能,我要定义多少全局变量呢?何况我的线程的目的就是收集一些结果来返回,所以这个是不可能的。这个过程还是有意义的尝试,因为我一开始搞不明白为什么编译器总是抱怨我的参数无法invocable,于是我借助static_cast先保证我的函数原型依照参数类型的确是invocable的,然后一个一个参数去替换实参的decltype来看看是哪一个实参的类型导致失败,结果发现是reference的实参有问题,然后加了std::ref虽然通过了invocable的测试,但是jthread依然报错才意识到参数都是被std::forward保证必须符合rvalue-reference了,因为jthread的ctor是一个模板要求参数必须是rvalue-reference我debug了几乎一天而最后才发现应该是我的愚蠢的手误,我想到了std::ref却花了很久才意识到我也需要std::cref 这里的 std::forward<_Args>(__args)保证了传递的参数是rvalue reference,所以,如果我的函数原型参数类型定义了lvalue,那么是通不过的。所以,一个天真的问题就是:一个线程如果不能返回什么有用的信息我要它做什么呢? 这个问题并不是jthread特有的,thread也是如此,那么我犯了什么错呢?我的错误就在于忘记了std::cref因为所有的reference都和rvalue是不同的。所以,最后我是这样子才能使用jthread。首先lambda递归函数的定义是这样子的 那么我的调用就必须是这样子的
  2. 感觉我的问题只能依赖于std::future来解决,可是thread应该怎么使用呢?
  3. 其实我的另一个最大的失误在于对于recursive lambda的怀疑,虽然我已经一直在使用这个std::function来依靠capture实现的recursive lambda的途径,但是面对std::thread arguments must be invocable after conversion to rvalues的错误我首先怀疑的是因为递归函数指针的问题而不是参数的问题。这个导致了我debug了几乎一天。
  4. 实际上我对于这两个函数的概念始终是模糊的,其实是一目了然的但是我始终就是一团浆糊,std::forward是针对实参的,而std::decay是针对型参的类型函数,为什么我会把这两个扯到一起呢?现在想起来应该是脑子混乱不堪,同时也暴露了这个正是HANA之类的meta programming最难的地方因为把两者夹杂在一起的编程,尽管威力巨大但是很容易让人混乱。对于std::decay实际上是很明确的:
    • If T names the type "array of U" or "reference to array of U", the member typedef type is U*.
    • Otherwise, if T is a function type F or a reference thereto, the member typedef type is std::add_pointer<F>::type.
    用自己话来说就是函数和函数引用要转化为函数指针;数组和数组引用也要转化为指针;去除所有的引用和const/volatile,它的目的是因为类型必须是最最单纯的类型才能接受static_cast转换为rvalue-reference的type。而std::forward是为了perfect forwarding的应用,我虽然花了好几天学习明白了最主要的动因,可是在细节方面依然不是很明确,所谓的perfect forwarding的目的是什么?
  5. 我以为我明白了其实我还是不明白!一个月前我有这样的感叹,过了一个月对于同样的问题我依然有这样的感叹!实际上我对于c++的误解很多来自于对于英语的误解,这个是中国人学习西方知识的缺陷,为什么呢?我对于perfect forwarding的概念要先从英文来理解。为什么我们有这个问题?其中的一个核心是对于什么样的参数类型能够被任何函数原型所接受呢?这个是一个非常基本的类型兼容的问题,但是这个是最最基本也是最最重要的概念。一个const ref的型参可以接受所有的实参。但是对于rvalue reference来说它不能接受lvalue,也不能接受const的参数。那么为什么使用模板就可以做perfect forwarding呢?

十一月二十三日等待变化等待机会

  1. 这个是一个关于range的练习题,顺便重温一下lower_bound/upper_bound,我脑子有些生锈想不清楚strict ordering/semi strict ordering,总之,本来想使用现成的函数结果发现更麻烦,而且我明确的知道有一个bug,可是我实在是懒得debug了。
  2. gsliceslice看起来是类似的,我指的是表面上用法可以是类似的,其实不然,我断断续续折腾了大半天才明白其中的奥妙。比如这个例子里给你一个很好的例子,于是我也照猫画虎想要制作一个方便操作sudoku的办法 这个是一个获得9个小方块的gslice_array的小lambda,可是它会crash,而类似的使用slice获得row/col则不会。为什么? 原因是gslice是一个想对复杂的机构,其中的所谓"index"都是动态分配的,拷贝之前就已经把临时变量给释放了。所以gdb之后明白了就只能这样子把gslice编程非临时变量了。
  3. 其实想明白了这一点,也就明白你的小lambda不应该返回slice_array/gslice_array,这个太危险了,因为你需要的其实仅仅是slice/gslice的产生,尽管这个通常很简单的,不值得写一个lambda,但是对于sudoku的gslice还是挺麻烦的,所以是值得的。因此,这个是我产生各种方便检验sudoku规则的lambda 注意这里gslice是存储在一个数组里就不存在原来例子里临时变量被释放的问题了。

十一月二十四日等待变化等待机会

  1. 似乎是一个很常见的问题,就是如何产生所有的子集。一开始我想着使用递归,可是似乎没有必要。相应的产生固定大小的子集就是一个过滤的问题了。
  2. 这个视频讲述move的含义,我以为我可以很轻松的听讲座了,可是在实现move assignment的时候,主讲人提倡大家先释放自己的资源在去做assignment,结果有人提了一个非常好的问题,我感觉自愧弗如,就是对于self-assignment就糟糕了,比如t1=std::move(t1);能提出这样的问题的人确实很不简单,当然从意义上来说是无理的,但是实践中你难免会有发生,所以,应证了那句名言,c++从来不禁止程序员自己开枪打自己的脚。所以,针对这种问题,程序员要自己保护自己做检查。
  3. 第二部分的视频难了很多。

十一月二十五日等待变化等待机会

  1. 捉摸着怎样使用dancing link 算法来解决sudoku。。。我看到gcd的实现不是教科书上的euclid的连续减法,而是直接使用求余数,的确这个对于cpu来说和减法差不多,但是快得多了。而lcm是依赖于gcd的。另外mid_point可以避免overflow。
  2. 证实了我之前的疑惑就是纯粹的auto的lambda叫做generic lambda,其实是一种模板,这个对于我是一个好消息,可是想想看这个东西已经出道六七年了我才第一次听说,实在是孤陋寡闻。很快的我就遇到了第一个明显的问题,调用lambda不能像普通模板传递参数的方式,因为这里大侠指出lambda实际上是一个functor,所以是针对它的operator()的模板而不是类的模板的。 这个新的语法让我感到可怕,我的IDE也快要崩溃了。我最后加了一个constexpr让这个怪物一槌定音。
  3. 相类似的我以前已经反反复复在为了把index_sequence直接转为vector而烦恼比如这个是违法的 之前我是定义了一个简单的模板函数来协助,现在有了generic lambda可以定义模板lambda可以简化为 同样的对于integer_sequence也可以做类似的动作
  4. 从这个很棒的网站下载古老的游戏。使用wine似乎很不容易,于是使用dosbox,在其中又需要mount cd/iso

十一月二十六日等待变化等待机会

  1. 所谓的钟摆就是相邻数字之差严格遵循忽正忽负,所以,用一个函数来表达就是这样子的 因此,寻找任何一个最长的钟摆可以允许你删除若干个数仍然能保持“钟摆”。接下去寻找所有的最长的就是有些麻烦了。
  2. 对于最大连续子数组的问题相对容易些因为连续降低了难度,我准备散步回来再改进能够返回子数组的解法,并且看看不要求连续的话?这个好像无意义,因为不连续你就直接把正数加起来就得了。

十一月二十八日等待变化等待机会

  1. 我觉得这个from_chars网页是不是有问题,因为我在c++20(gcc-10)的头文件里看不到double,float的overload了,难道说应该转而使用别的函数吗?
  2. 我看到这里提到c++20应该使用std::format这个我对于boost的对应部分似乎还有一个模糊的印象。可是出乎我的意料这个也在gcc里没有实现,根据这个功能没有什么编译器实现了,我的理解就是c++标准也许是通过了,可是实现没有跟上,或者不是那么急迫,或者实现者有先后取舍等等吧

    The new format library {By the time of writing, no compiler supported the library and the examples were implemented with its prototype version: the fmt library. The format library combines the expressibility of the format string with the type safety and the user-extensibility of stream I/O and adds the opportunity to reorder the arguments in the output.

  3. 总之一早上就发现gcc-10有两个没有实现的功能也算是个运气了。
  4. 我没有忘记关于libstdc++的问题,之前的gcc的status比较笼统,这里是特别针对c++的status page,也许这个更有针对性?我不知道。
  5. 居然“阿三”(libasan.so)是动态链接的?我看到静态库,默认还是会动态链接的。这个是编译指令里-fsanitize=address隐含带出来的?
  6. 我觉得这个libstdc++的configure值得读一下。
  7. 单独编译libstdc++-v3是没有可能的,或者是非常困难的,或者是几乎没有意义的,因为它是gcc的紧密的一部分,而且贯穿了整个gcc的编译的各个过程,我把之前编译gcc的脚本分步执行发现下载,配置都很容易,gcc编译支持non-inplace的configure/build,所以,你只需要把--srcdir=... --cache-file=./config.cache作为configure的参数就可以正确的在任意目录下编译,前提是你把gcc需要的四个dependency:gmp,mpc,isl,mpfr都放置在gcc的source的目录里。但是gcc的编译过程非常的复杂,单单对于各个stage的时间戳来看libstdc++-v3在三个目录下出现过stage1-x86_64-unknown-linux-gnu,prev-x86_64-unknown-linux-gnu,x86_64-unknown-linux-gnu我没有仔细比较三个不同的stage下是否配置命令不同,但是单单第一步就是配合libcpp等等的产生,单独run正确的配置命令不解决问题,因为它会检查父目录里那些文件已经编译完了,根据配置文件的不同configure的结果是不同的,所以,单独编译libstdc++-v3是不可能的。它是gcc/g++的一部分,而编译器的产生绝非一个独立动态库那么简单。我这个时候其实已经意识到我的错误想法,如果你想了解c++的语法parser,这个不是地方,因为这个是c++的stl,从名字也能猜出来这个是库,而语法是gcc的一部分,它在libcpp里面。
  8. 无意中搜到了这个大侠的博客文章是关于怎样写gcc的plugin的,很有趣,我看了个开头,真正的细节我就看累了。我粗略的搜了一下invoke_plugin_callbacks感觉就是很失望了,因为定义的callback节点类型就不多。让我相信看过的一篇文章关于为什么llvm在这方面优越。
  9. 我从来没有看过gcc的online doc。这里是一个c++的小历史。
  10. gcc的内部文档似乎没有很多的改变吧?

十一月三十日等待变化等待机会

  1. 一日不练手生
  2. 我又把之前的想法搬出来,可是意外的发现了abi::__cxa_demangle的一个极限吧?这个我本来以为是libstdc++-v3的实现,发现是在libiberty里的实现,其中的注解评论说也许可以使用libcxxabi 这里的TypeName是我对于__cxa_demangle的一个小包装
  3. 我相信llvm里libcxxabi是一个好的选择,,但是单单编译链接它的静态库无法直接体现,原因是同名同姓的libiberty是c++的首选,我还想不出什么好办法,除非把libcxxabi里更名,但是这个绝对是很苯的方法。总之,有谁需要打印一个模板参数有一百个类的类型名的demangle呢?我自己去hack替换gcc的libiberty是有一点点托大了。

十二月二日等待变化等待机会

  1. 我的练习增加到了几千行的时候eclipse基本上开始出状况了,我只好把代码备份起来,这个著名的“墙和灯光”的难题花了很多时间,主要是我想要找到一个方便的工具来处理二维数组的问题,本来我以为我可以很满意的使用valarray,结果最后发现了一个瑕疵,就是slice_array虽然重载operator= 可是没有重载+=这个让人非常的郁闷,因为两者完全不一样,前者的操作符是元素,后者是另一个valarray,这个实在是让人尴尬。 在算法的效率与代码的好看之间我宁愿选择后者,就是说我也许会使用valarray[slice(start,size,stride)]=sum来创建一个新的valarray,然后再把rowAry+colAry-lightAry=finalAry。

十二月三日等待变化等待机会

  1. 我今天早上学到了一些试图改掉了我一个长久依赖的缺点。每天进步一点点就很满足了。
  2. 关于这个题目虽然简单我做的不好,因为我脑子里有误区,对于这种不会回头的“直线”搜索反而不明白,其次,我虽然也意识到了路径总数是一个个累加的,但是脑子里转不过弯的是不知道是否循环可以覆盖所有。归根到底还是这一类的类似与动态规划的题目有障碍。我把它当作深度广度搜索来做就是落了下乘了。
  3. 对于这个题目我一开始认为有什么数学公式可以计算,可是。。。后来看来大家的算法都是循环,只是内存施用量上的比拼,我使用超级笨重的vector,后来使用valarray,主要是想
  4. 我觉得神情恍惚,难道我在做梦吗?我对于我的记忆开始怀疑,难道array什么时候可以支持变量size?VLA是什么时候被引入的?难道这个是gcc的扩展?似乎是的
  5. 对于这个题目的理解错误来自于对于离散数学的基础问题,就是说一组数字的最大的能被3整除的和的理解上。任何一组正整数数字的被三除其实无非余1,2,那么至多剩下一个或者两个数字,因为根据鸽子原理三人行必有我师。

十二月四日等待变化等待机会

  1. 早上一起来对于这个问题就想了很久,我想我的天真的算法是可以的只是老是有什么错误,并且非常的慢。

十二月五日等待变化等待机会

  1. 对于这个forward_list我是一无所知!看这个例子里的splice_after让我吃惊的是加入的iterator居然是"before"的iterator,后来仔细一想就明白其中的原因,因为这个是单向的linklist这个是必须的,否则你怎么处理?
  2. 这个erase_after更是清晰的说明了在forward_list里删除节点的限制,一定不可能是如其他容器一样的inclusive,必定是exclusive。
  3. 我一下子有些惊奇forward_list没有实现size的功能,而且为了做一个类似push_back的动作是如此的昂贵所以这是一个非常有意义的对于vector的补充,因为push_front/pop_front是它的优势。
  4. 对于forward_list实现的sort我很是崇拜,因为不知道怎么实现的,代码看不懂因为两个merge的list是从何而来的呢?我所能想到的就是直接把link list的元素的reference放在vector里去sort,这个肯定是端不上台面的想法。不过在我实验的时候发现,这个要小心,比如我们使用shared_ptr来包装link list之后是否依然要求使用reference_wrapper来包装呢?我似乎遇到了问题,也许是我的测试代码的问题,但是也许这个是错误的做法,比如这样子就可以不用reference而直接拷贝多个了 这个时候你是否需要还是简单的似乎前者会有内存错误,我感觉shared_ptr包装下是可以直接拷贝不需要额外的reference的,这个从我的全局变量counter的计数来证明我们并没有创建多余的拷贝,shared_ptr本身的多个拷贝是无关紧要的,于是我首先使用vector来sort,然后再重新创建我的linklist,这样子虽然麻烦,可是总比你去对于链表排序来的容易的多了吧? 初始化之后放在vector里 然后排序 重建linklist
  5. 如何使用现有的forward_list来实现它的排序呢?我采用insertion sort来实现一下。
  6. 那么对于怎样逆转一个linklist也是很容易实现的
  7. 这个insert_aftersplice_after看上去很像,似乎两者也可以互换,无非就是后者多了一个object。其实不然,仔细看才发现后者是"move"前者是copy,所以是截然不同的操作。这一点很重要,因为后者的要求是搞了很多的,如果目标区和来源区重合是UB了。

十二月五日等待变化等待机会

  1. 我对于span和view之前就没有搞明白,现在终于可以学习一下了。首先,我觉得span就是一个view,因为存储了一个指针和大小,这一点可能是它得名span的原因吧,数学上可以使用这个span来“延拓”?
  2. 看到一个具体的小方法first居然有两种类似的方法形式,一个是模板,一个是带参数,这中间的差异实在是微妙!我对于dynamic的理解就是大小为size_t(-1)的一个特例。就是不定。那么到底意义如何呢?是编译期不可知还是什么原因呢?模板的形式是返回确定类型,注意大小是类型的一部分,所以,是一个static的吧?而非模板的是返回一个dynamic就是类型里的大小是一个“无限大”。所以,归根结底我需要理解dynamic/static的本质差异。

    The class template span describes an object that can refer to a contiguous sequence of objects with the first element of the sequence at position zero. A span can either have a static extent, in which case the number of elements in the sequence is known at compile-time and encoded in the type, or a dynamic extent.

    If a span has dynamic extent a typical implementation holds two members: a pointer to T and a size. A span with static extent may have only one member: a pointer to T.

    这里解释的多么清晰明了啊!我的疑问再次阅读定义就得到了解答:static extent的成员数是编译期已知的所以成为类型的一部分,所以,
  3. 所谓的std::dynamic_extent就是一个最大的size_t
  4. 我一直以来对于size_t和int有着模糊的认识,这里的一句经典打消了疑虑。

    It is a type able to represent the size of any object in bytes: size_t is the type returned by the sizeof operator and is widely used in the standard library to represent sizes and counts.

    我自己的话就是说,size_t的目的是表达编译器理解的所有能够表达的对象的大小是sizeof的返回值类型,这个可能大于平台相关的unsigned int的大小,因为后者仅仅是平台相关的,而前者还是和编译器的实现相关。所以从这点来看我觉得size_t也许更加容易overflow,因为我觉得sizeof不需要太大吧?当然定义上说也许比int还要大。
  5. 我对于first来做一下小实验吧。首先对于一个array的span的类型是什么呢? 首先s1的类型是一个标准的span就是说一个static extent的span 而对于非模板的firsts2的类型是什么呢?很明显这个是一个dynamic_extent我故意使用危险的参数15这样一个超过原来类型大小,编译器是不会替你检查的,也不保证运行期的正确与否,因为也许运行期会有变化吧?看了一下代码检查是否超越size()这个就是extent,而对于dynamic它是无限大-1,所以,永远不会出错。对于dynamic来说和普通指针无差别,没有任何的保障。 那么对于模板的返回类型呢?s3是一个新的static的span类型只不过extent或者size不同。 可是让我不太满意或者说意外的是对于static的也没有任何的保护,比如s3[4]是合法的!虽然s3的类型是可是operator[]检查的仅仅是extent不为0,这个简直就是没有什么用,仅仅能够防止没有初始化的误用而已。这里我想用array来做对照,同样地也没有用什么static_assert来检验因为这个绝大多数都是运行期的错误,为了一点点的编译期可以明显觉察到的错误而枉费心机是无意义的。 为什么对于static_extent明明可以在编译期就明确知道越界的行为却不去制止呢?也许我们应该再次引用大师的名言就是c++不会防止程序员拿枪打自己的脚!
  6. 关于string_view我一直有一个小小的错误想法以为它就是一个像string一样的模式,大多数时候没有人去直接使用basic_string实际上你完全可以去specialize一个basic_string_view实际上为什么没有人去使用basic_string来承载一个int呢?我觉得原因就是没有什么大用处吧?它和vector有什么优势呢? 比如对于一个数组 你可以使用basic_string_view 或者也可以使用basic_string 可是这么做和vector相比无任何好处,你还是要自己重载operator <<所以,没有人这么去想。这些好处都是在char_traits里实现的。
  7. 不言而喻的view是一个const的span吗?其中最重要的保证就是前者的数据指针是 而后者则没有这个const要求但是这一点并没有阻止你创建一个const span比如 这个span是无法写数据的,比如s[1]=3;编译会出错的。
  8. 这个reduce的名字一开始迷惑了我很久,我是有怀疑它是map_reduce的含义,但是还是以为简单的accumlate之类的简单的加减乘除。实际上它是一个引入了并行计算的大腕,这一块我至今连接触都觉得恐惧。我想把它和一个类似的进行比较,找不到。是不是inclusive_sane呢?

十二月七日等待变化等待机会

  1. 关于reference_wrapper其实最好的办法就是使用对象的value_type,因为我忘记了unordered_map的类型实验了很多次都不对。
  2. 如何写一个自己的output_iterator呢?首先我为什么要这么做?原因很简单我需要collect一个inclusive_scan的结果,我对于它的中间结果不感兴趣,因为我只想得到它的最后结果和reduce加以比较,所以我需要这么一个dummy_iterator,看起来这个是最低限度的实现要求 这个最理想的应该是一个模板类,不过我讨厌再为它声明一个namespace,因为模板类不能是local的类。对于模板函数我可以投机使用auto运用generic template之类的避免声明namespace。另一个让我意外的是我必须实现assignment operator,这个在这里的concept看不出来 而且我是参考stl_iterator.h里的back_insert_iterator的做法,他也没有实现这个assignment operator,难道是它的继承的父类实现了吗?
  3. 那么使用了我的dummy_iterator就部分免除了inclusive_scan的额外的开销,现在我想来参考这个例子来比较reduce相对应inclusive_scan的加速。首先,我必须include execusion头文件才能使用std::execution::par 但是出乎我的意料的是这个加速似乎不可靠,之前我能看到大概三倍多的提升,这个是在预期之内的因为reduce就是分成四个一组的并行。可是使用了我的dummy_iterator似乎inclusive_scan和reduce不相上下了?
  4. 长久以来大家都在反复问这个是否可能?就是说在ostream_iterator可以自定义分隔符,那么为什么istream_iterator不可以呢?我的理解是这个依赖于它的basic_istream的重载operator<<对于各种数据类型的能力。换句话说就是istream_iterator仅仅在operator++里面依赖于它所依附的stream_type的来做事情,那么如果我们使用的不是POD,比如模板参数是string,那么这个就依赖于stream和string的operator<<怎么结束了。我注意到stl里专门为此有一个代码文件是istream-string来实现,它具体是依赖于stream里的locale的cty;e<char>的is和scan_is。它使用ctype_base::space来判断字符分隔,这个hardcoded的标志导致唯一可能的做法是创建自己的ctype,然后使用stream的imbue方法来注入自己的方法。可是结果却不行,我不知道为什么? 而且即便这个方法可行,你能自定义的分隔符也很有限,因为stl里的string的分隔符被ctype里定义的这么几种限制了。我现在还是不太清楚这个imbue是否不起作用还是说因为它显示的调用ctype<char>的方法导致我的重载无法调用?毕竟ctype不是有虚拟函数的类。
  5. 我花了快一个下午才搞明白了一点点为什么继承自ctype<wchar_t>的重载do_is虚方法是有效的,而直接继承自ctype<char>的方式则不行,因为后者并不是直接继承自__ctype_abstract_base至少specialization的ctype<char>不是的,它是直接继承locale::facet, ctype_base去实现那些“非虚”的方法,比如is于是乎你实现do_is是完全无用的,要怎么打破这个牢笼我还没有想明白。因为困难的地方是在basic_istream<char>里它使用的是它父亲basic_stream里定义的这样子基于string或者stream里都是使用char的,那么你的ctype能怎么办?就只能落在哪个specialization的ctype<char>上,我无论如何也想不出有什么好办法,简直就像是判了final的死刑一样不让你重载。这个算不算是故意的,因为怕后来人捣乱这个最最基本而且重要的基础?我也曾经想过自定义一个特殊的char type来仿照wchar_t的做法,可是类型就是模板里的类型不管你用typedef给它起了什么别名都是徒劳的。顺便说一下我一开始疑惑为什么wchar_t的do_is在stl的头文件里找不到而__ctype_abstract_base是明确要求你实现这个虚方法的,后来去查gcc的源代码才发现他们是在libstdc++-v3这个库里实现的我称之为平台相关的二进制码的部分吧。我依稀记得以前在stackoverflow上有大侠的类似的评论,看来这个问题是无解的。

十二月八日等待变化等待机会

  1. 原本想通过unordered_map来了解std::hash,可是何必呢?究其原因我原本非常奇怪的以为后者应该在container里面,当然是找不到,其实是在utility里。
  2. 我仿佛在浩瀚的大海边的沙滩上拣贝壳一般,几乎每天都能够发现新的宝藏。这个std::quoted也是一个奇妙的东西!这个就是我要报怨的因为代码逻辑越解释越模糊,我反而是不觉名厉了。
    1. First, the character delim is added to the sequence
    2. Then every character from s, except if the next character to output equals delim or equals escape (as determined by the stream's traits_type::eq), then first appends an extra copy of escape
    3. In the end, delim is appended to seq once more
    这里遗漏了关于escape的处理结果我读了几遍都是一头雾水,直到看到运行结果才焕然大悟,如此简单的毫无悬念的做法为什么让人疑惑?是我自己的理解力有问题? 看来是我的理解力有问题,它是针对escapedelim都各自加一个escape这个是所有的学计算机人第一天就理解的,我似乎忘了。
  3. std::quoted的最大的应用似乎来自于我一直以来的一个小小的痛点,就是比如我有时候把整个文件作为代码的一部分,比如输入常量,这个可以减小运行期读文件的开销,当然这个是以付出额外内存开销为代价的,可是有时候你不是很想暴露这个内容给普通用户,对于高级用户当然可以通过strings等等简单命令查询,不过防止普通用户乱修改文件也是好的。那么你怎样转为c/c++的字符串呢?难道要自己写一个小程序吗?多啰嗦啊,虽然也许就是几行代码,可是忙中出错的事情我遇到过很多次了,往往都是在调试最头疼的时候穷凶极恶,走投无路,丧心病狂的时候写这些莫名其妙的测试代码,出了错就只有以头抢地了。

十二月九日等待变化等待机会

  1. 其实我的一开始的想法就是错的因为hash的难度绝对不在于简单的数字类型的hash,因为这个就是一对一的没有任何ambiguous的,比如内部hash的实现也许就是简单的mod,所以,对于一个unordered_map目前这个论断是成立的 至于内部bucket的实现我用gdb跟了几次看得不是很清楚,但是大致的概念应该是对的,我估计整个bucket的列表是一个数组这样才能使用Index直接寻址,那么每一个bucket内部要解决固定数目的元素只能使用linklist。那么我可以做一个极端的实验故意全部存在同一个bucket来看代码怎么处理rehashing的反应如何?
  2. 所有的神奇的地方都是发生在rehash里的,因为我每次都找出装载元素最多的bucket然后按照hash计算bucket的方法计算出一个key当前如果已知需要装载的bucketIndex计算相应的key很简单,就是m.bucket_count()+bucketIndex故意把新元素装进这个最多的bucket里,可是当load_factor达到某个极限触发rehash之后所有的元素统统被平均分配到每一个不同的bucket里,这里的神奇之处我还是看不懂怎么发生的。而这个就是最关键的部分。
  3. 虽然这里解说轻描淡写的说size()等价于 i.estd::distance(begin(), end())但是你绝对不要天真的以为你可以无偿的使用后者,因为这个是一个input_iterator意味着它只能支持operator ++所以,事实上size的实现是返回内部的hash的一个成员变量,否则后者要一个个去数有多少个元素啊。这里的distance也是很典型的一个tag dispatch的范例,大体上就是类似于用tag作为非模板函数的重载的方法。
  4. 我对于这个新函数insert_or_assign和传统的operator[]有什么区别感到不解。
  5. 这个priority_queue我怎么一点印象都没有呢?这里给了我一个更好的理解,他们都是所谓的container adaptor
    container adapter adaptor semanticsunderneath container requirement
    stack adapts a container to provide stack (LIFO data structure) std::vector, std::deque and std::list
    • back()
    • push_back()
    • pop_back()
    queue adapts a container to provide queue (FIFO data structure) std::deque and std::list
    • back()
    • front()
    • push_back()
    • pop_front()
    priority_queue adapts a container to provide priority queue. std::vector and std::deque
    • front()
    • push_back()
    • pop_back()
  6. 所谓的deduction guide是很奇妙的东西。这部分其实很深奥,我暂时没有精力能力深入理解。

十二月十日等待变化等待机会

  1. 关于rotate array我看到一个大侠的算法真是佩服的五体投地,居然有这个让人难以企及的奇思妙想,留作纪念参考。
  2. 我也终于发现了一个使用c++新语法lambda的问题,就是内存消耗要大于同样代码逻辑的实现。比如我的答案和最好的答案几乎一样可我的内存和速度就差了很多。 当然我的代码质量差很多,大侠为了解决代码一致性的问题,生生造了一个新的所谓的before_begin的head,这个似乎是所有单链表的通用做筽法,我因为计算指针步进数头发白了好几根啊。 这样子以后计算链表的长度就不用加一了,那么计算新的头的步进数就是简单的公式=长度-(偏移mod长度)。这个做法还可以解决偏移是长度的整数倍的trivial case,直接返回的判断也省略了。这样子的代码才是优秀的代码。
  3. 我觉得这个简直就是幼儿园的问题,结果我花了好大的力气还是做的乱七八糟!比如把一个数字k,做n等分,要求分配差额小于2并且大份在前。

十二月十二日等待变化等待机会

  1. 能不能这样子总结bubble sort的哲学思想:每次实现局部目标期望最终推广到全局目标?因为排序的总目标体现在局部就是每两个相邻的元素也必定符合顺序,那么按照这个原则每次都实现局部满足达到处处满足。而selection sort也是有某种类似的想法,只不过着眼点不同,每次实现一个小目标就是说每次解决一个元素的位置,比如每次先把当前最大的元素的位置搞定,然后逐步缩小范围。这个思路也是分而治之的想法。那么假如你对于每个元素从一开始就认定它的在全局中的位置,那么你完全可以采取这样一个排序的方式,我称之为“先入为主”的评价式的排序方式。假如我们有一个类似于hash函数的“神奇函数”能够对于每一个元素作出精准的评估知道它在当前的序列中的位置,那么我们当然可以立刻把它放在它应该在的位置,那么这样子就实现了所谓的线性复杂度的排序。当然这个“神奇函数”几乎是不可能获得的,所以这个方法就是一个玩笑。比如我们有这个数组他们严格遵循了这个假设,就是我们可以判断每个元素它应该在的位置 我们先打乱次序以便排序 那么排序就变成简单的把元素放在它应该的位置了 这就是所谓的O(n)的排序算法,当然这个是纯粹无意义的想法,
  2. 关于is_permutation的实现如果允许排序的话复杂度是O(2*log(N)*N+N)似乎比stl里的实现O(N2)还快一点,当然代价是额外的空间或者破坏了传入参数。实际上我说的是worst case,而对于average case,标准算法要快的多,因为根本不需要等待排序结束就能判断出negative的case。所以,这个是我的浅薄了。
  3. insertion sort从思想上是一个自增长的模式,每时每刻保证当前的局部是排序状态然后扩展它,所以,它的优势是可以不停的添加。

十二月十三日等待变化等待机会

  1. 这个ODR看似简单,可是很难理解它的定义。

十二月十五日等待变化等待机会

  1. 遇到convert jpg pdconvert-im6.q16: not authorized错误需要修改/etc/ImageMagick-6/policy.xml里改为
  2. 这个据说是华为的一个面试题,不知真假,不过很有意思,就是说我以前曾经做过类似的计算机程序推导答案的小程序,一时手痒忍不住做了这么一个练习。
    其实这一类问题都是一个思路就是写好检验条件然后穷举,只要都通过条件审核就是唯一答案,前提是答案是唯一的,而且你的条件是严格约束的。我一开始想利用std::none_of,all_of等等,结果发现并不适用,因为我需要的是exact_one_of或者exact_two_of,根本不需要这些高级的定性的东西,只不过在翻译仅有一个号码正确位置正确的时候你必须剥夺有多个号码正确但是位置不正确的情况,所以使用count_if反而更适合。 这个是我的程序 所以运行结果是符合逻辑推理的986
  3. 长久以来我一直有时候遇到evince不能另存为新文件时候创建新的文件夹,我一直以为是mount的文件夹权限问题,可是全部改成了777依然无效,现在才明白是apparmor在捣鬼,不过怎么修改我比较没有把握。

十二月十六日等待变化等待机会

  1. 我翻看一个月前的笔记恍若隔世,我怎么一点印象都没有了?这里有一点可能是上一次没有彻底认识到的,就是conversion constructor的复杂性。我真的理解什么是conversion constructor吗?不,我其实并不明白!这一段话字数不多可是分量很足,我体会了好久。其中的信息量很大!
    • 首先是定义:
      A constructor that is not declared with the specifier explicit and which can be called with a single parameter<(until C++11) is called a converting constructor.
    • 这里第一是不能有explicit这一点是出乎我的预料,为什么我一直认为只有加了explicit才是?我可能从来就没有真正读过它的定义。因为带explicit的叫做explicit constructor
      which are only considered during direct initialization (which includes explicit conversions such as static_cast),
      也就是说他们的行为是不同的,explicit constructor靠的是类似于static_cast这样的手段,发生的时间也更早,是在直接初始化阶段。
    • 这里的解读就更加明确
      converting constructors are also considered during copy initialization, as part of user-defined conversion sequence.
      换句话说,converting constructor是user-defined,发生在相比explicit constructor这类直接初始化更晚一些时候,当然这个绝不是说两者可以同时发生,我认为你不可能同时有所谓的explicit conversion constructor两者肯定是二选一非此即彼的关系,只不过从实现机理来看,explicit是依靠编译器按照程序员的type cast的指示去作的。而conversion constructor呢?则是在没有代码强制类型转化的要求下的自动按照类型兼容机制实现的。我觉的这里的关键字是copy initialization和direct initialization,因为长久以来我对于c++里constructor是否使用=变得越来越不敏感,因为似乎两种copy constructor的形式是否使用=并无差别,可是实际上从这里看出来,他们是不同的过程,一个叫做direct intialization,一个叫做copy initialzation,虽然是实现层面的细节,但是在explicit/conversion里面似乎作用不可小觑。
    • 这句话对我有些振聋发聩
      It is said that a converting constructor specifies an implicit conversion from the types of its arguments (if any) to the type of its class.
      为什么这句话这么的有分量因为它居然让我对于默认的constructor有了重新的认识,因为根据这句话来说所有的constructor都是conversion constructor,也就是说把某种参数转化为另一个类型,这个就是constructor,这个看法角度是不是震撼,虽然是情理之中却在意料之外,因为这么多年的constructor的概念里我一直把它当作类似工厂是用来生产并且从无到有的“创造”,而现在突然点醒梦中人一样的告诉我我不是在创造,我只是在改造,也就是说普通的constructor不管带什么参数,或者不带参数都是把他们转化为另一个类型,也就是我们定义的这个类的类型。所以,他们都是conversion constructor!这下一句话说的更加清楚不过
      Note that non-explicit user-defined conversion function also specifies an implicit conversion.
    • 震撼并没有结束,这最后的总结和补充让我们再次重新审视看问题的视角
      Implicitly-declared and user-defined non-explicit copy constructors and move constructors are converting constructors.
      所谓的copy constructor也是一种converting constructor!难道说类型转化也包含了相同类型的转化也算是类型转化,或者英文的意思是仅仅转化,没有一定是类型转化。总之没有explicit的constructor都是一种潜在的conversion,不管有没有参数,不管是否从自己本身的类型进行无差别的转化,他们都被当作是implicit的转化。
  2. 对于这些例子的理解其实相当的不容易
  3. 对于explicit的定义要重新理解。这个c++20的新语法其实很让人很疑惑。
    explicit ( expression ) (since C++20)
    这里的( expression )实际上必须能够转化为bool类型,这里有一点点像是新语法里的require只是味道像而已,并不是bool类型,而是任意的类型。c++20的新东西太多了。
    expression - contextually converted constant expression of type bool
  4. 这里的deduction guide是一种更加高级的东西,我甚至连它是怎么回事Class template argument deduction (CTAD) 都搞不明白,只是望文生义而已。
  5. 这里是关于copy initializer的准确定义,为加深记忆摘抄如下
    定义语法
    when a named variable (automatic, static, or thread-local) of a non-reference type T is declared with the initializer consisting of an equals sign followed by an expression.
    (until C++11)when a named variable of a scalar type T is declared with the initializer consisting of an equals sign followed by a brace-enclosed expression (Note: as of C++11, this is classified as list initialization, and narrowing conversion is not allowed).
    when passing an argument to a function by value
    when returning from a function that returns by value
    when throwing or catching an exception by value
    as part of aggregate initialization, to initialize each element for which an initializer is provided

十二月十七日等待变化等待机会

  1. 对于是否真的理解weak_ptr/shared_ptr有一个简单的试金石就是enable_shared_from_this这个是一个最基本概念的体现。使用shared_ptr看似简单,但是如果是身在庐山出于被shared_ptr包裹的对象本身要怎么把自己“共享”出去呢?比如用一个天真无邪的头脑来做一个实验 那么对于这样子的两个shared_ptr他们的引用计数是独立的,根本达不到跟踪对象生命周期的目的。 怎么解决呢?这个绝对不是一般程序员所能解决的,因为你是要求shared_ptr的实现者给你开一个后门让你能够超越普通shared_ptr的能力范围来解决这个问题,因为在你创建它的时候就要让shared_ptr来配合你的类,或者说让它知道你的类要实现这个特殊的输出引用计数的需求。所以,这个是深深的隐藏在shared_ptr的代码深处。同时逻辑上单单理解这段话就够难的,不要说谈怎么实现了
    A common implementation for enable_shared_from_this is to hold a weak reference (such as std::weak_ptr) to this. The constructors of std::shared_ptr detect the presence of an unambiguous and accessible (ie. public inheritance is mandatory) (since C++17) enable_shared_from_this base and assign the newly created std::shared_ptr to the internally stored weak reference if not already owned by a live std::shared_ptr (since C++17).
    为了理解这段话我用gdb跟了半天依然不是很明白,只能理解一个大概。
    • 这个神奇的enable_shared_from_this是一个著名的curiously recurring template pattern (CRTP)它代表了一种效率超过使用vtable实现的继承,这一点对于shared_ptr这类对于实现性能要求很高的基础非常重要。那么“继承”自enable_shared_from_this自动的在内部帮你实现了其中的机制,但是这个实现不是在enable_shared_from_this里面那么简单,否则的话任何程序员也许都自己做了一个了。我只能说大概是在shared_ptr的实现类里通过一些神奇的type traits的操作帮助凡是继承自enable_shared_from_this的类制作了一个weak_ptr的实现类,因为你在weak_ptr的对外是根本看不到一丝痕迹的。它用到了它的private方法跳过门槛直接赋值引用计数对象。这个实在是太复杂了,我相信我还需要很多年的阅历才能看懂其中的奥秘。
    • 我能够明了的就是要首先继承自enable_shared_from_this这样一个模板类,而且如果你的对象没有使用shared_ptr你根本就没有必要用他,也就是说你的this指针已经是在shared_ptr控制下你才有这个需要。否则普通变量何必要关心引用计数?
    • 我曾经有另一个naive的想法就是即便我的this已经在shared_ptr控制下,我输出一个weak_ptr不增加引用计数让使用者自己检查看看是否expire行不行? 这里面错误百出,根本就是胡说八道因为第一解决不了用户分配的shared_ptr和你enable shared两个独立shared_ptr的根本问题。其次,即便返回一个weak_ptr也无法解决引用计数的问题,因为一个基本原则就是凡是把shared_ptr当作成员变量的想法都是错误的,因为它引用计数会在类的destructor呼叫前就为零了,如果它控制的是this指针就要对于this做额外的释放,这个做法从根本上就是逻辑不通。
  2. 要怎么使用shared_from_this呢?或者说真的有什么有用的应用吗?我尝试模仿朋友圈人们记录彼此通讯录的行为来应用一下这个特性。不过我的设想是人们meet之后会把新朋友介绍给自己的朋友圈,那么这个meet就是一个递归函数,可是我找不到怎样防止无限地归的办法,通过介绍人也许可以防止一部分,但是依然还是有很多情况,所以,在达到使用shared_from_this之后,这个练习的目的也就达到了。

十二月十八日等待变化等待机会

  1. 要怎么理解这里
    Effectively returns expired() ? shared_ptr<T>() : shared_ptr<T>(*this), executed atomically.
    的atomically呢?我看到的代码是 这里怎么保证没有race condition呢?难道这个就是atomically吗? 我不是很确定,是不是这个refcount背后的对象如果已经被销毁了还是有可能被使用?
  2. 有时候我觉得一些基本的东西让我开始感到困惑,比如我从来都是认为int*和int[]只有const的差异,我动态分配一个数组,然后想要把它转化为一个std::array,那么这么做似乎是顺理成章的,因为我只能使用reinterpret_cast来强制转化类型 这有错吗?我一开始认为int(&)[]只是一个reference因为int[]从来就是和指针没有差别,但是程序crash了让我很困惑,难道有什么错吗?gdb发现to_array传递的参数是p的地址,原来array的reference还是要传递array指针的地址,这个道理虽然浅显,但是用惯了reference已经开始淡忘地址了。所以,reinterpret_cast不能使用参数p,因为它实际上效果是std::addressof(p),所以,要改成 auto ary=std::to_array(reinterpret_cast<int(&)[5]>(*p));这个如此基本的东西可是我却头脑很含糊。
  3. 能不能理解weak_ptr的一个应用场景就是传递所有权,比如一个shared_ptr要间接的传递所有权的时候可以通过weak_ptr? 可是即便使用了shared_ptr多一次引用有大的关系吗?显然这个并不是很好的理由,暂时的所有权这个应用场景我还是看不出来。

十二月十九日等待变化等待机会

  1. 这个所谓member alias templates看不懂。
  2. 我总是遇到所谓的narrowing conversion,这个是一个引用的定义

    A narrowing conversion is an implicit conversion

    • from a floating-point type to an integer type, or
    • from long double to double or float, or from double to float, except where the source is a constant expression and the actual value after conversion is within the range of values that can be represented (even if it cannot be represented exactly), or
    • from an integer type or unscoped enumeration type to a floating-point type, except where the source is a constant expression and the actual value after conversion will fit into the target type and will produce the original value when converted back to the original type, or
    • from an integer type or unscoped enumeration type to an integer type that cannot represent all the values of the original type, except where the source is a constant expression and the actual value after conversion will fit into the target type and will produce the original value when converted back to the original type.
  3. 我一直想学习汇编,这里是一个入门,我常常大言不惭的认为自己看得懂很多汇编。练习一下循环设置bit这里我的体会是不要把所谓的gcc嵌入式汇编和汇编混为一谈,实际上这里的汇编“模板”是一种伪汇编,比如一个简单的问题就是汇编本来寄存器使用%现在为什么使用两个?就是因为这个需要模板解释器再一次翻译成为汇编码。

十二月二十日等待变化等待机会

  1. 也许这个关于gcc inline assembly教程更加的全面?我粗略看了一下这个才是专家的教程,之前的是一个摘抄与解读,似乎还有个别错误吧?我也是初学者看其他摘抄有时候也是很好的因为有更多的入门的帮助,但是就怕被误导。
  2. 这个大牛的早期的作品,有空我想看看它的代码是怎样的?可是我很怀疑我的虚假的承诺。internet上有很多这样子的沉睡的“古宅”,很多时候hosting服务变了,旧的功能不全了,只有残缺的断垣残壁让人联想曾经的辉煌。
  3. 这个是很多年以前的讲座,可是今天依旧有意义,但是一个基本的问题就是怎么计算char8_t的字串长度我就被卡住了。 那么这个sizeof(a)究竟是多少?我数了很多遍都不对,居然是15!为什么?原因是汉字“\u4f60\u597”(你好)的编码是utf-8实际是三个byte一个字符,这个听上去多么熟悉,我却忘记了。因为前面u8前缀说的很清楚我却无视。这里我之所以犯这样子的看似很愚蠢的错误是因为我先入为主认为我从这个网站查到的是UCS-2,的编码所以我想当然的认为这个是16bits的编码结果,不错的确他们是的,可是u8前缀把他们转化为了utf-8的编码。所以。。。Hello, 你好!是15个bytes。

十二月二十一日等待变化等待机会

  1. 我的脑子里始终想要做一个我自己也不清楚是什么的东西,大概就是一个通用的一个家族的类,比如integral_constant就是一个典型的家族,他们代表了不同的类型,但是本质上是完全一类的类。那么我这么定义的一个家族其实也完全代表了一类的相似的类 传统上我们是使用继承来表达他们是一个家族这样子我们才能把大量的同质化的对象放在一个容器里来管理,因为在tuple诞生之前我们是没有办法来管理这么大量的不同类型的对象的。这么做当然很简单因为我们之所以使用继承是因为他们本来就是既有联系又有区别,我脑子里却偏偏想动态产生大量的不同的类型,我也不知道这个实际的必要性是什么?仅仅是类型才能在meta programming来处理吗?我们抽象的原则是类型而不是对象,回到了对象就离开了抽象。所以,要提高一个维度似乎应该是创建不同的类型,至于实现层是否按照需要回归对象那应该不是我们所考虑的吧?所以,自动差生了这一系列的类型,虽然他们有一个相似的方法,但是因为是从完全不同的类型发出的方法似乎通常的回调函数指针也有些困难,我还没有想过是否mem_fn可以掩盖类型的差异而统一管理所有的不同类型的成员函数的指针,我试着这么做 可是紧接着就是收集了这些对象的实例也是一个tuple,你要对于两个tuple来对应的呼叫对应的成员函数不是多此一举吗?因为方法必须跟着对象走,至少目前我还看不出分开方法和对象的好处,因为那个层面我可以使用传统的同一个类的不同实例存储不同数据来做到,这个不是meta programming的范畴了。所以,我还是收集对象的tuple 然后我可以使用apply来对于tuple的成员来调用,这里我使用了std::experimental::make_array,我想应该不是必须的,有很多使用initializer_list初始化的容器吧?不错有很多使用initializer_list作为constrcutor参数的容器,可是使用parameter pack作为constructor的参数才是问题的关键,而initializer_list不是的。而我们要用到parameter pack来作为函数参数的特性而不是使用{}的aggregate initialization。这个真的是否定之否定,我根本就不需要直接使用initializer_list,因为隐性的使用就足够了。所以,我完全可以这样做所以,这里的确不需要使用这个高级的std::experimental::make_array 所以我们可以看到不同的类型的“众口一词”的“呐喊”:
  2. 对于std::hash我觉得挺趁手的,可是意外的发现这个specialization不是那么随心所欲的,比如你使用容器模板,你不用担心这个需要事先实现它,所以容器是万能的,可是如果你想要类似的使用hash却需要自己去specialization,比如hash<vector<string>>是不行的。那么怎么实现呢?这个是仁者见仁智者见智的,我参考了bitset的hash的做法并且简化了一下,因为它需要继承__hash_base这个无聊的东西吗?它只是定义了一下result_type/argument_type,似乎这两个东西现在也不需要了。 问题是这么实现specialization实在是意义不大。
  3. 关于怎样做到强制specialization的问题似乎很简单,你定义了primary比如 这样子就要强制的要求必须定义specialization才可以使用

十二月二十四日等待变化等待机会

  1. 我对于thread这一块非常的不清楚,以前只是满足于pthread的最基本,看来这里要把在boost欠帐一起来偿还了。首先从最基本的lock来入手吧。它是一个模板函数不是什么class,这一点很重要。
  2. lock_guard是一个RAII模型的使用mutex等的方式。不言而喻的一定是class,否则怎么RAII?它的使用和scoped_lock有相似也不同的。相似的都是RAII的mutex的使用帮手,而且都可以不锁定只是获得owner。(我对于owner的概念不是很清楚,猜想是不是锁定的第一步?)不同的是后者可以“群殴”就是锁定多个mutex。另一点相似的就是对于非recursive_mutex如果已经有了ownership再次lock是UB。两者都不能移交所有权,这一点很好理解因为RAII所以很自然。我的感觉后者就是前者的一个升级版增加了“群欧”功能。
  3. 要理解unique_lockshared_lock必须理解shared ownership对于conditional variable的影响。这个是我下一步的阅读目标。目前只能明白两者transfer ownership都是move,这一点不是unique_ptr/shared_ptr的概念。自然的RAII是不需要提的,除了std::lock不是以外,但是它有本事“群欧”。
  4. 买了一个外接的蓝光光驱想要转换购买的电影,发现只有makemkv比较好用,从官方下载了beta版,拷贝这里的注册码才能够rip,否则也可以备份这里就实际去除了加密保护就可以作为handbrake的输入目录,所以我两种方法都在用,随后还是需要ffmpeg把巨大的多路视频音轨删减。
  5. 下载了一首《燕无歇》的片段
  6. 看来我是生锈了需要时时打磨,因为这些都是基本概念,可是许久不用换了一个说法我就不认的了。所以,所谓的shared_ownership就是read_mutex,或者以前我们说的counting semaphore。至于说为什么lock需要ownership配合我以为这个是和RAII配合的,因为std::lock是一个模板函数和RAII不一致才导致lockable这些对象必须要先"own"才有可能去释放,这一点我觉得很容易通过看源代码来验证。
  7. 关于unique_lock,它把copy constructor删除了强迫你必须使用move constructor

十二月二十五日等待变化等待机会

  1. 我发现这个系列博客应该对于我有很大帮助,我决定一步一步学习。
  2. 这个第二篇就提出了一个很有意思的现象,就是定义一个std::thread对象居然会被编译器误读为定义一个函数!比如我们定义一个functor 那么这个时候定义一个thread声明为std::thread t((SayHello()));必须要添加这样的额外的括号,否则这个是会有编译错误的std::thread t(SayHello());,是什么错误呢?这个是编译器的解读std::thread t(SayHello (*)())并且指出它声明了函数但是没有定义,我想对于大多数人来说都是意外的甚至是迷惑的,因为对于函数指针的形态很少有人熟悉吧?至少我是不敏锐的:SayHello (*)()。没错, 它是一个函数指针,它被当作了函数std::thread t(???);的参数,就是你要脑补的???。这个真的好难啊,我的意思是thread是一个类需要一个“回调函数指针”作参数的,那么我们使用functor是不能避免的,可是它居然被理解为了另一个函数的声明,这个在c++的设计上能避免吗? 这里thread的constructor就是必须设计为一个模板参数为函数指针作为参数还连带函数指针自己本身的参数,这个是否总是有歧义呢?如果我们不用functor呢?那是自然不会发生了,比如我们定义一个lambda它的实例是不需要使用constructor的这个怎么可能有歧义呢?如果我们不使用传统的constructor而是转而使用c++的新的初始化操作符{}会不会避免呢?这个声明是不会有歧义的!std::thread t(SayHello{});这个再一次证明了c++使用非传统的constructor转而使用{}初始化对象的必要性!
  3. 我再继续读作者的解决方法前面的两种都不理想,因为使用额外的lvalue对于SayHello不能利用std::thread的move constructor是一种遗憾,额外的copy constructor也同样的不可取。只有第三种使用thread本身的初始化操作符和我的想法有异曲同工之效std::thread t{SayHello()};应当承认作者的解决方法也许更加的通用我觉得没有差别,个人偏好,如果大家总是使用{}压根就没有这类问题的空间了!毕竟我们的functor SayHello也许constructor有带参数的话。。。还能否使用{}?当然也是可以的!比如我们添加constructor 相应的也是可以的std::thread t(SayHello{"nick"});
  4. 这一篇关于传递参数by reference是有教育意义的,当然之前我们利用thread的move ctor其实也许更有效率,如果不需要多个线程的话。
  5. 看到现在才谈到我感兴趣的话题mutex
  6. 现在谈到unique_lock和lock_guard的异同优劣,我觉得与其仔细捉摸英文的字句不如看代码,很多时候代码比文字复杂的多,一件简简单单的逻辑可以写成无比复杂的代码,可是有时候无比简洁明了的代码被繁文缛节的描述越描越黑。我想这个属于后者,何况祖师爷教导我们RTFC!所以,其实看unique_lock无比的简单,它只有两个成员一个是mutex本身一个是ownership的描述变量一个bool值而已,正是如我所想作为owner必须要承担释放的责任,所以,它的destructor有释放的逻辑。那么怎么保证mutex一定有onwer呢?其实这个是一个逻辑问题而不是技术问题,如果我们禁止直接操纵mutex本身一定要通过unique_lock之类的包装来操纵mutex那么从创建unique_lock的轨迹就能够保证这个“谁使用谁释放”的原则,因为lock本身就既是使用mutex的唯一方法也是获得owner的结果。看完了短短的unique_lock的代码回过头来看lock_guard你会哑然失笑,因为它简单的和我们的auto_ptr一样,甚至更加的简单,如此的简单的包装为什么要写那么长的注解,也许给任何明白RAII的原理的人五秒钟的解说就足够了。它哪里有什么ownership的概念?但是在我看unique_lock的move assignment的时候我还是有困惑的,这里为什么要多此一举的创建一个临时变量呢? 这段代码写的如此高深莫测让我百思不得其解,而且不敢怀疑它的正确性,难道有什么应付exception的机制? 现在看来应该是这样子的,首先operator=的函数签名是参数要求move,所以,第一步必须先把参数__u传递给一个临时变量,这个是函数的契约一样的要求,有了临时变量__u我们才调用swap来实现。而且在第一步调用unique_lock的move ctor是一个很好的实践因为将来unique_lock的move的实现就算改变了也是正确的。
    unique_lock& operator=(unique_lock&& __u) noexcept
    {
    	if(_M_owns) unlock();
    	unique_lock(std::move(__u)).swap(*this);why do we need to use a temp to do swap instead of using swap directly?
    	__u._M_device = 0;
    	__u._M_owns = false;
    	return *this;
    }
    因为move ctor是很明了的就是和swap没区别 那么对于move assignment有什么特别需求吗?首先,我们要释放owned mutex,这个是不言而喻的因为move ctor不可能会有这个问题。但是接下来难道不是简单的swap吗?在我看来两者嵌套使用是多此一举。如果是出于对于swap的重载的恐惧可以参考这里swap的实现并没有什么特别的处理,所以,我完全看不出来这里有什么必要。
  7. 看了unique_lock就理解它为什么不能支持recursively lock了,这个就算你的mutex支持recursive_lock也是没有用的因为uniqe_lock本身从代码上就会抛出异常。我之所以对于这个解说不满是因为官方的说明有些不够准确比如对于explicit unique_lock( mutex_type& m );官方的解释是
    Locks the associated mutex by calling m.lock(). The behavior is undefined if the current thread already owns the mutex except when the mutex is recursive.
    这里也许是设计者或者c++标准的定义,但是从实现者的角度看根本不是这么回事,首先,这个是constructor,怎么可能事先拥有所有权?其次,从代码上直接就禁止了recursive的lock,因此即便是mutex支持recursively lock你也做不到,因为unique_lock对象本身是一个非常轻量级的结构根本也没有可能在ctor阶段有这个问题,即便创建后拥有所有权再次调用lock,目前也不过是抛出异常,当然这里UB是指的将来也许不这么做了。我想说的是unique_lock压根无法应对。比如我做了一个recursion来重复锁定mutex 这里唯一能够解决的问题是重复的lock,比如u.lock();这个低级错误。 那么对于在递归完全无法防止deadlock 这里一定会发生自锁,unique_lock的所谓的ownership仅仅是着眼于释放的问题,根本无法防止这个情况的发生,这个问题使用recursive_mutex可以简单的解决。回到我之前的抱怨就是这句话里的The behavior is undefined if the current thread already owns the mutex except when the mutex is recursive.这里的except根本没有那个意思,明摆着是误导。

十二月二十六日等待变化等待机会

  1. 关于unique_lock我要强调的是如果不是在constructor就锁定,那么随后调用任何lock的函数不管是lock/try_lock/try_lock_until/try_lock_for都必须要首先检验owns_lock否则就会遭遇异常。
  2. 这里所强调的unique_lock能够传递所有权的一个背后的原理在于它的moveable的特性,也就是支持move ctor的设计,当然了这个是禁止了copy ctor和assignment operator的前提下的。这个例子是很典型的值得体会 首先我喜欢这个局部静态变量的mutex能够隐藏。其次这里返回的是“值”而不是引用这让调用move ctor才有可能。最后也是要提醒的不要认为这可能引起三次两次move ctor的调用,实际上编译器会因为RVO或者copy elision优化为一个: 最后一点就是如果unique_lock作为参数传递的时候切记要声明参数为传值,而传递方是lvalue的话必须使用std::move,因为编译器肯定是要出错的。我打算用一个实验来证明这一点常识,首先还是模仿作者的acquire_lock做法,只不过做成lambda,然后我定义一个简化版的数据处理接受一个传入的unique_lock 注意到这里的传入参数auto&&因为你不能指望编译器的auto能够给你翻译成rvalue reference,这个是不可能的,你必须明确指明。而且在unique_ptr的move ctor里你必须使用std::move才能达到使用的效果,因为参数的类型仅仅是创造的基础条件,并不等于你就一定会调用move ctor,因为你可以调用任何的ctor,我倾向于使用一个大侠的揭示,就是参数的&&更像是所谓的“universal reference”,这个名词似乎已经期而不用了,是五六年前的提法。我想说的是它更应该接近于perfect forwarding的意思,因为传进来的参数可以是lvalue ref也可以是rvalue ref,所以才有“universal”这个提法,而对于它的使用比如unique_lock<std::mutex> u(std::move(l));必须要用std::move是很明显的因为基本的概念“有名有姓”就是lvalue,那么默认的就会调用unique_lock的copy ctor而这个是被禁止的。 这个简单的调用的lambda唯一值得提醒的是作为work的传递参数方根本不应该使用std::move!比如我也时常有一个冲动就是work(std::move(l));是不是就能够强迫调用move形式的参数呢?这个是很多讲座里反复批判的典型错误,因为它帮不了忙还帮倒忙,本来这个可以使用RVO减少一次move ctor,被你使用这个move反而把编译器吓得不敢替你使用copy elision了。我说的是当work是返回值的情况,这个当然不会帮倒忙可是完全没有帮助吧? 至于说完整的调用线程代码就无关紧要了
  3. 有时候我在想我为什么把无数多的自己的错误也留在日记里,为了给自己辩解(B*S*)我想引用这个经典的说法,这个是一个展示思维的过程,它的受益者也许是未来研究人类智力如何降低或者如何模拟人类思考的机器人考古学家。
  4. 我看来是孤陋寡闻因为我从来没有接触过std::call_once对于它的使用场景也不是很确定这个例子并不能说明多线程里它的存在的必要性。
  5. atomic的一些c++20的新特性让它很像是condition_variable,这个真的是太多东西了。

十二月二十七日等待变化等待机会

  1. 这个关于shared_ptr的atomic存取的函数家族让我想起了这个关于facebook的反复出现的bug的案例的一个。这个也是我长久的一个疑问,到底stl里有哪些容器那些函数是线程安全的?比如这个shared_ptr我始终不清楚它到底的实现机制是怎样的,到底为什么或者是否需要保护,因为如果这样常用的一个结构始终需要考虑线程安全的问题就几乎不会有人在用了,不是吗?我又看了一遍这个视频,其中有好几个非常的有经典教育意义的著名的bug。
    • 首先是关于map的问题,这些个对于有经验的c++程序员并不是什么大问题,很多时候是笔误,比如使用operator[]是插入不存在的键值的问题,很多时候debug的时候随手写一个printf/cout的某个测试键值的输出。至于说自己写一个包装的getdefault的小函数纠结于返回值还是引用导致default临时变量成为引用这个也不是大问题,但是提问环节那个专家提到copy elision省却返回引用,但是这个提法主讲人的犹豫我是有同感的,对于map里的字串你貌似还是多了一次拷贝,虽然copy elision省却了返回值的一个拷贝,但是它毕竟是多了一个copy,当然我也同意最后他的评论,这个本来也许就是一个返回类型应该是std::option的问题,但是使用者是懒惰的程序员是不接受的。因为你要逼着我使用option我宁可自己写一个这样简单的getdefault的包装函数。总之这类问题是无解的,因为返回的源头来自两个不同的地方你没有办法统一的。主讲人也透露了他们实际上是有重载了一个使用rvalue reference的参数类型来禁止它出现在返回引用的重载,比如
    • 关于“看似创建实际是声明”的这类错误是很有教育意义的。这个是一个非常让人沮丧的,因为它和这个是一类的问题能否编译呢?假定foo没有任何其他地方有声明的话,这个实际上是声明了一个类型为string的变量名为foo。基于此我想用以下这个goofy代码段来说明 这里我们声明了一个空string的foo和一个空的unique_lock,要命的不是无害的foo而是不期任何作用的第二个m_mutex因为它是一个unique_lock使用了default ctor!幸运的是gcc-10.2.0的警告是有意义的: 这个警告足以引起你对于这个foo声明的注意了吧? 这个不应当让你惊醒?可惜的是这里并没有“shadow”的警告?这个正是主讲人指出的很微妙的一个问题,我使用的警告是-Wshadow-compatible-local所以,不会警告我猜想是因为两个m_mutex的类型不同吧。只有你改为更加宽泛的-Wshadow警告才会出现这样的警告,但是代价是我看到我的代码里有非常多的无害的同名的临时变量或者参数的声明也被抱怨了,这个就是代价我想我不会愿意为了这么一个警告看到那么多的辣眼睛的玩意儿。
    • 主讲人也提醒这个问题不会出现在lock_guard身上因为它没有default ctor,我觉得这个是非常有意义的问题,因为RAII的原则被允许default ctor的unique_lock给破坏了,因为它允许所谓的flexible locking的理念让你可以选择什么时候lock,和unlock让它比lock_guard有更多的灵活性,但是在某种程度上它不是一个纯粹的RAII模型。但是这个是难以两全的,此事古难全,只能天上有。
    • 接下去的这个题目太大了,我感觉我需要自己做试验才能完全理解Is shared_ptr thread-safe?
  2. 立刻就能检验出来你是否熟悉shared_ptr因为我居然不明白使用new T[]的指针是不能简单的用在shared_ptr::reset里的?因为长久以来你就讨厌delete后面忘记了[]吗?其实debug的时候我就已经意识到了可是还是没有意识这个是类型声明的问题,难道你不应该声明array类型吗?shared_ptr<char[]>
  3. 整个早上我都在无厘头的错误里挣扎,花了好几个小时才清楚问题的实质是什么!这又是一个程序员的随手一挥,而代价是如此的大。对于临时变量的恐惧我还没有建立起来,比如我始终认为stringstream::str()返回的也许是一个string reference对于stringstream::str()的理解有那么难吗?它返回一个临时变量这么明显的错误我居然没有意识到吗?这个错误的观念让我吃了大亏。比如我想把它拷贝出来,鬼使神差的是memcpy在某些情况下有着诡异的表现,我下面在讲,于是我打算摒弃cstdlib的函数转而使用std::copy,那么这么写有问题吗?不用考虑目的地指针data.get()的正确与否,这个拷贝让我在gdb里不断的见鬼因为我居然发现end指针地址竟然有时候小于begin,其实这个时候我就应该意识到这个是一个随机发生的过程,可是我却依然没有清醒的认识,反而纠结于是否是iterator类型和char*的指针的不同,比如等等,实际上这样子的错误是明显的AddressSanitizer: negative-size-param: (size=-3254),当然之前很多时候是heap_address_after_free的一类错误。可是让人丧心病狂的是我在使用memcpy的时候经常有这样神奇的效果 神奇的在于data.reset(new char[ss.str().size()+1]);如果没有这个+1我的程序就会报出AddressSanitizer: heap-buffer-overflow的错误,而我加了这个+1就正确了,这个一度让我开始怀疑memcpy和strcpy有什么异同,甚至我对于string::size()/length()是否包含额外的null都开始怀疑。这个简直是让人抓狂的。注意这里memcpy和std::copy对于src的不同有着巨大的差别,后者临时变量出现两次当然大概率是两个完全不相干的地址,(这里我不知道是否使用range的模型能够防止),前者是一个临时变量的地址仅仅有可能是无效的,但是长度却不会错,也许拷贝的是垃圾,但是为什么会出现heap buffer overflow,我只能猜想是无效地址overwrite了导致内存corrupted。但是这个还是很神奇,加一个长度就不犯错。
  4. 我终于搞清楚了之前这个data.reset(new char[ss.str().size()+1]);的谜团,说来好笑这个跟代码几乎没有关系,完全是测试代码的问题,让我想起了昨天听讲座里那个map本身问题没有,但是有人写了一个随手的测试代码要结果反而给fileSize初始化为0了。同样的,我因为没有考虑清楚什么是我的数据就存储一个char[]在shared_ptr里结果我打印的代码自然而然的“读”过界了原因很简单,我没有+1自然没有null结尾,就读过界了。
  5. 我突然对于tmpfile发生了兴趣,我一下子忘了它的原理,在实验中居然看不到它在/tmp目录下的存在,而所谓的FILE*指针实际上通常就是一个数字3而已,因为0,1,2是通常的stdin/out/err,而例子里显示它是一个/proc/self/fd下面的symlink,而获得的inode并不能在/tmp目录下找到,而代码里使用std::filesystem::exists却显示它还是存在的,可是read_symlink却说它已经被deleted这个是怎么回事呢?我后来散步才想起来所谓的删除文件从来就是一个unlink的动作,只不过rm这类shell命令要等待或者返回错误等等,这就是为什么大师们删除文件通常用unlink而不是rm因为前者不须等待,尤其这类操作是程序退出前的清理工作你不想一个服务程序死在那里傻等,或者报错等等,而且前者几乎不报错,就是shell不报错,syscall里也不会有什么大的错误发生。所以,tmpfile的实现是很简单的,就是在解决了tmpname之后打开文件并且立刻unlink它的引用数让它出于被删除的状态,所以,一旦当前打开的文件句柄关闭之后文件肯定是被删除的,而因为之前unlink的效果使得这个文件事实上无法被其他程序再打开,这就是安全之处。
  6. 这个是所谓的defense dark arts class让我明白了居然i++并不是atomic的。而主讲人讲的很多东西都是比较实用与精微奥妙的实用法门,不过我现在似乎还不是很愿意或者有能力进一步深入。
  7. 比如这个是有关于回到是否shared_ptr是thread-safe的问题的,shared_ptr里面包含的指针的更新不能保证的,所以才有这个家族的atomic的操作。之前那个视频里提到的程序员们费尽心思寻找crash的原因最后才开始怀疑实用shared_ptr的问题真的是一个教训。比如shared_ptr::reset这个和绝对大多数stl的container的操作类似都没有保证它是atomic的,作为使用者这个是基本的事实而已。

十二月三十日等待变化等待机会

  1. 关于parameter pack的东西我每看一次就被震撼一次,这个完全是超过我理解的“高阶方程”啊!这里给我展示了一个可以多重继承也可以实用它,可是我甚至于不知道要怎么去应用它!一个混合的类究竟有什么能力呢?
    • 假如我们有很多相似的类,并且为了展示我们去实现一个操作符<<的重载。
    • 然后仿照例子我们有一个可以多重继承的类Hybrid,为了说明它的继承来源我们在ctor初始化一个成员变量来记录它继承的父类,这里我使用了fold expression里的binary right fold,它调用了Struct里重载的operator <<
    • 现在我们要展示一下这个多重继承的类它究竟继承自哪里 我对class template argument deduction (CTAD)总是不确定,究竟这个是编译器对于所有的模板类都做了吗?于是在胆战心惊的情况下我尝试把这个拿掉了模板参数,编译器愉快的接受了:Hybrid h123(h0,h1,h2);,这个是多么让人高兴啊!我然后注意到CTAD是一个c++17就有的功能,我却一直不明白它的默默奉献。
    • 结果是这样子的
      id [2] says 1804289383
      id [1] says 846930886
      id [0] says 1681692777
      
      。可是我们究竟要怎么“自动的”运用这样子的多重继承呢?不要告诉我你可以任意调用父类的方法,我的想法是如果这样子的多重继承是大量的,那么无差别的调用父类的统一的方法才有意义,否则我们为什么要多重继承呢?注意到了binary right fold是一个类似于FILO的结果。
    • 如果我们利用那个神器make_index_sequence来自动产生这些父类的继承的话会怎样呢?首先我们老调重谈来做一个helper 它用index_sequence来创建一个Hybrid。
    • 使用这个helper来创建一系列的Hybrid就是一步之遥而已
    • 使用它来创建一个“杂种”吧: 结果是这样子的
      id [4] says 1714636915
      id [3] says 1957747793
      id [2] says 424238335
      id [1] says 719885386
      id [0] says 1649760492
  2. 不过以上并不能说明多重继承的目的,我们继承的目的是要使用他们的父类的特征,调用virtual的成员函数也许是一种,可是自动的调用其实颇不容易,这个Hybrid究竟要怎么利用它继承自这么多的父类的能力呢?或者一个更加简单的问题:一个类怎么知道它继承了哪些类呢? 不要告诉我创建者知道,这个被创建者的类本身知道吗?这个似乎是reflexive的问题。我感觉目前最多也只能使用type_trait里判断它是否有存在某个方法而已。可以使用is_base_of可是问题这个是只能告诉你结果如果你知道要问的问题,很多时候我们压根不知道要问什么问题。

    我们只能在ctor里看到,然后呢?也许我们可以在那里记录下来,这个也许就是使用parameter pack的fold expression的意义。除此之外呢?

  3. google实在是太厉害了,因为我自己都不知道要怎么表达我自己的问题,结果它找到了我最想要看到的问题和部分解答!总之我觉得这个多重继承从编程的角度来看除了能够作为某种"tag",否则基本没有什么用途,比如我们可以依赖继承性的特点来建立某种tree的关系 这个关系可以用作排序的pred?但是费了这么多的精力难道就实现了一个tag的功能?也许作为metaprogramming里它会有比较多的用途?
  4. 当我跳出圈子来看我想要做的东西的时候我意识到这个是不可能的。首先我要实现的Hybrid是一个继承自多个类的类,这个继承的信息是一个类的内存layout的基本条件,这个编译器必须要在编译期知道,而一个类的继承自什么类不可能仅仅在ctor才要延迟决定,这个在ctor里运用CTAD“推理”出它的继承类是一种假象因为这个一定还是在编译期就解决的,这个不可能任由你在运行期想怎么做就怎么做因为他不是成员变量的值之类的信息而是继承!所以,我只能依赖于模板类index_sequence,也许也可以使用intializer_list之类,但是也一定是编译期就明确的。所以我痴心妄想使用ctor参数而不是模板参数初始化Hybrid是不可能的,因为编译器看的是模板参数而不是ctor的参数。这个和我以前妄想用我的模板类Struct<N>的ctor的参数来诱导CTAD省却模板参数是一样的不可能。
  5. 我在散步的时候又意识到之所以发明index_sequence的原因就在于ctor的参数无法和模板参数保持一致,所以才需要这么一个玩意作为的保证说这个模板的参数就是ctor的参数。如果看到它的源代码就不要惊讶于这个struct却没有任何存储的功能,因为它仅仅是一个placeholder样的功能,我觉得就是一个tag吧?

十二月三十一日等待变化等待机会

  1. 在头条上看到的一个腾讯的分享非常的棒,我给的评论是字字珠玑,准备细细研读验证一下。
  2. 这里有一个很长篇的讨论安全转换整型的问题,我现在没有耐心看。

Smiley face