工作里用到 C/C++ 越来越多,花了些时间加强。
本文主要是学习极客时间的 C++ 课程笔记。
弄懂这些位于“犄角旮旯”里的特性(无贬义),需要花费我们很多的脑力,但在我们一般的开发过程中,通常很少会触及这些点,或者说是会尽力避免,它们通常只是对编译器有意义,所以在这些“细枝末节”上下功夫就不是很值了,说白了,就是性价比太低。
我个人认为,在掌握了专栏里 C++11/14 知识的基础上,如果再面对一个 C++ 新的语言特性,你不能够在五分钟(或者再略长一点)的时间里理解它的含义和作用,就说明它里面的“坑”很深。
你应当采用“迂回战术”,暂时放弃,不要细究,把精力集中在现有知识的消化和理解上,练好“基本功”,等你以后真正不得不用它的时候,通过实践再来学习会更好。
编码风格:http://openresty.org/cn/c-coding-style-guide.html
C++ 预编译期间,通过各种宏和条件判断修改代码逻辑。
#if
必须得以 #endif
配对出现g++ -E -dM - < /dev/null //查看内置的宏
基于它们,你就可以更精细地根据具体的语言、编译器、系统特性来改变源码,有,就用新特性;没有,就采用变通实现
__cplusplus
: 目前使用的 c++ 标准,201402, 201103属性:可以给变量、函数、类添加一个标签(类似注解),方便编译器识别处理。
用两对方括号的形式,方括号中间就是属性标签:[[noreturn]]
, [[depreacated]]
attribute :
attribute 参数:
static_assert 运行在编译阶段,只能看到编译时的常数和类型,看不到运行时的变量、指针、内存数据等,
(__visibility__("default")
: 某个符号是否导出
试想这样的情景,程序调用某函数A,A函数存在于两个动态链接库liba.so,libb.so中,并且程序执行需要链接这两个库,此时程序调用的A函数到底是来自于a还是b呢? 这取决于链接时的顺序,比如先链接liba.so,这时候通过liba.so的导出符号表就可以找到函数A的定义,并加入到符号表中,链接libb.so的时候,符号表中已经存在函数A,就不会再更新符号表,所以调用的始终是liba.so中的A函数。 为了避免这种混乱,所以使用__attribute__((visibility(“default”)))attribute((visibility(“hideen”))) 设置这个属性。
https://time.geekbang.org/column/article/235301
struct
实现抽象和封装。多重继承、纯虚接口类、虚析构函数,动态转型、对象切片、函数重载等很多危险的陷阱
:
表示继承,final 放在类名后面C++ 四大函数:
为了减少创建对象成本,C++ 11 引入了右值 (Rvalue) 和转移(move):
对于比较重要的构造、析构函数,可以使用
= default
,让编译器生成默认实现。= delete
表示明确禁用某个函数(非构造、析构也可以用),让外界无法调用
C++ 有隐式构造和隐式转型的规则。
隐式类型转换:类型 A 被自动转成表达式需要的类型 B。
两种类型别名:
using name = type
typedef type name
第一种比较直观。 如果类里有外部类型,名称比较长,可以定义个别名。
auto
:初始化时类型推导,总是“值”类型,没办法是引用类型decltype
, 表达式的方式计算类型,可以用于任何场合“自动类型推导”实际上和“attribute”一样,是编译阶段的特殊指令,指示编译器去计算类型。所以,它在泛型编程和模板元编程里还有更多的用处。
auto 的“自动推导”能力只能用在“初始化”的场合。
赋值初始化或者花括号初始化(初始化列表、Initializer list),变量右边必须要有一个表达式(简单、复杂都可以)
很容易理解,只是声明,没有赋值,无法推导类型。
另外,auto
也不能用于类成员变量:
但可以使用 using + decltype
的方式,简化类变量的类型声明:
除了简化代码,auto 还避免了对类型的“硬编码”。方便后续修改。
自动类型推导”实际上和“attribute”一样(第 4 讲),是编译阶段的特殊指令,指示编译器去计算类型。所以,它在泛型编程和模板元编程里还有更多的用处
auto 用于有表达式的时候,decltype 用于没有表达式,但先确定类型的情况。
上图里的名称写错了,*p 是取值符。
一个数取地址,得到的类型就是一个指针:
使用 auto 可以简化遍历的方式,使用 rang-for-loop
const 其实是只读变量,编译保证了不会被修改。
const
const&
是函数参数的最佳选择const_cast
,去常量化,变成普通变量volatile
mutable
既然对象是 const,那么它所有的相关操作也必然是 const。
const_cast,去常量化,变成普通变量
#include <memory>
智能指针,使用代理模式,利用 RAII 技术代替裸指针(重载了 * 和 -> 操作符,所以使用和裸指针一样),能够在指针对象析构时自动释放内存。用于替代手动创建裸指针和手动释放内存。
什么是 RAII 技术
:
函数内部的一些成员是放置在栈空间上的,当函数返回时,这些栈上的局部变量就会立即释放空间,于是Bjarne Stroustrup就想到确保能运行资源释放代码的地方就是在这个程序段(栈)中放置的对象的析构函数了。 RAII就利用了栈里面的变量的这一特点。 RAII 的一般做法是这样的: 在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。
指针与引用的区别?
指针完全映射了计算机硬件,操作效率高,是 C++ 效率高的根源。 同时也导致了很多问题:访问无效地址,指针越界,内存没有即使释放等。 Java/Go 没有这方面问题。
make_unique()
use_count
可以查看有几个引用,引用为 0 时,才会 delete 内存make_shared()
lock()
获取 shared_ptr
自定义智能指针工厂方法:
template<class T, class... Args> //可变参数模板
std::unique_ptr<T> //返回智能指针
my_make_unique(Args&&... args) //可变参数模板的入口参数
{
return std::unique_ptr<T>( //构造智能指针
new T(std::forward<Args>(args)...) //转发参数
);
}
#include <stdexcept>
C++ 处理异常的方式:
errno
使用 noexcept
修饰不会抛出异常的函数,方便编译器做优化:
noexcept 的真正意思是:“我对外承诺不抛出异常,我也不想处理异常,如果真的有异常发生,请让我死得干脆点,直接崩溃(crash、core dump)。” 一般认为,重要的构造函数(普通构造、拷贝构造、赋值构造、转移构造)、析构函数,尽量声明为
noexcept
,优化性能。
函数的目的:封装执行的细节,简化程序的复杂度。
面向对象编程:程序由一个个对象组成,彼此组合、通信完成任务; 函数式编程:程序由一个个函数组成,彼此组合、调用完成任务。
C++ 函数的特点:
C++ 中的 lambda:
void test_lambda_2() {
//4.嵌套 lambda 定义、调用
auto open_website = [](string website)
{
string local_ip = "192.168.77.33";
//按值捕获,指定捕获变量名
auto dns = [local_ip](string website)
{
cout << "Transform website to ip: " << website << " to " << local_ip << endl;
};
auto tcp = [local_ip](string server)
{
cout << "Establish connect from " << local_ip << " to " << server << endl;
};
cout << "Your website is opening... " << website << endl;
dns(website);
tcp("202,184,293,23");
};
open_website("www.baidubaike.com");
}
和 JS 的闭包差不多 lambda 保存了定义时捕获的外部变量,就可以跳离定义点,把这段代码“打包”传递到其他地方去执行
在 C++ 里,每个 lambda 表达式都会有一个独特的类型,而这个类型只有编译器才知道,我们是无法直接写出来的,所以必须用 auto。
捕获外界变量:
参数使用 auto
声明,可以让 lambda 表达式处理不同类型的参数:
//5.泛型的 lambda
void test_lambda_3() {
//参数使用 auto 声明,泛型化
auto f = [](const auto& i) {
cout << i+i << endl;
};
f(6);
f(0.24f);
string str = "hao";
f(str);
}
用“map+lambda”的方式来替换难以维护的 if/else/switch,可读性要比大量的分支语句好得多。
template <class _CharT, // for <stdexcept>
class _Traits = char_traits<_CharT>,
class _Allocator = allocator<_CharT> >
class _LIBCPP_TEMPLATE_VIS basic_string;
typedef basic_string<char, char_traits<char>, allocator<char> > string;
typedef basic_string<wchar_t, char_traits<wchar_t>, allocator<wchar_t> > wstring;
#ifndef _LIBCPP_HAS_NO_UNICODE_CHARS
typedef basic_string<char16_t> u16string;
typedef basic_string<char32_t> u32string;
#endif // _LIBCPP_HAS_NO_UNICODE_CHARS
char
是单个字符,因此增加了 wchar_t
char16_t
,适配 UTF-16char32_t
,适配 UTF-32wstring 等新字符串基本上没人用,大多数程序员为了不“自找麻烦”,还是选择最基本的 string。 Unicode 还有一个 UTF-8 编码方式,与单字节的 char 完全兼容,用 string 也足以适应大多数的应用场合
*end = 0
,这一句很重要,执行前,end 指向的是第一个换行符的位置;执行后,把换行符改成了 0void test_other_string_api() {
//单纯保存的是字符序列,使用 vector<char>
vector<char> char_array;
//字面量后缀, s 后缀,表示是一个 string 类型
auto str = "shixinzhang hahaha"s;
auto not_raw_string = "zhangshi \n xin hhha";
//想直接输出各种转义符,使用 raw string
auto raw_string = R"(zhangshi \n xin hhha)";
cout << not_raw_string << endl;
cout << raw_string << endl;
//字符串转数字 stoi, stof, stod
auto test_integer = "444.4";
cout << stoi(test_integer) << endl;
cout << stof(test_integer) << endl;
//to_string 数字转字符串
cout << to_string(1024) << endl;
}
void test_string_func() {
string name = "shixinzhang";
cout << "\nlength: " << name.length() << "size:" << name.size() << endl;
//c_str() 和 data() 都返回一个 const char* 指针,但 c_str() 会在末尾加一个 \0
cout << "c_str strlen: " << strlen((name.c_str())) << endl;
cout << "data strlen: " << strlen((name.data())) << endl;
cout << "substr:" << name.substr(0, 5) << endl;
//既可以查找一个子字符串,也可以查找某个字符(如果有多个,返回的是第一个)
cout << "find:" << name.find("shi") << " , " << name.find('i') << endl;
//也支持查找第一个和最后一个字符
cout << "find_first_of:" << name.find_first_of('n') << endl;
cout << "find_first_not_of:" << name.find_first_not_of('z') << endl;
cout << "find_last_of:" << name.find_last_of('i') << endl;
cout << "find_last_not_of:" << name.find_last_not_of('n') << endl;
}
void test_string_reg() {
auto make_regex = [](const string& str){
return std::regex(str);
};
auto make_match = [](){
return std::smatch();
};
//1.创建字符串, s 表明类型
auto str = "name:shixin-2016"s;
//2.创建正则表达式
auto reg = make_regex(R"(^(\w+):(\w+)-(\w+)$)");
//3.创建存储结果的 match
auto match = make_match();
//4.调用匹配
regex_match(str, match, reg);
cout << "regex result:" << match.size() << "\n" << endl;
//匹配结果可以像容器那样被访问,第0个是整个匹配的内容,后面的匹配的多个子串
for (const auto&x: match) {
cout << x << " , ";
}
cout << endl;
cout << "Parsed name: " << match[2] << endl;
cout << "Parsed age: " << match[3] << endl;
}
void StringTest::test() {
test_string_func();
test_other_string_api();
test_string_reg();
}
#include <vector>
#include <list>
#include <set>
#include <map>
#include <deque>
#include <forward_list>
容器里存储的是元素的拷贝、副本,而不是引用,尽量为元素实现转移构造和转移赋值函数,在加入容器的时候使用 std::move()
来“转移”,减少元素复制的成本(测试一下内存占用)
array 和 vector 直接对应 C 的内置数组,内存布局与 C 完全兼容,所以是开销最低、速度最快的容器。
有序容器
class Student {
public:
Student(int age) : age(age) {}
//const 函数,不修改成员函数
int operator+(int v) const {
return this->age + v;
}
//return-type operator操作符(参数)函数题
//Overloaded 'operator<' must be a binary operator (has 3 parameters)
friend bool operator<(const Student& a, const Student& b){
//这里的顺序,就是有序容器里的顺序(大的在前)
return a.age > b.age; // 自定义比较运算
}
friend ostream& operator << (ostream & os, const Student& s)
{
os << "student: " << s.age;
return os;
}
public:
int age;
};
void test_order_collection() {
//set, map, multiset, multimap
auto comparator = [](const Student&a, const Student&b) {
return a.age > b.age;
};
//1.自定义类,需要重载比较 < 操作符,才能放入 set
std::set<Student> students ;
//1.2 或者自定义比较器
// std::set<Student, decltype(comparator)> students ;
//emplace 可以直接构造元素,免去了构造后再拷贝、转移的成本
students.emplace(12); //无需 new Student(12)
//每次插入都会排序,如果数量很大时,需要考虑是否合理
students.emplace(16);
students.emplace(13);
//2.要输出内容,需要重载 << 操作符
for(const Student&x: students) {
std::cout << x << " , addr:" <<&x << " , size: " << sizeof(x) << std::endl;
}
}
无序容器
https://www.zhihu.com/question/44865154
2 点:
https://en.cppreference.com/w/cpp/header/iterator
https://en.cppreference.com/w/cpp/header/algorithm
stl 算法操作的是迭代器,这是泛型编程,分离数据和操作
通过容器的 begin() end() 可以获取指向两个端的迭代器(cbegin() 返回的是常量迭代器);也有通用函数:std::begin(容器对象)。
void test_iterator() {
typedef std::array<int, 6> my_array;
my_array a = {0,1,2,3,4, 4};
cout << "print array:" << endl;
for (auto iterator = a.begin(); iterator != a.end(); iterator++) {
//迭代器是个指针
cout << *iterator << " , ";
}
cout << endl;
//全局函数获取迭代器
auto begin = std::begin(a);
auto end = std::end(a);
//逆序迭代
auto reverse_begin = std::rbegin(a);
//返回的是常量迭代器
auto const_begin = std::cbegin(a);
//distance, 计算 2 个迭代器之间的距离
cout << "Distance of two iterator: " << std::distance(begin, end) << endl;
//获取下一步,不修改参数
begin = std::next(begin);
cout << "Distance of two iterator: " << std::distance(begin, end) << endl;
//往前走几步,修改参数
std::advance(begin, 2);
cout << "Distance of two iterator: " << std::distance(begin, end) << endl;
}
void test_algorithm() {
typedef std::array<int, 6> my_array;
my_array a = {4,2,1,3,5, 4};
cout << a[1] << endl;
auto begin = std::begin(a);
auto end = std::end(a);
//1.统计满足条件的个数
//结合 lambda 表达式写的很 easy
auto n =std::count_if(begin, end, [](auto x){
return x > 1;
});
cout << "Number of item which larger than 1 :" << n << endl;
//2.二分查找,返回的是是否存在
auto binary_search_result = std::binary_search(begin, end, 44);
cout << "binary_search_result: " << binary_search_result << endl;
//3.快速排序
std::sort(begin, end);
//4.另一种遍历方式,可以自定义每个 item 的输出内容
cout << "test_for_each" << endl;
//std::for_each 把要做的事情分成了两部分:一个遍历容器元素,另一个操纵容器元素
int index = 0;
std::for_each(begin, end, [&index](const auto&x){
if (x == 4) {
//可以通过修改捕获的参数的地址,来向外传递数据
index++;
}
cout << x << " ; ";
});
cout << "4 count: " << index << endl;
}
算法
https://time.geekbang.org/column/article/243357 要求排序后仍然保持元素的相对顺序,应该用 stable_sort,它是稳定的; 选出前几名(TopN),应该用 partial_sort; 选出前几名,但不要求再排出名次(BestN),应该用 nth_element; 中位数(Median)、百分位数(Percentile),还是用 nth_element; 按照某种规则把元素划分成两组,用 partition;第一名和最后一名,用 minmax_element。 最好在顺序容器 array/vector 上调用!
查找:
它俩的返回值构成一个区间,这个区间往前就是所有比被查找值小的元素,往后就是所有比被查找值大的元素: begin < x <= lower_bound < upper_bound < end
还有很多有用的方法,类似 memcpy 的 copy/move 算法(搭配插入迭代器)、检查元素的 all_of/any_of 算法,用好了都可以替代很多手写 for 循环。
一个最基本但也最容易被忽视的常识:“读而不写”就不会有数据竞争 多用 const 关键字,尽可能让操作都是只读的,为多线程打造一个坚实的基础。
std::atomic<int>
) 和一个 flag std::atomic_flag
get()
获取结果lock_guard, condition_variable, promise 等需要自己去查
c++20 引入了协程,用户态的线程,创建、切换成本低,开销更低,性能更好
#include <iostream>
#include <string>
#include <thread>
#include <unistd.h>
#include <future>
using namespace std;
//仅调用一次
//最好是静态、全局的(线程可见)
static std::once_flag flag;
//线程局部变量
thread_local int n = 0;
void test_once_flag() {
auto thread_print = []() {
//通过 std::call_once 调用,参数 flag 是否初始化作为是否执行
std::call_once(flag, []() {
cout << "thread_print:"<< endl;
});
};
//启动一个线程,执行参数函数
thread t(thread_print);
thread t2(thread_print);
t.join();
t2.join();
}
void test_thread_local() {
//按值捕获外部参数,内部要修改
auto f = [&](int p) {
n += p;
cout << this_thread::get_id() << " thread run: " << n << endl;
};
//启动一个线程,执行参数函数
thread t(f, 10);
thread t2(f, 20);
cout << "test_thread_local >>> " << endl;
//等待执行完
t.join();
t2.join();
}
//原子标记位
static atomic_flag flag_a(false);
void test_atomic() {
//atomic 禁用了拷贝构造函数,所以不能 = 赋值,而需要圆/花括号赋值
std::atomic<int> a{0};
// std::atomic<int> b(1);
auto f = [&](int sleep_millis) {
//修改标记位,通过标记位进行同步
auto set = flag_a.test_and_set();
if (set) {
cout << "flag has been set" << endl;
} else {
cout << "flg set by " << this_thread::get_id() << endl;
}
//时间字面量?
this_thread::sleep_for(sleep_millis * 100ms);
//修改
a.store(1);
//获取
cout << "load a: " << a.load() << endl;
};
thread thread_a(f, 5);
thread thread_b(f, 7);
thread_a.join();
thread_b.join();
}
void test_async() {
auto task = [](const auto& x) {
this_thread::sleep_for(x * 1ms);
cout << this_thread::get_id() << " sleep for " << x << endl;
return x * 2;
};
//不获取返回值的话,就变成了同步?!
std::async(task, 100);
//启动一个异步任务,不保证立刻执行
//返回一个 future
auto f = std::async(task, 100);
//等待执行结束
f.wait();
//获取结果, get 只能调用一次?
cout << "valid ? " << f.valid() << endl;
cout << "get ? " << f.get() << endl;
cout << "get 2 ? " << f.get() << endl;
}
void ConcurrentTest::run() {
test_once_flag();
test_thread_local();
test_atomic();
test_async();
}
https://time.geekbang.org/column/article/245880
json, messagePack, pb 都敲一下
为什么需要序列化,不能直接 memcpy 吗? 直接memcpy,同一种语言不同机器,或者不同语言可能存在兼容问题(变量内存存储布局、编码可能不同),而Json是一种标准,且不同语言间统一
json不存在大小端,字节序的问题吧,反正就是一个字符串。messagepack这种二进制格式的东西,才要考虑大小端。
使用 libcurl 进行网络请求的四个步骤:初始化句柄、设置参数、发送请求、清理句柄
(无捕获的 lambda 可以转成函数指针)
看的时候可以先从代码风格看起,再熟悉C++关键字的用法,再到整体架构、接口设计。不能心急,不要想着几天或者一个月就能看完。而且也没必要完全看懂,只要能从中学到一两个点就可以说是值得了。
写 Lua 扩展模块的时候,内部可以用 C++,但对外的接口必须转换成纯 C 函数(extern “C”)。
C++ 高效、灵活,但开发周期长、成本高,在混合系统里可以辅助其他语言,编写各种底层模块提供扩展功能,从而扬长避短;
Python 很“大众”,但比较复杂、性能不是特别高;而 Lua 比较“小众”,很小巧,有 LuaJIT 让它运行速度极快。你可以结合自己的实际情况来选择,比如语言的熟悉程度、项目的功能 / 性能需求、开发的难易度,等等。
从零开始学C++,我的建议是不要去抠那些内存管理、指针、构造/析构等细枝末节,先把C++当做java、Python来用,有了一些实际经验体会后再了解底层机制。 C++偏向在底层写高性能组件,Python实现业务逻辑,而服务器应用一般业务比较多,这样就难以发挥C++
在运行阶段能做、应该做的事情主要有三件:调试(Debug)、测试(Test)和性能分析(Performance Profiling)
GDB:让高速的 CPU 慢下来,让我们可以理清程序的状态
什么是性能分析呢?
你可以把它跟 Code Review 对比一下。Code Review 是一种静态的程序分析方法,在编码阶段通过观察源码来优化程序、找出隐藏的 Bug。而性能分析是一种动态的程序分析方法,在运行阶段采集程序的各种信息,再整合、研究,找出软件运行的“瓶颈”,为进一步优化性能提供依据,指明方向。
性能分析的关键是测量,而测量就需要使用工具
把 pstack 和 strace 结合起来,你大概就可以知道,进程在用户空间和内核空间都干了些什么。当进程的 CPU 利用率过高或者过低的时候,我们有很大概率能直接发现瓶颈所在。
perf 可以说是 pstack 和 strace 的“高级版”,它按照固定的频率去“采样”,相当于连续执行多次的 pstack,然后再统计函数的调用次数,算出百分比。只要采样的频率足够大,把这些“瞬时截面”组合在一起,就可以得到进程运行时的可信数据,比较全面地描述出 CPU 使用情况。
gperftools 是“侵入”式的性能分析工具,能够生成文本或者图形化的分析报告,最直观的方式是火焰图。
ASAN,检测内存问题
经典的《设计模式》一书里面介绍了 23 个模式,并依据设计目的把它们分成了三大类:创建型模式、结构型模式和行为模式。 这三类模式分别对应了开发面向对象系统的三个关键问题:如何创建对象、如何组合对象,以及如何处理对象之间的动态通信和职责分配。解决了这三大问题,软件系统的“架子”也就基本上搭出来了。
创建,结构,行为
模式里的结构和实现方式直接表现为代码,可能是最容易学习的部分,但我认为,其实这些反而是最不重要的。 你更应该去关注它的参与者、设计意图、面对的问题、应用的场合、后续的效果等代码之外的部分,它们通常比实现代码更重要。
因为代码是“死”的,只能限定由某几种语言实现,而模式发现问题、分析问题、解决问题的思路是“活”的,适用性更广泛,这种思考“What、Where、When、Why、How”并逐步得出结论的过程,才是设计模式专家经验的真正价值。
1.头文件 include guard(其实就是判断 #ifndef xx, #define xx, #endif) 通过判断内置属性,提供兼容处理的方法的宏,比如 deprecated 和 static_assert
2.自旋锁的头文件 类型别名,禁止拷贝构造和赋值函数,通过自旋重试、原子变量的 TAS 来判断获得锁
自定义的 LockGuard,用于在析构函数里 unlock
使用原子变量(atomic)可以实现自旋锁,比互斥量的成本要低,更高效
C++要求静态成员变量必须在cpp文件里定义实现,头文件里只是声明。 而在静态成员函数里声明静态变量,再以函数返回值的形式来使用,就不需要在cpp里重复写一遍,只要在头文件里写就行了。
1.类名使用 CamelCase,函数和变量用 snake_case,成员变量加“m_”前缀
在编译阶段使用静态断言,保证整数、浮点数的精度
使用 final 终结类继承体系,不允许别人产生子类
使用 default 显示定义拷贝构造、拷贝赋值、转移构造、转移赋值等重要函数
using 定义类型别名
使用 noexcept 标记不抛出异常,优化函数
使用 const 来修饰常函数;
2.【SalesData】 跟着敲一下
代码里显式声明了转移构造和转移赋值函数,这样,在放入容器的时候就避免了拷贝,能提高运行效率。
3.MessagePack
小巧轻便,而且用起来也很容易,只要在类定义里添加一个宏,就可以实现序列化:public: MSGPACK_DEFINE(m_id, m_sold, m_revenue); // 实现MessagePack序列化功能
4.Summary 类
关联了好几个类的核心类
类型别名对于它来说就特别重要,不仅可以简化代码,也方便后续的维护
锁不影响类的状态,所以要用 mutable 修饰??
lockGuard 使用很简单,申明一个全局 lock 变量,然后在访问数据的函数里,创建临时的 lockGuard(参数是这个 lock)。获取到 lock 时才会继续执行,然后在走出代码块后,就会析构。简单、安全
在使用 lambda 表达式的时候,要特别注意捕获变量的生命周期,如果是在线程里异步执行,应当尽量用智能指针的【值】捕获,虽然有点麻烦,但比较安全
5.搭建 http 服务
介绍及Windows:https://time.geekbang.org/column/article/100124 Mac/Linux:https://time.geekbang.org/column/article/146833 OpenResty 使用介绍:https://www.runoob.com/w3cnote/openresty-intro.htm
1.这个符号的确不存在
2.符号存在,但命名规则不对(C 与 C++ 不同)
今天遇到一个奇怪的问题,报错如下:
* What went wrong:
Execution failed for task ':profmancompat:externalNativeBuildRelease'.
> Build command failed.
Error while executing process /Users/simon/Library/Android/sdk/cmake/3.10.2.4988404/bin/ninja with arguments {-C /Users/simon/AndroidStudioProjects/profmancompat/profmancompat/.cxx/cmake/release/armeabi-v7a profmancompat-lib}
ninja: Entering directory `/Users/simon/AndroidStudioProjects/profmancompat/profmancompat/.cxx/cmake/release/armeabi-v7a'
[1/2] Building CXX object CMakeFiles/profmancompat-lib.dir/profmancompat.cpp.o
[2/2] Linking CXX executable profmancompat-lib
FAILED: profmancompat-lib
: && /Users/simon/Library/Android/sdk/ndk/21.0.6113669/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang++ --target=armv7-none-linux-androideabi16 --gcc-toolchain=/Users/simon/Library/Android/sdk/ndk/21.0.6113669/toolchains/llvm/prebuilt/darwin-x86_64 --sysroot=/Users/simon/Library/Android/sdk/ndk/21.0.6113669/toolchains/llvm/prebuilt/darwin-x86_64/sysroot -g -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -D_FORTIFY_SOURCE=2 -march=armv7-a -mthumb -Wformat -Werror=format-security -std=c++11 -g -Oz -DNDEBUG -Wl,--exclude-libs,libgcc_real.a -Wl,--exclude-libs,libatomic.a -static-libstdc++ -Wl,--build-id -Wl,--fatal-warnings -Wl,--exclude-libs,libunwind.a -Wl,--no-undefined -Qunused-arguments -Wl,--gc-sections CMakeFiles/profmancompat-lib.dir/profmancompat.cpp.o -o profmancompat-lib -L/Users/simon/AndroidStudioProjects/profmancompat/profmancompat/src/main/cpp/../jniLibs/armeabi-v7a -lprofman-slib -lprofman-29 -llog -latomic -lm && :
/Users/simon/Library/Android/sdk/ndk/21.0.6113669/toolchains/llvm/prebuilt/darwin-x86_64/lib/gcc/arm-linux-androideabi/4.9.x/../../../../arm-linux-androideabi/bin/ld: error: CMakeFiles/profmancompat-lib.dir/profmancompat.cpp.o:1:3: invalid character
/Users/simon/Library/Android/sdk/ndk/21.0.6113669/toolchains/llvm/prebuilt/darwin-x86_64/lib/gcc/arm-linux-androideabi/4.9.x/../../../../arm-linux-androideabi/bin/ld: error: CMakeFiles/profmancompat-lib.dir/profmancompat.cpp.o:1:3: syntax error, unexpected $end
/Users/simon/Library/Android/sdk/ndk/21.0.6113669/toolchains/llvm/prebuilt/darwin-x86_64/lib/gcc/arm-linux-androideabi/4.9.x/../../../../arm-linux-androideabi/bin/ld: error: CMakeFiles/profmancompat-lib.dir/profmancompat.cpp.o: not an object or archive
/Users/simon/Library/Android/sdk/ndk/21.0.6113669/toolchains/llvm/prebuilt/darwin-x86_64/sysroot/usr/lib/arm-linux-androideabi/16/crtbegin_dynamic.o:crtbegin.c:function _start_main: error: undefined reference to 'main'
clang++: error: linker command failed with exit code 1 (use -v to see invocation)
ninja: build stopped: subcommand failed.
重点是这几句:
搜了下关键字,回答基本上是 main 定义的问题,和我实际情况不符。
在尝试把代码里无关的字符删除后,还是不行,那报错信息里的字符究竟是哪儿来的呢?
后来盯着 CMakeList,看到这些编译、link 优化项,心想也没有可能是这些的配置导致的:
add_compile_options(-Oz -flto -ffunction-sections -fdata-sections -fexceptions -frtti)
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -O3 -flto -Wl,--exclude-libs,ALL -Wl,--gc-sections -Wl,--no-fatal-warnings")
从 https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html 里查到,-flto
大概的作用就是:在编译时,会在生成的目标文件里,插入一个特殊的格式信息(GIMPLE 格式)。然后在链接时,读取多个目标文件里的 GIMPLE 信息,合并成一个。这样 gcc 就能做一些内联优化,从而减少最终生成物体积。
那有没有可能是 -flto
优化过程中生成的信息导致了这个问题呢?
试着删除这个信息后,居然真的编译通过了!
这个问题耗费了些时间,虽然具体原因还不清楚,但希望遇到同样问题的同学,可以多一个尝试选择。