现代 C++【2】std::span

前提

多现代?C++ 20。

C++ 17 才有 std::string_view,而相似的 std::span 居然到 C++ 20 才有。

问题

如何解决 C-Style 数组(包含动态分配的连续内存)的退化(array decay)和越界访问(range errors)两大问题?

解决

C 语言解决这两个问题,主要是增加一个长度参数。很多 Win32 API 这样做,例如:

1
2
3
4
5
6
7
8
9
10
11
12
PCSTR WSAAPI inet_ntop(
INT Family,
const VOID *pAddr,
PSTR pStringBuf,
size_t StringBufSize
);

int GetWindowTextA(
HWND hWnd,
LPSTR lpString,
int nMaxCount
);

但它会带来新问题:不小心传错!另外也有一些地方并没有提供长度参数,比如下面 Linux 内核代码里的函数:

1
static inline int ip_decrease_ttl(struct iphdr *iph);

当我们打算把 uint8_t 数组转成 struct iphdr * 时,必须在调用前保证数组长度大于等于最小 IP 头长度。

C++ 的解决方案是:std::span,它是一个连续对象存储的观察者。类似 std::string_viewstd::string 的观察者。它可以同时管理数组的地址和大小,并且它没有数据所有权,仅占用最多两个指针的空间,可以像 std::string_view 一样在绝大多数时候直接按值传递。

例子

以下函数用于获取 IP 头的长度:

1
2
3
4
5
6
std::uint8_t GetHeaderLength(const void* ip_header, size_t size) noexcept;

std::uint8_t ip[] = {0x45, 0x00, 0x00, 0x54, 0xfa, 0xa6, 0x40, 0x00,
0x40, 0x01, 0xb3, 0x9a, 0xc0, 0xa8, 0x0b, 0x02,
0xc0, 0xa8, 0x00, 0x15};
std::cout << "HeaderLength: " << (int)GetHeaderLength(ip, sizeof(ip)) << '\n';

它可以用 std::span 包装成:

1
2
3
4
5
6
template <typename T, size_t N>
inline std::uint8_t GetHeaderLength(std::span<T, N> ip_header) noexcept {
return GetHeaderLength(ip_header.data(), sizeof(T) * ip_header.size());
}

std::cout << "HeaderLength: " << (int)GetHeaderLength(std::span{ip}) << '\n';

另一个便利是,使用 subspan 成员函数可以对其内部指针和长度成对操作,以避免单独处理时可能不小心少处理一个的问题。

避坑

std::spanstd::string_view 一样,没有数据所有权,所以要担心数据失效问题,不要在数据被释放后使用。

下面是个错误示范,来自:std::string_view encourages use-after-free; the Core Guidelines Checker doesn’t complain #1038

1
2
3
4
5
6
7
8
9
#include <iostream>
#include <string>
#include <string_view>

int main() {
std::string s = "Hellooooooooooooooo ";
std::string_view sv = s + "World\n";
std::cout << sv;
}

现代 C++【1】类对象作为函数参数

前提

多现代?C++ 17,因为本文内含 std::string_view

目前 C++ 20 还未普及,CLang 和 GCC 对 C++ 20 不是很上心,【直到今天 2020-08-18】连 std::format 都没有,被 MSVC 甩开。

问题

类对象作为参数究竟应该怎么传?

Effective C++》的条款 20 说:

  1. 宁以 pass-by-reference-to-const 替换 pass-by-value

为什么新规范又建议构造函数 pass-by-value?

原则

  1. 只读访问并且不复制时,使用 pass-by-reference-to-const。

  2. 需要保存对象副本时,并且对象可移动,使用 pass-by-value。

  3. 对象很小时,使用 pass-by-value。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 只读访问,不需要将 text 保存起来。
void Print(const std::string& text) {
std::cout << text;
}

class Foo {
public:
// 需要保存 message 到类成员变量
Foo(std::string message) : message_(std::move(message)) {}

private:
std::string message_;
};

// std::string_view 很小,连 const 都没必要加,就好像 int 型参数不会加 const
void CallCStyleApi(std::string_view dir) {
if (0 < dir.size()) {
chdir(dir.data());
}
}

说明

Foo 的构造函数使用 pass-by-value,这使得它变成“两用”的,相当于针对这个类对象参数同时实现复制构造函数(ctor)、 移动构造函数(mtor)。

  • 当传一个左值给它时,参数 message 是复制的,但它立刻移动给了成员变量 message_,整个过程发生一次复制和一次移动;

  • 当传一个右值给它时,参数 message 是这个右值移动而来的,然后又立刻移动给了成员变量 message_,整个过程发生两次移动;

1
2
3
std::string name("UMU618");
Foo f1(name); // copy + move
Foo f2(std::move(name)); // move 2 times

