0%

源码地址

https://github.com/puzzzzzzle/algorithm_study/blob/master/algorithm_cpp/src/object_pool/ObjectPool.h

自动回收裸指针的对象池

原理

  1. c++ shared_ptr, 在析构时, 会调用deleter 策略, 一般用于 共享指针中存储数组
  2. 我们可以在这里做文章, 自定义deleter, 按照一定的策略, 选择释放内存还是回收内存
  3. 注意, 这种方式无法回收共享内存本身, 但是可以做到自动回收
  4. 适用于构造和析构消耗较大 的对象

获取对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ObjectT *AllocWithTrunk() {
for (int i = 0; i < KEEP_SIZE / 10; ++i) {
m_reusePool.Put(new ObjectT());
}
return new ObjectT();
}
ObjectPtrT Alloc() {
// 有可重用的, 就直接使用
ObjectT *rowPtr = m_reusePool.Take();
if (rowPtr == nullptr) {
// 没有就申请
rowPtr = AllocWithTrunk();
}
assert(rowPtr != nullptr);
m_constructor(rowPtr);
return std::shared_ptr<ObjectT>(
rowPtr, [this](ObjectT *ptr) { ReleaseObject(ptr); });
}
  1. 从池中尝试获取一个, 没拿到的话就分配一批

    1. 目前的策略是分配最大池的大小的1/10, 可以调整
  2. 分配的时候, 拿到裸指针后, 构建共享指针, 同时指定自定义的deleter

自动回收

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void ReleaseObject(ObjectT *ptr) {
bool needReuse = true;
// 析构池子时, 不再回收,
if (m_isStopping) {
needReuse = false;
}
if (m_reusePool.Size() > KEEP_SIZE) {
needReuse = false;
}

m_destructor(ptr);
if (needReuse) {
// 回收一个对象

// 这里不会有问题, 前一个shared_ptr 调用了 deleter 后, 就不再管这个指针了,
// 只要我们没有真的delete, 相当与这个指针泄漏了
// 但是这里把这个指针保存起来了, 下次再用, 就没有泄漏
m_reusePool.Put(ptr);
} else {
// 直接释放, 不再回收
delete ptr;
}
}
  1. 当共享指针计数为0时, 会调用deleter, 回调到我们自定义的ReleaseObject
  2. 首先调用已定义的析构策略
  3. 按照一定的策略决定要不要回收
    1. 不回收的直接delete
    2. 回收的 再次放回池子中

释放内存池

1
2
3
4
5
6
7
8
9
~ObjectPool() {
// 标记不再回收
m_isStopping = true;
// 释放所有对象
while (m_reusePool.Size() > 0) {
auto *rowPtr = m_reusePool.Take();
delete rowPtr;
}
}
  1. 释放对象池时, 要销毁所有对象, 释放池子中的对象
  2. 这时候要停止回收
    1. 我们设置 m_isStopping 为true, 这时候后续的都不会被回收

自定义策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 自动回收的对象池实现
* 线程安全性取决于 PoolType 是否是线程安全的
* 只回收裸指针, shared_ptr不会回收
* @tparam Object 要存储的数据类型
* @tparam PoolType 用于存储池子中的对象, 默认是一个有锁的队列, 多线程安全性取决于它
* @tparam KEEP_SIZE_NUM 决定池子保存的对象的最大大小和每次分配量
* @tparam Constructor 从池子中获取一个对象前总会调用的策略
* @tparam Destructor 一个对象共享指针计数为0 时总会调用, 无论是否放回池子
*/
template <typename Object,
typename ReusePoolType = ThreadSafeQueuePoolType<Object *>,
size_t KEEP_SIZE_NUM = 10000,
typename Constructor = DefaultObjectConstructor<Object *>,
typename Destructor = DefaultObjectDestructor<Object *>>
class ObjectPool
  1. Object: 要存储的数据类型
  2. ReusePoolType: 用于存储池子中的对象, 默认是一个有锁的队列, 多线程安全性取决于它
  3. KEEP_SIZE_NUM : 决定池子保存的对象的最大大小和每次分配量
  4. Constructor : 从池子中获取一个对象前总会调用的策略
  5. Destructor : 一个对象共享指针计数为0 时总会调用, 无论是否放回池子
  6. 构造和析构策略满足下面的格式就行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 每个对象被get 时 调用的构造方法
* @tparam Object
*/
template <typename Object>
struct DefaultObjectConstructor {
using ObjectT = Object;
void operator()(ObjectT object) const {}
};
/**
* 每个对象被 release, 重新放回 pool 时 调用的析构方法
* @tparam Object
*/
template <typename Object>
struct DefaultObjectDestructor {
using ObjectT = Object;
void operator()(ObjectT object) const {}
};

多线程安全

  1. 这个对象池除了 对象存储外都是多线程安全的

    1. 因此, 只要 ReusePoolType 是多线程安全的, 那池子本身就是安全的
  2. 满足如下存储器策略即可
    1.

    1
    2
    3
    4
    5
    6
    template <typename Object>
    struct PoolType {
    void Put(ObjectT ptr) ;
    ObjectT Take();
    size_t Size();
    };
  3. 默认提供一个基于锁的线程安全存储器, 无锁版本可以包装一个无锁队列就行

    1. 有需要的话可以使用 moodycamel 实现的无锁安全队列
    2. https://github.com/cameron314/concurrentqueue
1
2
3
4
5
6
7
8
9
/**
* 线程安全的队列池
* 锁实现的性能较差
* 有需要的话可以使用 moodycamel 实现的无锁安全队列
* https://github.com/cameron314/concurrentqueue
* @tparam Object
*/
template <typename Object>
struct ThreadSafeQueuePoolType

手动回收共享指针的对象池

  1. 线程安全性取决于 PoolType 是否是线程安全的
  2. 连 shared_ptr 也一同回收,但是需要手动调用回收函数,如果回收时, 引用计数不为1, 就会放弃回收
  3. 回收后, 原来的指针会被置为null
  4. 如果一个对象没有被 ReleaseObject 那就不会调用 Destructor, 而是直接使用默认的析构函数
  5. 需要保证析构函数和 Destructor 都可以做到资源释放, 且二次释放没有问题
  6. 用于 对象量极大, 连shared_ptr的构造和析构都是瓶颈的地方