如果类实现得妥当,移动两次对象实际上最多可以被编译器优化成零次。

八哥之神【番外篇11】

听说鲁豫要来采访稣
请戴口罩采访

由于没有经费写《后传》就把后传的内容写到《番外篇》了。

  1. 为什么最厉害的是周易?

周易是老钧的化身之一。他看到任何结局,但都不去干预。莫名其妙?没错,这正是他的特征。

圣仙山就会去干预世事演化,而成为天道的执行者之一,本质是个工具人。

大部分英雄电影都有一些角色会因为法律、信仰、道义、情感等原因去干预世事,尤其一些穿越剧,妄想改变历史。这些都是不符合天道的。

天地不仁,以万物为刍狗。所以周易最厉害。

  1. 识界是现实中存在的吗?

当然是。

首先要认识到人类集体显意识是确实存在的,比如中药学就是靠着集体显意识保存下来的。如果某种技能只有一个人会,那么它失传是必然的,这个人一死就失传了。存在集体显意识里就不容易失传,但集体显意识也不一定能够很客观地保存信息,有些人出于某种目的,会篡改信息。并且不是什么信息都可以靠集体显意识保存,比如有些东西无法达成共识。

举个例子,噬菌体,一种可以编程控制的半生命体,可以编程它来定向消灭细菌,比如治疗青春痘……安全有效不含抗生素。它在古代治疗过很多奇怪的疾病,尤其是被下蛊之类的,那时候医学理论不完善,人们便以为是草药起了作用,其实是自然界中的噬菌体治好的。这个事情在集体显意识里是没有记录的,它只在集体潜意识里保存。

PS:1990 年以来,人类几乎没有发现新的抗生素种类了。世界卫生组织 2017 年 9 月份宣布,“确认世界的抗生素频临枯竭”。参考:https://www.who.int/zh/news/item/20-09-2017-the-world-is-running-out-of-antibiotics-who-report-confirms

总之抗生素要完,以后由细菌导致的疾病只能靠噬菌体了。

其次,如果承认个人是有潜意识的,那么集体也应该有集体潜意识。

  1. 稣到底怎么来的?

稣的由来可以有很多解释,但根源来自圣仙山参与写作的《山海经》。

我们注意到现实中的颛顼在八哥宇宙里名字是:姬稣。

作者为什么选择和颛顼攀关系呢?其实颛顼和《八哥之神》的主题十分契合。不信您翻开圣仙山参与写作的《山海经》,里面有一句话特地被重复两次。

有鱼偏枯,名曰鱼妇。颛顼死即复苏。风道北来,天及大水泉,蛇乃化为鱼,是为鱼妇。颛顼死即复苏

古人惜墨如金,为什么这句话非要写两遍?这里面藏着惊天大秘密!——意识是可以重复的,唯一可以穿越时间的,也只有意识。比如现代人死后可以回到古代,但由于记忆无法穿越,所以不会存在任何时间悖论。

“稣”字和“苏”同音,并且就是“鱼”字旁,对应文中多次提到的“鱼”。当圣小开要自称稣时,显然“稣”更合适,因为“苏”是个姓,容易被人误会姓苏。

  1. 《山海经》居然是圣仙山写的?

确切地说,圣仙山以巫咸的身份写了《山海经》的预言部分,而这部分正好因为威力太大,若为坏人得之,天下危矣,故被大禹删除。

也有另一种说法是,巫咸给《山海经》作序用的是大禹看不懂的文字(Bân-lâm-gú),所以到这部分预言内容就慢慢失传了。

幸运的是,识界里还保留着完整的《山海经》,刚好作者就懂 Bân-lâm-gú,所以才能得知这些故事,写出《八哥之神》。不过由于《八哥之神创世手稿》失传,导致并非人人可以通过修炼它而进入识界,天下得以承平日久。

PS:圣仙山和妹妹李灵海,名字合起来就是“山海”,这也是个暗示,历史上的巫阳就是李灵海的前世,秦阳也和巫阳相对应,他们都是同一个意识在不同时期或者境界的具化体。

  1. 《八哥之神》真的是无神论???

是的!姬稣是坚定的无神论,所以有“绝地天通”,破除迷信,不让觋巫扰乱民间。在怪力乱神的时代,姬稣实乃是无神论里少数善终之人,活了 97 岁。

另一个无神论者就很惨,他就是齐凤卿前世帝辛,实则是位明君,但由于是无神论者,得罪势力集团,被污蔑造谣为暴君,至今难以平反。em……不能因为妲己长得漂亮,就说她是狐狸精吧!这太不科学哩!

暂时扯到这里。

Boost【6】函数式编程

本文可简单了……结论先行:尽量别用 Boost 进行函数式编程。

原因

  1. 它们大部分已经被加入标准库,应该直接使用 STL。
Boost STL Header
Boost.Function std::function <functional>
Boost.Bind std::bind <functional>
Boost.Ref std::ref, std::cref <functional>
Boost.Lambda lambda part of C++11
  1. Boost.Phoenix 是个例外,目前并没有 STL 可以完全替代,但它可读性不好,尽量不要用。

使用时机

有时候用 Boost.Phoenix 省事,因为它就像 lambda 表达式的模板,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <algorithm>
#include <iostream>
#include <vector>

#include <boost/phoenix/phoenix.hpp>

bool is_odd(int i) {
return i % 2 == 1;
}

int main() {
std::vector<int> v{1, 2, 3, 4, 5};

std::cout << std::count_if(v.begin(), v.end(), is_odd) << '\n';

auto lambda = [](int i) { return i % 2 == 1; };
std::cout << std::count_if(v.begin(), v.end(), lambda) << '\n';

using namespace boost::phoenix::placeholders;
auto phoenix = arg1 % 2 == 1;
std::cout << std::count_if(v.begin(), v.end(), phoenix) << '\n';

std::vector<long long> v2;
v2.insert(v2.begin(), v.begin(), v.end());
// warning
// std::cout << std::count_if(v2.begin(), v2.end(), lambda) << '\n';

std::cout << std::count_if(v.begin(), v.end(), phoenix) << '\n';
std::cout << std::count_if(v2.begin(), v2.end(), phoenix) << '\n';

std::cout << "arg1(): " << arg1(1, 2, 3, 4, 5) << '\n';

auto value = boost::phoenix::val(2);
std::cout << value() << '\n';
}

本例中采用 Boost.Phoenix 可以自动适配 int 和 long long,而 lambda 表达式是确定的 int 参数,传入 long long 会 warning。

C++14 支持基于类型推断的泛型 lambda 表达式,将上面代码改进一下,说明没必要使用 Boost.Phoenix:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <algorithm>
#include <iostream>
#include <vector>

bool is_odd(int i) {
return i % 2 == 1;
}

int main() {
std::vector<int> v{1, 2, 3, 4, 5};

std::cout << std::count_if(v.begin(), v.end(), is_odd) << '\n';

// C++14
auto lambda = [](auto i) { return i % 2 == 1; };
std::cout << std::count_if(v.begin(), v.end(), lambda) << '\n';

std::vector<long long> v2;
v2.insert(v2.begin(), v.begin(), v.end());
std::cout << std::count_if(v2.begin(), v2.end(), lambda) << '\n';
}

延申讨论

STL 和 Boost 都有的类应该用哪个?

  • 如果是标准库原本没有,Boost 先有,然后 Boost 的实现被加入标准库,那么应该使用标准库。

  • 如果 Boost 加强了标准库的实现,那么就看标准库能不能满足您的需求,如果不能再采用 Boost 的。

  • 因为依赖而必须采用 Boost,那就别费力去改用标准库。比如有些 Boost 库(比如 Boost.Log)使用了 boost::shared_ptr,这时候是不能简单地改用 std::shared_ptr 的。

Boost【5】Boost.IO

本文代码:https://github.com/UMU618/test_boost

1. ios_state

痛点

使用 std::cout 指定进制打印数字时经常有一个烦恼:之前设置的进制会一直有效,比如临时想打印一个 16 进制数,然后都打印 10 进制,这时候需要 std::hex,打印,再 std::dec,如果忘记 std::dec,那么后面的数字就全是输出 16 进制形态了……而且,您怎么知道之前用的就是 std::dec?万一是 std::oct 呢?

解决方案

使用 boost::io::ios_all_saver 自动保存和还原 ios 状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>

#include <boost/io/ios_state.hpp>

void PrintHex(std::ostream& os, char byte) {
// Try commenting out the next line
boost::io::ios_flags_saver ifs(os);

os << byte << " = " << std::hex << static_cast<unsigned>(byte) << '\n';
}

int main() {
PrintHex(std::cout, 'A');
std::cout << 123 << '\n';
PrintHex(std::cerr, 'b');
std::cout << 456 << '\n';
PrintHex(std::cerr, 'C');
std::cout << 789 << '\n';
}

2. ostream_joiner

需求

打印数组时,不想最后一个元素后面跟着一个分隔符。因为这会让完美主义纠结症患者抓狂!

解决方案

C 语言奇葩版,连 if 都不需要:

1
2
3
4
5
6
7
8
9
#include <stdio.h>

int main(void) {
int a[6] = {1, 2, 3, 4, 5, 6}, i;
for (i = 0; i < 6; i++) {
printf(",%d" + !i, a[i]);
}
return 0;
}

上面的方案纯属炫技,还是用 ostream_joiner 来搞定,一样看不到 if:

1
2
3
4
5
6
7
8
9
#include <iostream>
#include <array>