1
2
3
4
5
6
7
template <typename Object,
typename ReusePoolType =
ThreadSafeQueuePoolType<std::shared_ptr<Object>>,
size_t KEEP_SIZE_NUM = 10000,
typename Constructor = DefaultObjectConstructor<Object *>,
typename Destructor = DefaultObjectDestructor<Object *>>
class ObjectManulPool

手动回收

  1. 使用完毕后需要手动调用TryPushBack
1
2
3
4
5
6
7
8
bool TryPushBack(ObjectPtrT &ptr) {
if (ptr.use_count() == 1) {
ReleaseObject(ptr);
ptr = nullptr;
return true;
}
return false;
}

容错机制

  1. 会检查use_count, 只回收use_count() == 1的, 回收完原来的指针会被置为nullptr
    1. 保证还在使用的对象不会被回收
  2. 对于忘记回收的, 由 shared_ptr来保证进行收尾, 不会泄漏
  3. 即使吧不是由这个对象池构造的对象进行回收也没问题, 可以兼容

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
(lldb) call PyUnicode_FromWideChar(L"算法符合", -1)
(_object *) $16 = 0x000001fd3a9e0a50
(lldb) p *$16
(_object) $17 = {
ob_refcnt = 1
ob_type = 0x00007fff35396140
}
(lldb) p *(PyASCIIObject *)$16
(PyASCIIObject) $18 = {
ob_base = {
ob_refcnt = 1
ob_type = 0x00007fff35396140
}
length = 4
hash = -1
state = (interned = 0, kind = 2, compact = 1, ascii = 0, ready = 1)
wstr = 0x000001fd3a9e0a98 L"算法符合"
}
(lldb) p *(PyCompactUnicodeObject *)$16
(PyCompactUnicodeObject) $20 = {
_base = {
ob_base = {
ob_refcnt = 1
ob_type = 0x00007fff35396140
}
length = 4
hash = -1
state = (interned = 0, kind = 2, compact = 1, ascii = 0, ready = 1)
wstr = 0x000001fd3a9e0a98 L"算法符合"
}
utf8_length = 0
utf8 = 0x0000000000000000
wstr_length = 4
}
(lldb) p *(PyUnicodeObject *)$16
(PyUnicodeObject) $19 = {
_base = {
_base = {
ob_base = {
ob_refcnt = 1
ob_type = 0x00007fff35396140
}
length = 4
hash = -1
state = (interned = 0, kind = 2, compact = 1, ascii = 0, ready = 1)
wstr = 0x000001fd3a9e0a98 L"算法符合"
}
utf8_length = 0
utf8 = 0x0000000000000000
wstr_length = 4
}
data = (interned = 3, kind = 5, compact = 0, ascii = 0, ready = 1)
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
PyObject * PyUnicode_FromWideChar(const wchar_t *u, Py_ssize_t size)
PyObject * PyUnicode_New(Py_ssize_t size, Py_UCS4 maxchar)
_PyUnicode_CONVERT_BYTES

struct_size = sizeof(PyCompactUnicodeObject);
if (maxchar < 128) {
kind = PyUnicode_1BYTE_KIND;
char_size = 1;
is_ascii = 1;
struct_size = sizeof(PyASCIIObject);
}

obj = (PyObject *) PyObject_Malloc(struct_size + (size + 1) * char_size);

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
(lldb) p *(PyASCIIObject *)unicode
(PyASCIIObject) $18 = {
ob_base = {
ob_refcnt = 1
ob_type = 0x00007fff35396140
}
length = 67
hash = -1
state = (interned = 0, kind = 1, compact = 1, ascii = 1, ready = 1)
wstr = 0x0000000000000000
}

do {
unsigned char *_to = (unsigned char *)(((
Py_UCS1
*)((void)((!!(PyType_HasFeature((((PyObject *)(unicode))->ob_type),
(1UL << 28)))) ||
(_wassert(L"PyUnicode_Check(unicode)", L"_file_name_",
(unsigned)(2255)),
0)),
(((PyASCIIObject *)(unicode))->state.compact)
? (((void)((!!(PyType_HasFeature(
(((PyObject *)(unicode))->ob_type),
(1UL << 28)))) ||
(_wassert(L"PyUnicode_Check(unicode)",
L"_file_name_", (unsigned)(2255)),
0)),
(void)((!!((((PyASCIIObject *)unicode)->state.ready))) ||
(_wassert(L"PyUnicode_IS_READY(unicode)",
L"_file_name_", (unsigned)(2255)),
0)),
((PyASCIIObject *)unicode)->state.ascii)
? ((void *)((PyASCIIObject *)(unicode) + 1))
: ((void *)((PyCompactUnicodeObject *)(unicode) + 1)))
: ((void)((!!(((PyUnicodeObject *)(unicode))->data.any)) ||
(_wassert(L"((PyUnicodeObject*)(unicode))->data.any",
L"_file_name_", (unsigned)(2255)),
0)),
((((PyUnicodeObject *)(unicode))->data.any))))));
const Py_UNICODE *_iter = (const Py_UNICODE *)(u);
const Py_UNICODE *_end = (const Py_UNICODE *)(u + size);
Py_ssize_t n = (_end) - (_iter);
const Py_UNICODE *_unrolled_end = _iter + ((size_t)(n) & ~(size_t)((4) - 1));
while (_iter < (_unrolled_end)) {
_to[0] = (unsigned char)_iter[0];
_to[1] = (unsigned char)_iter[1];
_to[2] = (unsigned char)_iter[2];
_to[3] = (unsigned char)_iter[3];
_iter += 4;
_to += 4;
}
while (_iter < (_end))
*_to++ = (unsigned char)*_iter++;
} while (0)


(lldb) p ((void *)((PyASCIIObject *)(unicode) + 1))
(void *) $19 = 0x0000021aa88448e0
(lldb) p ((const char *)((PyASCIIObject *)(unicode) + 1))
(const char *) $20 = 0x0000021aa88448e0 "G:\projects\open_source_projects\cpython\PCbuild\amd64\python_d.exe"

c++ union 中使用非POD对象

c++ 定义

1
2
3
4
5
6
7
8
9
(C++11 前)
联合体不能含有带非平凡特殊成员函数(复制构造函数、复制赋值运算符或析构函数)的非静态数据成员。

(C++11 起)
若联合体含有带非平凡特殊成员函数(复制/移动构造函数,复制/移动赋值,或析构函数)的非静态数据成员,则联合体中的该函数默认被弃置,且需要程序员显式定义它。

若联合体含有带非平凡默认构造函数的非静态数据成员,则该联合体的默认构造函数默认被弃置,除非联合体的变体成员拥有一个默认成员初始化器。

至多一个变体成员可以拥有默认成员初始化器。
  1. c++11 起, 可以使用 非POD对象, 但是 c++不会帮你调用默认的构造和析构函数, 必须自己定义

对于POD数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
union S {
std::int32_t n; // 占用4字节
std::uint16_t s[2]; // 占用4字节
std::uint8_t c; // 占用1字节
}; // 整体占用4字节

TEST(build_in, 1) {
S s = {0x12345678}; // 初始化第一个成员,当前s.n为活跃成员
// 于此点,读取 s.s 或 s.c 是未定义行为
std::cout << std::hex << "s.n = " << s.n << '\n';
s.s[0] = 0x0011; // s.s 现在是活跃成员
// 在此点,读取 s.n 或 s.c 是未定义行为
std::cout << "s.c is now " << +s.c << '\n' // 11 or 00, 依赖平台实现
<< "s.n is now " << s.n << '\n'; // 12340011 or 00115678
}
  1. 这个很好理解, c++只是提供了一块内存, 保证可以放下最大的, 怎么解析是程序员的事情

对于非POD对象

用于测试的c++ class:

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

namespace MyT {

class MyClass {
public:
std::string name{};
std::string ToString() const {
return "MyClass:" + name + "\t@" + std::to_string((intptr_t)this);
}
MyClass(){LOG_DEBUG(ToString())};
MyClass(const std::string& _name) {
name = _name;
LOG_DEBUG(ToString())
}
MyClass(const MyClass& obj) {
name = obj.name;
LOG_DEBUG("this: " << ToString() << "\tobj: " << obj.ToString())
};
MyClass(MyClass&& obj) {
LOG_DEBUG("\tobj: " << obj.ToString())
std::swap(name, obj.name);
LOG_DEBUG("this: " << ToString())
};
virtual ~MyClass(){LOG_DEBUG(ToString())};
MyClass& operator=(const MyClass& obj) {
LOG_DEBUG("this: " << ToString() << "\tobj: " << obj.ToString())
name = obj.name;
LOG_DEBUG("this: " << ToString() << "\tobj: " << obj.ToString())
return *this;
};

MyClass& operator=(MyClass&& obj) {
LOG_DEBUG("\tobj: " << obj.ToString())
std::swap(name, obj.name);
LOG_DEBUG("this: " << ToString())
return *this;
};
};
} // namespace MyT
  1. 调用构造, 拷贝, 移动, 析构时都打条log, 方便我们定位
  2. 测试代码:
    1. 需要注意: MyClass c4 = c2 会调用复制构造函数, MyClass c5; c5 = c2; 才会调用operator=(&)
1
2
3
4
5
6
7
8
9
10
11
12
13
TEST(my_class, 1) {
using MyT::MyClass;
MyClass c1{};
MyClass c2{"khalid"};
MyClass c3(c2);
// 没有调用 operator= 而是使用了复制构造函数, 这是为了做优化
// 而赋值操作符要求‘=’的左右对象均已存在,它的作用就是把‘=’右边的对象的值赋给左边的对象
MyClass c4 = c2;
// 这种 的左右对象均已存在 才会
MyClass c5;
c5 = c2;
MyClass c6(std::move(c2));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[1]    [debug]    [MyClass:	@140733882434096]   [/tmp/tmp.s62aUyvWJo/src/cpp_language/union_adt_test/main.cpp:44    MyT::MyClass::MyClass()]     [2021-05-25 11:04:36.885673]    [0x00005efc]    [0x00007f3d8c6ad780]    []
[2] [debug] [MyClass:khalid @140733882434080] [/tmp/tmp.s62aUyvWJo/src/cpp_language/union_adt_test/main.cpp:47 MyT::MyClass::MyClass(const string&)] [2021-05-25 11:04:36.885830] [0x00005efc] [0x00007f3d8c6ad780] []
[3] [debug] [this: MyClass:khalid @140733882434064 obj: MyClass:khalid @140733882434080] [/tmp/tmp.s62aUyvWJo/src/cpp_language/union_adt_test/main.cpp:51 MyT::MyClass::MyClass(const MyT::MyClass&)] [2021-05-25 11:04:36.885914] [0x00005efc] [0x00007f3d8c6ad780] []
[4] [debug] [this: MyClass:khalid @140733882434048 obj: MyClass:khalid @140733882434080] [/tmp/tmp.s62aUyvWJo/src/cpp_language/union_adt_test/main.cpp:51 MyT::MyClass::MyClass(const MyT::MyClass&)] [2021-05-25 11:04:36.885991] [0x00005efc] [0x00007f3d8c6ad780] []
[5] [debug] [MyClass: @140733882434032] [/tmp/tmp.s62aUyvWJo/src/cpp_language/union_adt_test/main.cpp:44 MyT::MyClass::MyClass()] [2021-05-25 11:04:36.886049] [0x00005efc] [0x00007f3d8c6ad780] []
[6] [debug] [this: MyClass: @140733882434032 obj: MyClass:khalid @140733882434080] [/tmp/tmp.s62aUyvWJo/src/cpp_language/union_adt_test/main.cpp:60 MyT::MyClass& MyT::MyClass::operator=(const MyT::MyClass&)] [2021-05-25 11:04:36.886101] [0x00005efc] [0x00007f3d8c6ad780] []
[7] [debug] [this: MyClass:khalid @140733882434032 obj: MyClass:khalid @140733882434080] [/tmp/tmp.s62aUyvWJo/src/cpp_language/union_adt_test/main.cpp:62 MyT::MyClass& MyT::MyClass::operator=(const MyT::MyClass&)] [2021-05-25 11:04:36.886158] [0x00005efc] [0x00007f3d8c6ad780] []
[8] [debug] [ obj: MyClass:khalid @140733882434080] [/tmp/tmp.s62aUyvWJo/src/cpp_language/union_adt_test/main.cpp:54 MyT::MyClass::MyClass(MyT::MyClass&&)] [2021-05-25 11:04:36.886211] [0x00005efc] [0x00007f3d8c6ad780] []
[9] [debug] [this: MyClass:khalid @140733882434016] [/tmp/tmp.s62aUyvWJo/src/cpp_language/union_adt_test/main.cpp:56 MyT::MyClass::MyClass(MyT::MyClass&&)] [2021-05-25 11:04:36.886264] [0x00005efc] [0x00007f3d8c6ad780] []
[10] [debug] [MyClass:khalid @140733882434016] [/tmp/tmp.s62aUyvWJo/src/cpp_language/union_adt_test/main.cpp:58 virtual MyT::MyClass::~MyClass()] [2021-05-25 11:04:36.886315] [0x00005efc] [0x00007f3d8c6ad780] []
[11] [debug] [MyClass:khalid @140733882434032] [/tmp/tmp.s62aUyvWJo/src/cpp_language/union_adt_test/main.cpp:58 virtual MyT::MyClass::~MyClass()] [2021-05-25 11:04:36.886365] [0x00005efc] [0x00007f3d8c6ad780] []
[12] [debug] [MyClass:khalid @140733882434048] [/tmp/tmp.s62aUyvWJo/src/cpp_language/union_adt_test/main.cpp:58 virtual MyT::MyClass::~MyClass()] [2021-05-25 11:04:36.886416] [0x00005efc] [0x00007f3d8c6ad780] []
[13] [debug] [MyClass:khalid @140733882434064] [/tmp/tmp.s62aUyvWJo/src/cpp_language/union_adt_test/main.cpp:58 virtual MyT::MyClass::~MyClass()] [2021-05-25 11:04:36.886466] [0x00005efc] [0x00007f3d8c6ad780] []
[14] [debug] [MyClass: @140733882434080] [/tmp/tmp.s62aUyvWJo/src/cpp_language/union_adt_test/main.cpp:58 virtual MyT::MyClass::~MyClass()] [2021-05-25 11:04:36.886515] [0x00005efc] [0x00007f3d8c6ad780] []
[15] [debug] [MyClass: @140733882434096] [/tmp/tmp.s62aUyvWJo/src/cpp_language/union_adt_test/main.cpp:58 virtual MyT::MyClass::~MyClass()] [2021-05-25 11:04:36.886564] [0x00005efc] [0x00007f3d8c6ad780] []
Process finished with exit code 0

包含非POD的union

一个错误的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// error
namespace t0 {
union A {
int a;
double b;
std::string c;
MyClass d;
std::shared_ptr<MyClass> e;
};
// default constructor of 'A0' is implicitly deleted because variant field 'c'
// has a non-trivial default constructor
// c 不是平凡类型, 必须自定义构造函数
TEST(adt,1)
{
A s;
}
}
  1. 无法通过编译, c 不是POD对象, 必须自定义构造函数

一个可用的例子

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
// 主动调用析构函数, 包装下免得调错
template <class T>
inline void DestoryAt(T& t) {
t.~T();
}
namespace t1 {
union A {
std::string c;
MyT::MyClass d;
std::shared_ptr<MyT::MyClass> e;
A() {} // 因为std::string拥有是非平凡的的数据类型,
~A() {} // 则A必须自定义构造函数和析构函数,否则无法进行实例化
// 如果想实现复制语义,还得自定义复制(构造)函数
};
TEST(adt, 1) {
A s;
// 激活 d
new (&s.d) MyT::MyClass();
// d可以使用了
s.d.name = "in union";
LOG_DEBUG("&s.d " << &s.d)
// 必须手动释放 d
// union 不会自动调用析构函数
// 下面两种方法都行, 包装下免得调错
DestoryAt(s.d);
// s.d.~MyClass();

// 激活e
new (&s.e) std::shared_ptr<MyT::MyClass>();
// 初始化一个 MyClass
s.e = std::make_shared<MyT::MyClass>();
s.e->name = "union ptr";
// 释放e
DestoryAt(s.e);
}
} // namespace t1
1
2
3
4
5
[1]    [debug]    [MyClass:	@140730698243312]   [/tmp/tmp.s62aUyvWJo/src/cpp_language/union_adt_test/main.cpp:44    MyT::MyClass::MyClass()]     [2021-05-25 11:13:42.707161]    [0x0000648e]    [0x00007f5df07a5780]    []
[2] [debug] [&s.d 0x7ffe6b471cf0] [/tmp/tmp.s62aUyvWJo/src/cpp_language/union_adt_test/main.cpp:126 virtual void t1::adt_1_Test::TestBody()] [2021-05-25 11:13:42.707324] [0x0000648e] [0x00007f5df07a5780] []
[3] [debug] [MyClass:in union @140730698243312] [/tmp/tmp.s62aUyvWJo/src/cpp_language/union_adt_test/main.cpp:58 virtual MyT::MyClass::~MyClass()] [2021-05-25 11:13:42.707407] [0x0000648e] [0x00007f5df07a5780] []
[4] [debug] [MyClass: @22852584] [/tmp/tmp.s62aUyvWJo/src/cpp_language/union_adt_test/main.cpp:44 MyT::MyClass::MyClass()] [2021-05-25 11:13:42.707480] [0x0000648e] [0x00007f5df07a5780] []
[5] [debug] [MyClass:union ptr @22852584] [/tmp/tmp.s62aUyvWJo/src/cpp_language/union_adt_test/main.cpp:58 virtual MyT::MyClass::~MyClass()] [2021-05-25 11:13:42.707535] [0x0000648e] [0x00007f5df07a5780] []
  1. 这里构造和析构函数啥都没干, 也就是说不会自动构造和析构
  2. 我们必须手动构造(使用 placement new), 手动析构(显示调用~ClassName())
  3. 但这是证明union中放非POD对象是可行的

一种常见的封装方式

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
71
72
73

namespace t2 {
// 使用struct包装
struct SA {
enum class Tag : int {
STRING = 0,
MY_CLASS,
MY_CLASS_SPTR,
};
const Tag tag;
union {
std::string c;
MyT::MyClass d;
std::shared_ptr<MyT::MyClass> e;
};
SA() = delete;
// TODO
SA(const SA&) = delete;
SA(const SA&&) = delete;
SA& operator=(const SA&) = delete;
SA& operator=(SA&&) = delete;


SA(Tag t) : tag(t) {
switch (tag) {
case Tag::STRING:
new (&c) decltype(c);
break;
case Tag::MY_CLASS:
new (&d) decltype(d);
break;
case Tag::MY_CLASS_SPTR:
new (&e) decltype(e);
break;
default:
assert(false);
break;
}
}

virtual ~SA() {
switch (tag) {
case Tag::STRING:
DestoryAt(c);
break;
case Tag::MY_CLASS:
DestoryAt(d);
break;
case Tag::MY_CLASS_SPTR:
DestoryAt(e);
break;
default:
assert(false);
break;
}
}
};
TEST(adt, struct_type) {
{
SA myclass(SA::Tag::MY_CLASS);
myclass.d.name = "SA";
LOG_DEBUG(myclass.d.ToString());
}

{
SA myclassPtr(SA::Tag::MY_CLASS_SPTR);
myclassPtr.e = std::make_shared<decltype(myclassPtr.e)::element_type>();
myclassPtr.e->name = "SAP";
LOG_DEBUG(myclassPtr.e->ToString());
}

}
} // namespace t2
1
2
3
4
5
6
[1]    [debug]    [MyClass:	@140731968851312]   [/tmp/tmp.s62aUyvWJo/src/cpp_language/union_adt_test/main.cpp:44    MyT::MyClass::MyClass()]     [2021-05-25 11:15:15.048335]    [0x0000659b]    [0x00007fec328fe780]    []
[2] [debug] [MyClass:SA @140731968851312] [/tmp/tmp.s62aUyvWJo/src/cpp_language/union_adt_test/main.cpp:203 virtual void t2::adt_struct_type_Test::TestBody()] [2021-05-25 11:15:15.048452] [0x0000659b] [0x00007fec328fe780] []
[3] [debug] [MyClass:SA @140731968851312] [/tmp/tmp.s62aUyvWJo/src/cpp_language/union_adt_test/main.cpp:58 virtual MyT::MyClass::~MyClass()] [2021-05-25 11:15:15.048486] [0x0000659b] [0x00007fec328fe780] []
[4] [debug] [MyClass: @26641416] [/tmp/tmp.s62aUyvWJo/src/cpp_language/union_adt_test/main.cpp:44 MyT::MyClass::MyClass()] [2021-05-25 11:15:15.048515] [0x0000659b] [0x00007fec328fe780] []
[5] [debug] [MyClass:SAP @26641416] [/tmp/tmp.s62aUyvWJo/src/cpp_language/union_adt_test/main.cpp:210 virtual void t2::adt_struct_type_Test::TestBody()] [2021-05-25 11:15:15.048543] [0x0000659b] [0x00007fec328fe780] []
[6] [debug] [MyClass:SAP @26641416] [/tmp/tmp.s62aUyvWJo/src/cpp_language/union_adt_test/main.cpp:58 virtual MyT::MyClass::~MyClass()] [2021-05-25 11:15:15.048571] [0x0000659b] [0x00007fec328fe780] []
  1. 这里使用 TLV的思路, 使用Tag+Value进行封装, 使用struct进行包装
  2. 这样外部使用的时候就类似在使用一个struct而非union, 实现自动构造&析构
  3. 当然, 这只是个简单的例子, 实际使用的时候, 还可以实现各种构造函数和operator, 使他更像struct, 还可以把union对象private化, 使用函数获取, 这样就可以校验类型是否对的上
    1. 当然, 一般都是用脚本生成的, 我在项目中是用Python写了个protobuf 的protoc后端, 用jinja2直接渲染出代码用

cmake execute_process 用法

原文档

  1. https://cmake.org/cmake/help/latest/command/execute_process.html
  2. Runs the given sequence of one or more commands.
  3. Commands are executed concurrently as a pipeline, with the standard output of each process piped to the standard input of the next. A single standard error pipe is used for all processes.
  4. COMMAND
    1. A child process command line.
    2. CMake executes the child process using operating system APIs directly. All arguments are passed VERBATIM to the child process. No intermediate shell is used, so shell operators such as > are treated as normal arguments. (Use the INPUT_*, OUTPUT_*, and ERROR_* options to redirect stdin, stdout, and stderr.)
    3. If a sequential execution of multiple commands is required, use multiple execute_process() calls with a single COMMAND argument.

总结如下

  1. 虽然文中说会被逐字传递给子进程, 但是, 以${} 包围的代码会被提前解析, 使用cmake 中的值替代
    1. 也可以使用环境变量传递
  2. 可以运行多个命令
  3. 但是上一个命令的标准输出会被管道发送到下一个命令的标准输入
    1. 所以多个Command 都有输出的话, 只有最有一个的输出会被保存到OUTPUT_VARIABLE
    2. 前面的都传入给下一个了
  4. 如果需要多个命令的输出, 使用多个execute_process, 每个包含一个Command

实际使用注意点

  1. COMMAND 并不是按照shell执行的, 而是直接起一个进程, 执行输入的命令
    1. 所以COMMAND echo $PATH 会直接输出 $PATH, 而非解析 $PATH 值, 因为这是在直接执行 echo 可执行程序, 环境变量的解析 是shell干的活
      1. 这里没有shell
  2. 需要执行shell命令的 使用类似 COMMAND sh -c "echo $PATH"
    1. 这样会起一个sh进程, 再由sh进程执行 echo 命令, 这样就有环境变量了

一个接受参数的并使用的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 设置参数
# env 中设置 参数, COMMAND 可以通过 $ARG_TO_PASS 获取, 但是 ${ARG_TO_PASS} 会被提前解析, 传入cmd 中的时候, 就已经是 它的值了
set(ENV{ARG_TO_PASS} "from cmake, env var")
set(ARG_TO_PASS "this is cmake var")

execute_process(
# 有了sh 后 就可以使用sh中带不带括号的方式获取变量了
# 但是注意: $ARG_TO_PASS 和 cmake 中的变量没关系, 已经被设置为系统环境变量了
# ${ARG_TO_PASS} 在执行前, 已经被cmake 替换了, 传入的就是 "this is cmake var"
COMMAND sh -c "echo $ARG_TO_PASS ------ ${ARG_TO_PASS}"
OUTPUT_VARIABLE _RES
ERROR_VARIABLE _RES
ERROR_QUIET
)
message("run result: ${_RES}")

输出

1
run result: from cmake, env var ------ this is cmake var

cmake 查看预处理文件

  1. 虽然clion自带的宏展开挺方便的, 但是偶尔需要整个文件预处理后的结果, 用于分析代码膨胀等问题
  2. cmake 直接生成了.o文件, 没法直接查看
  3. 去 cmake-build 文件夹下, 可以发现有个Makefile, 查看后其实就是项目的所有.o和输出目标
    1. 有多层cmake管理的有时候需要编译一下才会生成子目录的MakeFile, 需要进入对应的子目录的Makefile文件夹中才可以
  4. 直接make help 就可以看到有有输出.i .s .o的target
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
➜  thread_test_2 make help
The following are some of the valid targets for this Makefile:
... all (the default if no target is provided)
... clean
... depend
... edit_cache
... rebuild_cache
... stl_thread_test_2
... __/__/__/cpp_bootstrap/common/common_include/common_funcs.o
... __/__/__/cpp_bootstrap/common/common_include/common_funcs.i
... __/__/__/cpp_bootstrap/common/common_include/common_funcs.s
... __/__/__/cpp_bootstrap/common/common_include/common_includes.o
... __/__/__/cpp_bootstrap/common/common_include/common_includes.i
... __/__/__/cpp_bootstrap/common/common_include/common_includes.s
... __/__/__/cpp_bootstrap/common/common_include/log_init.o
... __/__/__/cpp_bootstrap/common/common_include/log_init.i
... __/__/__/cpp_bootstrap/common/common_include/log_init.s
... main.o
... main.i
... main.s
  1. 按需make需要的.i .s就行拉
1
2
3
4
5
6
7
8
➜  thread_test_2 make main.i                                     
Preprocessing CXX source to CMakeFiles/stl_thread_test_2.dir/main.cpp.i
➜ thread_test_2 ll
总用量 24K
drwxr-xr-x 3 root root 4.0K 4月 30 10:55 CMakeFiles
-rw-r--r-- 1 root root 1.2K 4月 30 10:44 cmake_install.cmake
-rw-r--r-- 1 root root 13K 4月 30 10:44 Makefile
➜ thread_test_2 cat CMakeFiles/stl_thread_test_2.dir/main.cpp.i

type id 细节

typeid 细节

  1. typeid 返回中:
    1. Parent, const Parent, const Parent &, const Parent && 全部一样, 会退化掉各种修饰 -> N10TestTypeID8ClassDef6ParentE
    2. (const Parent *, Parent const *) , const Parent const 一样, 都是个指针(P), 且指针不可变(const, PK), 会忽略掉执行对象的可变性 -> PKN10TestTypeID8ClassDef6ParentE
    3. Parent *, Parent const 一样,都是个指针(P), 指针可变(, P), 会忽略掉执行对象的可变性 -> PN10TestTypeID8ClassDef6ParentE
    4. 总结: 首先退化掉class 对象的各种const限定
      1. 然后就有三种类型: 原始对象, 指针对象, const指针对象(指针不可变, 指向的对象不管)
      2. 当然, 二级指针, 三级指针也是类似的

测试代码

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
namespace TestTypeID {
namespace ClassDef {

class Parent {
virtual ~Parent() = default;
virtual std::string Hello() { return "hello from parent"; }
virtual void CopyFrom(const Parent *const obj) {}
};
class Child {
virtual std::string Hello() { return "hello from Child"; }
virtual void CopyFrom(const Parent *const obj) {}
};
} // namespace ClassDef
} // namespace TestTypeID
using namespace TestTypeID::ClassDef;
inline static void printTypeIdInfo(){
#define PRINT_TYPE(type) \
LOG_WARNING(#type << "\t" << typeid(type).name() << "\t" \
<< typeid(type).hash_code())
//
PRINT_TYPE(Parent)
PRINT_TYPE(Child)
//
PRINT_TYPE(const Parent)
PRINT_TYPE(const Child)
//
PRINT_TYPE(const Parent &)
PRINT_TYPE(const Child &)
//
PRINT_TYPE(const Parent &&)
PRINT_TYPE(const Child &&)
//
PRINT_TYPE(Parent *)
PRINT_TYPE(Child *)
// 下面这两组等价
PRINT_TYPE(const Parent *)
PRINT_TYPE(const Child *)
PRINT_TYPE(Parent const *)
PRINT_TYPE(Child const *)
// this 指针就是这种类型
PRINT_TYPE(Parent *const)
PRINT_TYPE(Child *const)
//
PRINT_TYPE(const Parent *const)
PRINT_TYPE(const Child *const)}

结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[1]    [warning]    [Parent	N10TestTypeID8ClassDef6ParentE	13888289143273616310]   [/data/root/cpp_study/src/cpp_language/typeid/main.cpp:37    void printTypeIdInfo()]     [2021-04-08 19:46:46.200666]    [0x00002841]    [0x00007f3144fa3840]    [MySink1]
[2] [warning] [Child N10TestTypeID8ClassDef5ChildE 281579981607273881] [/data/root/cpp_study/src/cpp_language/typeid/main.cpp:38 void printTypeIdInfo()] [2021-04-08 19:46:46.200714] [0x00002841] [0x00007f3144fa3840] [MySink1]
[3] [warning] [const Parent N10TestTypeID8ClassDef6ParentE 13888289143273616310] [/data/root/cpp_study/src/cpp_language/typeid/main.cpp:40 void printTypeIdInfo()] [2021-04-08 19:46:46.200791] [0x00002841] [0x00007f3144fa3840] [MySink1]
[4] [warning] [const Child N10TestTypeID8ClassDef5ChildE 281579981607273881] [/data/root/cpp_study/src/cpp_language/typeid/main.cpp:41 void printTypeIdInfo()] [2021-04-08 19:46:46.200802] [0x00002841] [0x00007f3144fa3840] [MySink1]
[5] [warning] [const Parent & N10TestTypeID8ClassDef6ParentE 13888289143273616310] [/data/root/cpp_study/src/cpp_language/typeid/main.cpp:43 void printTypeIdInfo()] [2021-04-08 19:46:46.200807] [0x00002841] [0x00007f3144fa3840] [MySink1]
[6] [warning] [const Child & N10TestTypeID8ClassDef5ChildE 281579981607273881] [/data/root/cpp_study/src/cpp_language/typeid/main.cpp:44 void printTypeIdInfo()] [2021-04-08 19:46:46.200812] [0x00002841] [0x00007f3144fa3840] [MySink1]
[7] [warning] [const Parent && N10TestTypeID8ClassDef6ParentE 13888289143273616310] [/data/root/cpp_study/src/cpp_language/typeid/main.cpp:46 void printTypeIdInfo()] [2021-04-08 19:46:46.200820] [0x00002841] [0x00007f3144fa3840] [MySink1]
[8] [warning] [const Child && N10TestTypeID8ClassDef5ChildE 281579981607273881] [/data/root/cpp_study/src/cpp_language/typeid/main.cpp:47 void printTypeIdInfo()] [2021-04-08 19:46:46.200832] [0x00002841] [0x00007f3144fa3840] [MySink1]
[9] [warning] [Parent * PN10TestTypeID8ClassDef6ParentE 6222248450804906692] [/data/root/cpp_study/src/cpp_language/typeid/main.cpp:49 void printTypeIdInfo()] [2021-04-08 19:46:46.200843] [0x00002841] [0x00007f3144fa3840] [MySink1]
[10] [warning] [Child * PN10TestTypeID8ClassDef5ChildE 15623637030535351372] [/data/root/cpp_study/src/cpp_language/typeid/main.cpp:50 void printTypeIdInfo()] [2021-04-08 19:46:46.200852] [0x00002841] [0x00007f3144fa3840] [MySink1]
[11] [warning] [const Parent * PKN10TestTypeID8ClassDef6ParentE 402975985570091798] [/data/root/cpp_study/src/cpp_language/typeid/main.cpp:52 void printTypeIdInfo()] [2021-04-08 19:46:46.200861] [0x00002841] [0x00007f3144fa3840] [MySink1]
[12] [warning] [const Child * PKN10TestTypeID8ClassDef5ChildE 8521000854627404144] [/data/root/cpp_study/src/cpp_language/typeid/main.cpp:53 void printTypeIdInfo()] [2021-04-08 19:46:46.200868] [0x00002841] [0x00007f3144fa3840] [MySink1]
[13] [warning] [Parent const * PKN10TestTypeID8ClassDef6ParentE 402975985570091798] [/data/root/cpp_study/src/cpp_language/typeid/main.cpp:54 void printTypeIdInfo()] [2021-04-08 19:46:46.200873] [0x00002841] [0x00007f3144fa3840] [MySink1]
[14] [warning] [Child const * PKN10TestTypeID8ClassDef5ChildE 8521000854627404144] [/data/root/cpp_study/src/cpp_language/typeid/main.cpp:55 void printTypeIdInfo()] [2021-04-08 19:46:46.200904] [0x00002841] [0x00007f3144fa3840] [MySink1]
[15] [warning] [Parent *const PN10TestTypeID8ClassDef6ParentE 6222248450804906692] [/data/root/cpp_study/src/cpp_language/typeid/main.cpp:57 void printTypeIdInfo()] [2021-04-08 19:46:46.200914] [0x00002841] [0x00007f3144fa3840] [MySink1]
[16] [warning] [Child *const PN10TestTypeID8ClassDef5ChildE 15623637030535351372] [/data/root/cpp_study/src/cpp_language/typeid/main.cpp:58 void printTypeIdInfo()] [2021-04-08 19:46:46.200919] [0x00002841] [0x00007f3144fa3840] [MySink1]
[17] [warning] [const Parent *const PKN10TestTypeID8ClassDef6ParentE 402975985570091798] [/data/root/cpp_study/src/cpp_language/typeid/main.cpp:60 void printTypeIdInfo()] [2021-04-08 19:46:46.200925] [0x00002841] [0x00007f3144fa3840] [MySink1]
[18] [warning] [const Child *const PKN10TestTypeID8ClassDef5ChildE 8521000854627404144] [/data/root/cpp_study/src/cpp_language/typeid/main.cpp:61 void printTypeIdInfo()] [2021-04-08 19:46:46.200931] [0x00002841] [0x00007f3144fa3840] [MySink1]

Python scripting is not supported in this copy of GDB

sudo yum install libc6-dbg gdb valgrind

install stl pretty pointer

https://sourceware.org/gdb/wiki/STLSupport

`
Check-out the latest Python libstdc++ printers to a place on your machine. In a local directory, do:
svn co svn://gcc.gnu.org/svn/gcc/trunk/libstdc++-v3/python

Add the following to your ~/.gdbinit. The path needs to match where the python module above was checked-out. So if checked out to: /home/maude/gdb_printers/, the path would be as written in the example:
python
import sys
sys.path.insert(0, ‘/home/maude/gdb_printers/python’)
from libstdcxx.v6.printers import register_libstdcxx_printers
register_libstdcxx_printers (None)
end
`

使用awk重命名文件

  • sub : 在开头替换
  • gsub : 全量替换
  • ls | awk '{printf ("mv %s ", $0); sub(/python_3_9_docs_en_python3_doc_/,"python_3_9_docs_zh_"); print $0}'|bash

glibc 汇编中的cfi_xxx指令与一些宏定义的说明

[TOC]

  • 最近在看glibc中的一些源码, 涉及到一些系统底层操作时, 需要看汇编代码, 发现了一些cfi_xxx开头的调用,这里梳理下
  • 这里以32位下i386架构说明, sysdep文件在:glibc/sysdeps/i386/sysdep.h

ENTRY/END 宏

  • sysdep中的ENTRY/END 用来定义汇编函数的开始/结束
    • 比较反直觉的是, 这里定义的代码, 全部是汇编指令, 不会再编译后的代码中出现,
    • 并不是push %ebp; move %ebp %esp; 这种标准c调用开头语结尾

ENTRY

1
2
3
4
5
6
7
#define	ENTRY(name)							      \
.globl C_SYMBOL_NAME(name); \
.type C_SYMBOL_NAME(name),@function; \
.align ALIGNARG(4); \
C_LABEL(name) \
cfi_startproc; \
CALL_MCOUNT
  • 逐句说明:
    • .globl C_SYMBOL_NAME(name);.type C_SYMBOL_NAME(name),@function;
      • 这两句简单, 大家都看得懂, 声明一个全局符号, 定义一个函数名
    • .align ALIGNARG(4);
      • 地址对齐, 方便在一个指令周期内拿到数据
    • C_LABEL(name)

      1
      2
      /* Define a macro we can use to construct the asm name for a C symbol.  */
      # define C_LABEL(name) name##:
      • 函数名+”:”, 用于jmp
    • cfi_startproc
      • cfi_xx系列调用, 后面再说
    • CALL_MCOUNT

      1
      2
      3
      4
      5
      6
      7
      #define CALL_MCOUNT \
      pushl %ebp; cfi_adjust_cfa_offset (4); movl %esp, %ebp; \
      cfi_def_cfa_register (ebp); call JUMPTARGET(mcount); \
      popl %ebp; cfi_def_cfa (esp, 4);
      #else
      #define CALL_MCOUNT /* Do nothing. */
      #endif
      • 用于性能分析时, 计数这个函数的调用次数用的, 不开相关宏的话, 不会体现在代码里.

END

1
2
3
4
#undef	END
#define END(name) \
cfi_endproc; \
ASM_SIZE_DIRECTIVE(name)
  • ASM_SIZE_DIRECTIVE

    1
    #define ASM_SIZE_DIRECTIVE(name) .size name,.-name
    • 计算这一段的长度, 不会再编译后生成, 只是告诉编译器,这一段的长度

cfi_xxx系列调用

  • 现在只剩cfi_xxx 的调用了, 文档看得不明所以, 我太菜了T_T
  • 下面的是我结合DWARF4官方文档的理解

dwarf

  • DWARF: Debugging Information Format 调试信息格式
  • 一种用于定义兼容的调试信息的格式定义, 可以用于f Ada, C, C++, COBOL, and Fortran;
  • 但这种信息需要额外的编译时支持, 需要在编译时, 明确的告知编译器, 哪里是函数开始, 结束, 每次esp发生变化时, 都要进行对齐
  • 当然, 这些都是编译时指令, 在最终的二进制中, 不会再运行的代码中出现

eh_frame段

  • gcc在调用stackTrace, 处理异常信息 , 需要额外的数据来进行堆栈的展开, 这些信息就是保存在eh_frame段中的
  • 这些信息是在编译时, 通过cfi_xxx系列的编译器指令来实现保存的
  • 这些指令只在编译时生效, 用于生成eh_frame中的数据
  • 最终的.o中, 是没有这些编译指令的

cfi_xxx 指令

  • cfi Call Frame Information 调用帧信息
  • 分配在堆栈上的内存区域,称为“调用帧”, “调用帧由堆栈上的地址标识。
  • CFA Canonical Frame Address 规范框架地址
  • 通过cfi信息, 可以实现断点, debug栈展开, 异步事件的中断等功能
  • 现在,根据gnubinutile/as说明, 就可以很清晰的理解cfi的具体作用了
  • 开支定义一个函数的cfi信息, 结束函数的cfi信息, 计算偏移量, 定义CFA开始等等各种操作, 这里就不列举列, 了解它的作用后,文档里面描述就很清晰了

例子

  • 最后, 放一个 getcontext 函数的 glibc汇编源码与编译后的反汇编代码的区别, 就可以证明这些

cmake+idea+conan构建c++开发环境

  • conan是一个还算能用的c++包管理器, 用它可以简化很多依赖关系
    • conan 本身使用python写的, 所以…
    • 推荐python3 pip3 以上进行安装
    • pip3 install conan
  • 这里假设你已经安装好了conan
    • $conan -v
      Conan version 1.24.1
      
      1
      2
      ## 定义conan依赖文件```conanfile.txt```
      - 在项目根目录下定义一个名为```conanfile.txt```的文件,eg:

[requires]
zlib/1.2.11@conan/stable
boost/1.71.0@conan/stable
gtest/1.8.1@bincrafters/stable
protobuf/3.9.1@bincrafters/stable
opencv/4.1.1@conan/stable

[generators]
cmake

[options]

1
- 在项目cmake中使用conan: 在cmake合适的位置加载conan

加载conan

set(CONAN_LIBS “”)
execute_process(
COMMAND conan install ..
)
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup()
message(“conan get libs\t${CONAN_LIBS}\n”)

1
2
- cmake reload之后,所有你指定的依赖就会下载到本地了
- 需要的链接目标链接就行(单个或者全部都可以)

target_link_libraries(${build_temp} pthread ${CONAN_LIBS})

1
2
3
4
5
6
7
8
9
## 配合clion进行远程开发
## 我的编译机是一台linux云服务器, 平时使用win/mac开发
- 使用clion进行远程开发这里就不说了, 网上很多
- 说明下conan头文件的同步吧:
- 我使用的clion版本无法下载conan动态依赖的头文件到本地,虽然编译没问题, 但是代码提示就很难看
- 原因是头文件被下载下来时,包含了conan的路径,idea没有识别(Mac上没有识别,windows上识别了...)
- 暴力解决:重新下载一份头文件添加进去
- 找到conan编译的临时位置:eg;```~/.conan/data/boost/1.71.0/conan/stable```
- 这个目录下package目录就是最终生成的包,
ll package/9434a65ef948ba36ef81ae1ae01f4667d80ff307/
总用量 1152
-rw-r--r-- 1 root root    3077 1月  13 16:56 conaninfo.txt
-rw-r--r-- 1 root root 1166067 1月  13 16:56 conanmanifest.txt
drwxr-xr-x 3 root root    4096 1月  13 16:56 include
drwxr-xr-x 2 root root    4096 1月  13 16:56 lib
1
2
- 中间多了一层hash值, 不要在意, 我们最终需要```~/.conan/data```下所有的include文件
- 写个命令,在~/.conan/data下执行
mkdir temp_include & find . -name include -type d |grep package|xargs -I{} cp -vr {} temp_include/ ``` - 下载下来放在clion自己同步的远程头文件中, 解决了 ``` C:\Users\用户名\.CLion版本号\system\.remote\服务器IP和端口\hash 值\usr\include ```