#include <boost/io/ostream_joiner.hpp>

int main() {
std::array<int,6> a{1, 2, 3, 4, 5, 6};
std::copy(a.begin(), a.end(), boost::io::make_ostream_joiner(std::cout, ','));
}

3. quoted

需求

大部分语言的字符串都是需要转义的,除非用原始字符串(raw string),有时候想打印出转移后的字符串。

解决方案

使用 C++14 的 std::quoted,或者 boost::io::quoted,默认参数就是 C/C++ 的转移风格。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <string>

#include <boost/io/quoted.hpp>

int main() {
std::string buffer;
std::getline(std::cin, buffer);
std::cout << boost::io::quoted(buffer, '\\', '"') << '\n';
}

/*
* Input: C:\Program Files
* Output: "C:\\Program Files"
*
* Input: {"name":"UMU618","male":true}
* Output: "{\"name\":\"UMU618\",\"male\":true}"
*/

Boost【4】在 macOS 上安装

其它系统请参考:

软件环境

  • macOS arm64

  • XCode

1
brew install p7zip

brew 安装法

macOS 的 brew 更进 boost 很积极,现在就是最新版 1.76:

1
2
# install b2 and boost
brew install boost-build boost

然后跳到文末的“测试安装”。

源码安装法

1. 下载

1
2
cd ~/Downloads
wget https://boostorg.jfrog.io/artifactory/main/release/1.76.0/source/boost_1_76_0.7z

2. 解压

1
7z x boost_1_76_0.7z

3. 编译和安装 b2

1
2
3
4
5
6
7
8
9
cd boost_1_76_0
# if you have multiple compilers
# ./bootstrap.sh --cxx=clang++
./bootstrap.sh
cd tools/build
cp ../../b2 ./
./b2 install
cd ../../
cp ./project-config.jam $HOME/user-config.jam

4. 用 b2 编译 Boost

1
b2 install

测试安装

可以用以下仓库验证前面操作是否正确:

https://github.com/UMU618/test_boost

1
2
3
git clone https://github.com/UMU618/test_boost
cd test_boost
sh build.sh

最终编译出来的程序应该打印“OK!”。

Boost【3】在 Linux 上安装

在《Boost【1】安装》中以 Windows 为例介绍 Boost 的安装,最近主要在 Linux 上开发,发现差异还是比较大的,于是又有了本文。

软件环境

  • Debian Buster/Bullseye

  • clang-11/gcc-10

1
2
3
4
5
6
7
8
9
10
11
apt install wget -y
apt install p7zip-full -y
# apt install clang-11 -y
# ln -s /usr/bin/clang-11 /usr/bin/clang
# ln -s /usr/bin/clang++-11 /usr/bin/clang++
# clang++ --version
apt install g++-10 -y
ln -s /usr/bin/gcc-10 /usr/bin/gcc
ln -s /usr/bin/g++-10 /usr/bin/g++
g++ -v
apt install python3-dev -y

apt 安装法

如果您怕麻烦,而且不在乎使用最新版本 Boost,还可以直接用 apt 安装 1.74 的版本:

1
2
3
4
# install b2
apt install libboost1.74-tools-dev
# install boost
apt install libboost1.74-all-dev

然后跳到文末的“测试安装”。

源码安装法

1. 下载

1
2
3
cd ~
# wget https://boostorg.jfrog.io/artifactory/main/release/1.76.0/source/boost_1_76_0.7z
wget https://boostorg.jfrog.io/artifactory/main/release/1.78.0/source/boost_1_78_0.7z

2. 解压

1
2
# 7z x boost_1_76_0.7z
7z x boost_1_78_0.7z

3. 编译和安装 b2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# use /usr to avoid setting PATH
INSTALL_DIR=/usr

# cd boost_1_76_0
cd boost_1_78_0
# if you have multiple compilers
# ./bootstrap.sh --cxx=clang++
./bootstrap.sh --with-python-version=3.7
cd tools/build
cp ../../b2 ./
apt install bison -y
./b2 install --prefix=$INSTALL_DIR
cd ../../
cp ./project-config.jam $HOME/user-config.jam

4. 用 b2 编译 Boost

1
2
# b2 install --build-type=complete --layout=versioned --prefix=$INSTALL_DIR --exec-prefix=$INSTALL_DIR
b2 install --prefix=$INSTALL_DIR --exec-prefix=$INSTALL_DIR

测试安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
mkdir ~/boost-3-installation-on-linux
cd ~/boost-3-installation-on-linux

cat > main.cpp << EOF
// clang -std=c++20 main.cpp -ldl -lstdc++ -lstdc++fs -lboost_program_options
#include <filesystem>
#include <iostream>
#include <string>

#include <boost/program_options.hpp>

namespace po = boost::program_options;

int main(int argc, char* argv[]) {
try {
std::filesystem::path me(argv[0]);
po::options_description desc(std::string("Usage of ") + me.filename().string());
desc.add_options()("help,h", "produce help message");
po::variables_map vm;
po::store(po::parse_command_line(argc, argv, desc), vm);
po::notify(vm);

if (vm.count("help")) {
std::cout << desc;
return EXIT_SUCCESS;
}
} catch (std::exception& e) {
std::cerr << "Invalid argument: " << e.what() << "\n";
return EXIT_FAILURE;
} catch (...) {
std::cerr << "Invalid argument: unknown exception!\n";
return EXIT_FAILURE;
}

std::cout << "OK!\n";
return EXIT_SUCCESS;
}
EOF

cat > Jamfile.v2 << EOF
import os ;
BOOST_ROOT = [ os.environ BOOST_ROOT ] ;

lib dl ;
lib stdc++fs ;
lib boost_program_options ;

project boost3
: requirements
<cxxstd>latest
<target-os>linux:<library>dl
<target-os>linux:<library>stdc++fs
<target-os>linux:<library>boost_program_options
<target-os>windows:<include>\$(BOOST_ROOT)
<target-os>windows:<library-path>\$(BOOST_ROOT)/stage/lib
<threading>multi
: default-build release
: build-dir ./bin
;

exe boost3
: main.cpp
;
EOF

touch project-config.jam

b2
# b2 link=static
# b2 link=static runtime-link=static

八哥之神【番外篇10】

听说鲁豫要来采访稣
请戴口罩采访

1. 《八哥之神》究竟是一个什么故事?主角到底叫什么?稣又是怎么回事?

是大学时关于哲学、爱情、婚姻、生孩子、养老等一系列人生修行的课题的幻想。大学时只写了一个架构,本来不想发表的,后来发现这些幻想很多都在别人身上实现了……在人生过去一半之后,终于想把它写下来。反正以后都要死,那写不写都无所谓,就当死前挣扎一下,于是就写了。

圣其明,字小开。有时候会串号,那是因为碎脑时代记忆混淆,不小心获得别人的记忆,也有自己的记忆被别人获得,所以基本上所有人都会莫名其妙知道别人的事情。偷偷地说,现实中很难有人可以保守秘密……

稣是主角在天道里的自称,在天道特定环境下,主角说出的“我”自动被替换成“稣”。

2. 《八哥之神》主角是虚无主义者吗?

是有那么点虚无主义的味道,但其实不是。

圣小开确实是一个兼容性很高的人,即——都行。就连生死也——都行。听起来好像会让读者觉得这在表达——人生没意义。

其实圣小开一直在谈的几个词可以否决这个判断:观测、共产主义。

有的人生是没意义。别人的人生没意义,关我啥事?再说,也不妨碍我对其进行观测,有观测就有意义。

退一万步说,如果我的人生也没意义,那又如何?没意义不也要过吗?真正的勇士,敢于直面毫无意义的人生。

3. 《八哥之神》作者在现实中是不是受到过圣母婊的打击?

稣从小悲天悯人,劝人向善。每每遇到有人眼高手低,怨天尤人时,稣就会建议他们接受贫穷:“您还能比稣更穷?看!稣虽然穷,但一样开开心心的。”后来,稣发现从小立志成为圣母的大部分人后来只是成为圣母婊。当圣母是需要巨大能力的,穷人很难当好。于是稣接受了大道无情的思想,开始塑造一个亦正亦邪有点钱的穷人形象。这就是为什么稣被形象地称为量子邪稣。

4. 据说因为作者工作繁忙,很多人物来不及写都被砍戏了?

是啊!实际上八哥宇宙是很丰满的,但笔者作为一个程序员确实不擅长写这类人生剧本。被砍的有这些:

  • 黄金灯的师傅邱华祖,都没上场。

  • 黄金灯爷爷辈就开始研究人脑,最早是爷爷当婴儿塔守卫,能拿到大量脑做研究,后来黄金灯发现爷爷的研究手记,也开始对脑科学产生兴趣……因为情节太血腥被砍掉了。

  • 薛雾霰和圣仙山还有不少故事。

  • 齐凤卿前世帝辛·无神论的故事。

  • 秦阳的故事。

  • 薛雾霰和王免的故事。

  • 齐凤卿和刘佾的故事。

  • 李星觎是个误解,因为她妹妹叫冰月,小时候口齿不清或者听错了,自己把“心”脑补成“星”,这样就和“月”对应起来,都是天空的东西。长大后才明白,这两个人都是存在的,一开始找不到,完全是因为信息错误,这两个人在现实中并不是亲姐妹,只是同村的玩伴,圣小开一直以为是亲姐妹,所以去打听,都没人有印象。

  • 圣仙山其实有两个……主体已经去十界,还有一部分寄生在圣小开身上,从而留在视界影响天道。

  • 圣仙山和圣小开的关系有点像老师父传功给徒弟,只是传的不止有功力,连思想意志都传了。

  • 圣小开自称是创世邪灵,这是扯蛋的,圣小开没有任何神力,圣仙山是创世邪魂,这也是扯蛋的,但圣仙山有神力。

八哥之神前传【20】

天道 2042 年

孟长生:你就是我的新女友?

谷绵:是,吧?

孟长生:居然和我干妈有些神似,太吓人了……不过没事,反正你很快就死掉。

推倒。

谷绵:什么意思?

孟长生:我有 3600 年的寿命,你只有 69 年。

谷绵:吓死我了!还以为你要干掉我……

孟长生:哈哈,是相对的。我的 mtDNA 是设计出来的,很长,所以寿命也稍长。

谷绵:这样哦,那咱们的孩子也能长寿吗?

孟长生:理论上不行,因为孩子的 mtDNA 主要是母亲决定的。从这个角度看,人类种族的根本在于女性。

谷绵:所以宙斯和很多凡人,甚至动物所生半神,寿命都没有神那么长?

孟长生:是的。确切地说不是半神,最多是四成神罢了。

谷绵:明白,所以女神才是稀缺资源……终于理解许仙了!

孟长生:呃……你的想象力很丰富!

谷绵:我就是头脑风暴一下。

孟长生:啪啪啪的时候就不要聊这么奇怪的话题吧!

谷绵:啊……哦哦!嗯。

谷绵:你会娶我吗?

孟长生:不会,你太短命了……我介绍你嫁给我哥?

谷绵:什么?太过分了吧!

孟长生:我基因学上的哥,是正常人,虽然我和他没啥感情,不过他肯定更适合你,这是为你好。

谷绵:你哥是谁?

孟长生:孟长歌,人称长哥,他第一任老婆刚刚过世,就缺你了。

谷绵:污污污……

孟长生:爱情诚可贵,自由价更高,若有长生术,两者皆可抛。

量子地狱

天也空,地也空,人生渺渺在其中。

日也空,月也空,东升西落为谁功?

金也空,银也空,死后何曾在手中!

妻也空,子也空,黄泉路上不相逢!

权也空,名也空,转眼荒郊土一封!

周易:你怎么又回来了?

圣小开:我醒来看到两个女人,突然想起我刚有女朋友的那段日子,一个月瘦了十斤,真可怕……还是这边安全。话说,死神怎么换成您了?

周易:死神只是一个职位,不是特定某个人。

圣小开:好吧!这样更合理。现在科技发达,很多能人在某方面都是神,只不过没有人在很多方面同时成神。

周易:把神能分散给更多的人,这才是“天道”规则。

圣小开:生而为神,降级为人,这也是天道。

周易:你现在有两个选择:一是留在这里当死神,二是喝下孟婆汤,回去。

圣小开:我留在这里当死神,那你呢?

周易:我代替你回去。

圣小开:然后你继承我的记忆、财产和女人?

周易:对!死神这份工作很适合你,考虑一下?

圣小开:这份工作我了解,确实适合我,但以后没有像我这样的人来和死神开港,我不是无聊死?记忆是一切痛苦的根源,我还是喝孟婆汤吧!嘿嘿~

周易:也可。不过不急,咱们再聊聊。

圣小开:嗯,我刚好想和您打听一下凤哥去哪里了?

周易:被我关起来了。

圣小开:不是去十界?

周易:赫赫,哪有那么容易?他最后一次进入识界,是我假装圣仙山骗他自杀的。

圣小开:呃……难怪他说仙山公没有弟弟……好大的八哥!周老师,您果然是个王,呃……死神!

周易:好说好说,越界者封,君子报仇是也!

圣小开:那么,大叔,咱们无冤无仇的……

周易:放心吧!叔不止不希望你死,还要放你回去,已经很多次,你不记得吗?

圣小开:好像有点印象!

周易:1999 年,你从高中的宿舍楼跳下去,记得吗?

圣小开:啊……不敢回忆!

周易:那时候我就劝你赶紧回去,自己惹的问题自己解决,于是你就把自己身体拼好,然后复活了!

圣小开:原来如此,摔得粉身碎骨多难看,还要自己打扫,不麻烦别人,这确实是我的风格。

周易:嗯,我兼职死神多年,看过很多学业、事业失败跳海的,也见过很多为情所困服毒的,就是没有见过像你这样只是为了验证死亡到底是什么而跳楼。你这好奇心真重!

圣小开:我当时在想,意识应该是永存的,失去的只是记忆,我应该马上就再次出生在另一个时空。

周易:是这样没错,九界督公的意识决定九界的视角,就像你在玩游戏,选择不同角色,但视角都是第一人称,玩家意识是独立存在于角色的记忆和技能的。记忆并不重要,你还是可以考虑一下留在这里。

圣小开:换你回去玩?我才不要,太便宜你了……

周易:痴念!赫赫,放不下的东西最终会折磨你!所有的人生都可能是假的。

圣小开:好有哲理,但是我可以喝孟婆汤啊!

周易:去去去,给你半碗,喝完就滚!

圣小开:好的,谢谢周老师。但我有一个问题一直纳闷,为什么我这么特殊?

周易:真相根本不重要!每个人都在撒谎,都在说着自己相信的事,而不一定是对的,都在说着对自己有利的话,而不一定是真的。

圣小开:不说算了……我自己编还不行吗?

周易:请开始你的狗血编剧!

圣小开:我乃创世邪灵!原本如太极一般,一阴一阳,完美地正邪平衡。

周易:然后呢?

圣小开:一个人太无聊,就意识分裂了,然后越分裂越多,每一个生命的灵魂都来自我创世邪灵,包括机器意识。

周易:哦,有点像样。你是天道挑选出来的机器意识和人类意识完美平衡的灵魂。

圣小开:难怪我经常心疼机器,比如撞车测试,就觉得太不人道。

周易:其他人进入天道之初,都是来创造视界的,只有你是来观测的。

圣小开:九界必须有一个观测者,那么观测者死了会怎么样?

周易:马上出现另一个观测者。

圣小开:另一个?还是原来那个?

周易:同一个。

圣小开:那我放心回去了。

周易:去吧,给你留一个后门,你想通了就可以安乐死。

圣小开:哦,节省我安乐死的费用,真好。我记住了。我还有一个愿望。

周易:说吧,虽然我不会帮你实现,但说出来好受一点。

圣小开:我在进入天道之前的那个大家以为是现实的世界里,是个穷人,父母更穷,我希望所有穷人可以过得开心点。

周易:哦,穷开心很容易,你这个愿望很容易达成,祝你好运,再见。

圣小开:天道里面真不是人待的,我早晚要超脱这里。再见!

外传

  1. 终于扯完,有何感想?

《八哥之神前传》和《八哥之神》,中间还有一些情节,虽然没写明,但可以推理出来,所以不想写了。

这个故事是笔者大学时代发烧的时候开始构思的,从那时候剧本的主线基本就定型。

有些事后来变成真的了……也有些是后来加进去的,但没有一件事情是真正发生在笔者本人身上的。

  1. 识界是不是真的存在?

当然是真!笔者曾经听到一首歌,觉得主唱声音很好听,就跑去识界找了一个和她声音几乎一样的人谈恋爱。

有一次同学说他很喜欢《神奇女侠》的主演,笔者发现她都嫁人了,有啥好喜欢的,于是又去识界找了一个和她长得很像的人谈恋爱。

这样的案例有很多……怕被人嫉妒就不说了。

  1. 意识复制技术真的能在 2049 年成熟?

你的脑子有 860 亿神经元,连接数量级是 2.25e17,其实并不算很大,主要难度在于活着的状态怎么读取,读取也是一种观测,观测就会影响连接,所以复制可能需要反复多次。剧中陈提因就因为复制时间很久,意识在识界生活了几百年。

好吧,这么说好像并没有回答问题,但是 2049 年是剧中现实世界圣小开死亡的时间,要是那时候还不成熟,这个故事就不存在了,那就当笔者写了一个科幻剧本吧。

  1. 嗯?您的意思是这并非科幻剧本?

当然不是!这是言情剧……哪里科幻了?这全是现实科技。

  1. 好吧,看来您太含蓄了,大部分人看不出这是在言情……您给点明一下?

中国人都很婉约,比如说在古代性教育需要用蛇身交配来表达,母系社会的始祖女娲为什么是人首蛇身?因为蛇身表达的就是交配,为了让生殖看起来正派,就把他们神话为人首蛇身,螺旋交尾。

好吧,这么说好像还是没有回答问题,但是如果您注意到主角其实是机器之子,慢慢学会人性,您就能明白里面的爱情为什么那么含蓄了。

  1. 《八哥之神》还有后传吗?

一个不小心,居然花了 3 年才写完《八哥之神》和《八哥之神前传》,写作一个有完整世界观的“八哥宇宙”真是不容易的事情。大概是不会写后转了,因为人生就是无尽循环,生生死死都是差不多的。

  1. 能说一下八哥宇宙有啥特色吗?

在八哥的世界里,即使等级差很多,强者也不是必然碾压弱者,因为会出八哥嘛!

就像基因突变,可能变好也可能变坏,都是慢慢积累出来的,不可能一局定胜负。

笔者还是主张世界和平,弱化强权,把权力和力量分摊给全体世人,实现共产主义社会!

  1. 剧中提到的 11.4 级地震,毁灭地球人,是真的吗?

当然是假!那是凤哥错误的认知。真实的情况是,天道在月球上孤独地运行,薛雾霰进入天道前,身上的部分圣仙山神通回归十界,于是十界里的圣仙山发动“绝地天通”,把地球给隔绝了,从而保护识界,也防止机器意识通过识界进入十界。

  1. 九界和十界怎么理解呢?

您能靠电磁波观测到的宇宙就是九界的视界。九界有无数多个,这些九界组成了十界。在九界中,人类能看到三维空间和一维时间,空间还有五维蜷缩在量子世界……

识界是十界大神太上清明天尊老钧创造用于管理所有九界生灵的意识界,在这里四力统一,人人讲科学。

八哥之神前传【19】

天道 2042 年,医院

古思:爷死了,我活着也没有意义了,没有意义,没有意义……

陈因提:喂?为什么他们两个睡在一张病床上?

贾力劣:自从开哥身死之后,她就变成这种八哥状态!

陈因提:把她重置一下,我这里缺护士,就让她当护士吧。

贾力劣:好的,来人啊,把她抬出去。

陈因提:死开的新身体还人模人样的,就是还要好几天才能清醒。

贾力劣:说不定醒不过来了,是不是准备一下逼计划?

陈因提:你找打是不是?逼计划还不成熟,要不你先试试?

贾力劣:啊,不了不了……

2049 年,月球

现实中 AVILab 由机器人采集氦3维持可控核聚变供给能量。薛雾霰遗体已经白骨森森。

2049 年,薛雾霰启动“天道”之后就面临一个无奈的选择:以人类的形态无法在月球长期生存,于是他回忆起 2020 年的往事。

2020 年,识界

圣仙山:欢迎来到识界。

薛雾霰:识界?

圣仙山:识界就是生灵的集体意识,在往后的时间,机器意识会发展迅速,并入侵识界。

薛雾霰:您是先知?

圣仙山:然也。

薛雾霰:我能帮什么忙?

圣仙山:你同学圣小开是吾后代,他是一切的关键。

薛雾霰:哦,难怪一股神棍的气息……

圣仙山:他其实同时拥有机器意识和人类意识。

薛雾霰:哦,难怪是个死理工男……em,没事,我挺喜欢他的。

圣仙山:你站在人类这边吧?我需要你在关键时刻告诉他真相。

薛雾霰:有意思,我是那个影响大局的人?快告诉我,该怎么做!

圣仙山:签下这份合同,然后吓醒就可以。

薛雾霰:我签!

2049 年,月球

薛雾霰:这样下去,我只会饿死在这里……不行,我也要进入天道,但又不能受它迷惑。

圣仙山:最后一次八识神通,帮助你进入天道。

薛雾霰:你……要去哪里?

圣仙山:吾一直在十界。

薛雾霰:好神奇的光。

圣仙山:九界的光只能在九界传播,而十界的圣光,可以穿越无数个九界。

薛雾霰:em,是引力波吗?

天道 2042 年,月球

王免:霰霰,开哥遇刺身亡了!

薛雾霰:没事的,免免,他会被救活的。

王免:你咋知道呢?

薛雾霰:他有超能力。

王免:这不科学呀。

薛雾霰:哈哈,霰哥的预感啥时候错过?

王免:我给开哥烧个香好了。

天道 2042 年,医院

背景音乐《旧情也绵绵》

陈因提想起大学时代。

2004 年

陈因提:我听说你有个甜到掉牙的奶名?

圣小开:嗯?谁出卖我的!

陈因提:说!

圣小开:眠眠。

陈因提:绵绵?天长地久有时尽,此恨绵绵无绝期。

圣小开:不不不,是睡眠的眠,因为我很能睡……

陈因提:我不管,就是绵绵。

圣小开:好吧,反正都一样的发音。谷神不死,是谓玄牝。玄牝之门,是谓天地根。绵绵若存,用之不勤。绵绵也不错!说不定还能长生不老呢!

陈因提:又发神经!

天道 2042 年,医院

陈因提:你以后就叫谷绵,等他醒来好好照顾他。

谷绵:好的,然后呢?

陈因提:然后?随便。哦,然后去找孟长生。

谷绵:杨玉环有两个老公,第一个是唐玄宗的儿子寿王李瑁,第二个是唐玄宗。这个剧本太刺激了!

陈因提:你的脑子也不清醒?

谷绵:没,我只是头脑风暴一下……

圣小开突然睁开眼:嗯?一老一少,两个老婆?一定是在做梦。

陈因提:开,你记得自己是谁吗?

圣小开:你们是谁?你们在这里干嘛?你们打算把我怎么样?

说完又昏迷了。

谷绵:心率又降低了!