烈性果汁 - C++
type: channel
link: https://www.nhyilin.cn/category/default/
description: 只是一个默认分类
items: 清明踏青路书
items: 根据QComboBox调整界面label并整理至json
items: 达梦数据库tips
items: 多线程简易笔记
items: 《C++ Concurrency In Action 2ed》（第3章 共享数据）
items: 《C++ Concurrency In Action 2ed》（第2章 线程管理）
items: 《Effective STL》 Chapter 1: Containers（7-12）
items: 《Effective STL》 Chapter 1: Containers（1-6）
items: git使用笔记
items: Fast-DDS在Linux平台编译

清明踏青路书
type: item
date: 2026-04-04T01:23:00+08:00
description: 3天2晚详细路书Day 1（4月4日 周五）：北京 → 忻州 → 太原（住太原）核心：忻州古城（速览）+ 晋祠 + 山西博物院（人文双核心）04:30 北京出发导航点：北京（起点）→ 忻州古城（南门）路线：G5京昆高速 → G1812沧榆高速 → 二广高速里程：约450km | 预计耗时：5.5h（含1次服务区休整15min）服务区：保定服务区、五台山服务区10:00 抵达忻州古城游玩时长：1h（速览，不深度）必逛：南北大街、秀容书院（外观）、古城墙午餐：古城内「忻州瓦酥/羊杂割/碗托」（简餐，30min）11:30 离开忻州11:30 忻州 → 太原导航点：忻州古城 → 晋祠（正门）路线：二广高速 → 太原绕城高速里程：约80km | 预计耗时：1h12:30 抵达晋祠12:30–15:00 晋祠（核心景点）门票：80元（建议提前1天线上预约）游玩时长：2.5h必看：圣母殿、鱼沼飞梁、难老泉、周柏唐槐15:00 离开晋祠 → 山西博物院15:30–17:30 山西博物院导航点：晋祠 → 山西博物院（主馆）门票：免费（需预约，带身份证）游玩时长：2h必看：晋侯鸟尊、侯马盟书、虞弘墓石椁17:30 结束游览18:00 入住太原酒店推荐区域：迎泽大街/柳巷（市中心，近美食）晚餐：柳巷「清和元刀削面/认一力蒸饺/过油肉」（1h）休息：缓解长途驾驶疲劳Day 2（4月5日 周六）：太原 → 洛阳（住洛阳）核心：直达洛阳，龙门石窟 + 白马寺（两大世界遗产）07:30 太原出发导航点：太原酒店 → 龙门石窟（西北服务区）路线：二广高速 → 菏宝高速里程：约460km | 预计耗时：5.5h（含2次服务区休整，各20min）服务区：长治服务区、济源服务区（加油/简餐）13:00 抵达洛阳，午餐午餐：龙门石窟附近「牛肉汤/烩面/锅贴」（40min）13:40 前往龙门石窟14:00–16:30 龙门石窟门票：90元（提前预约，含西山+东山+香山寺）游玩时长：2.5h（西山石窟精华，重点看卢舍那大佛）16:30 离开 → 白马寺17:00–18:30 白马寺导航点：龙门石窟 → 白马寺（正门）里程：约15km | 耗时：25min门票：35元游玩时长：1.5h（中国第一古刹，看齐云塔、国际佛殿苑）18:30 结束游览19:00 入住洛阳酒店推荐区域：老城区/十字街（近夜景与美食）晚餐：十字街「洛阳水席/牡丹饼/不翻汤」（1h）晚上：可选逛应天门/天堂明堂夜景（外观免费）Day 3（4月6日 周日）：洛阳 → 小浪底 → 石家庄 → 北京核心：小浪底水库（自然景观）→ 石家庄休整 → 返京07:00 洛阳出发 → 小浪底导航点：洛阳酒店 → 小浪底水利枢纽风景区（南门）路线：王城大道 → 白横线 → 小浪底专用线里程：约40km | 预计耗时：1h08:00 抵达小浪底08:00–09:30 小浪底水库门票：40元游玩时长：1.5h（大坝观景台、黄河湿地公园、泄洪道）09:30 离开小浪底 → 石家庄09:30 小浪底 → 石家庄（休整）导航点：小浪底 → 河北博物院（正门）路线：二广高速 → 菏宝高速 → G4京港澳高速里程：约525km | 预计耗时：6.5h（含2次服务区休整，各20min+午餐40min）16:00 抵达石家庄（河北博物院）16:00–16:40 石家庄休整（河北博物院）门票：免费（带身份证）游玩时长：40min（速览金缕玉衣、长信宫灯）16:40 离开 → 北京16:40 石家庄 → 北京导航点：河北博物院 → 北京（终点）路线：G4京港澳高速 → 北京六环里程：约290km | 预计耗时：3.5h（含1次服务区休整15min）20:10 抵达北京，行程结束预约：晋祠、龙门石窟、山西博物院、河北博物院 务必提前1天在官方公众号预约。路况：4.6下午京港澳高速（石家庄→北京）易堵，16:30前务必离开石家庄。美食清单：忻州（羊杂、瓦酥）、太原（刀削面、过油肉）、洛阳（牛肉汤、水席）。

根据QComboBox调整界面label并整理至json
type: item
date: 2025-11-22T12:29:00+08:00
description: 根据界面QComboBox切换调整需要获取信息类型，按确定后将界面内容整理为json的全流程。定义需要获取的信息类型，避免字符串匹配：enum class EnvInfo { none = 0, Platform = 1, Object = 2, Env = 3 };在EditPlatformAttributeWindow类中维护AttributeWindow的vector指针列表，动态维护AttributeWindow个数：std::vector<AttributeWindow*> m_attribute_windows_;初始化comboBox，并设置槽函数onComboBoxIndexChanged，注意用QVariant绑定，方便后期获取index转为EnvInfo：void AttributeWindow::InitCombox() { ui->comboBox_model_4->clear(); ui->comboBox_model_4->addItem("请选择属性", QVariant::fromValue(static_cast<int>(EnvInfo::none))); ui->comboBox_model_4->addItem("平台", QVariant::fromValue(static_cast<int>(EnvInfo::Platform))); ui->comboBox_model_4->addItem("目标", QVariant::fromValue(static_cast<int>(EnvInfo::Object))); ui->comboBox_model_4->addItem("环境", QVariant::fromValue(static_cast<int>(EnvInfo::Env))); connect(ui->comboBox_model_4, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &AttributeWindow::onComboBoxIndexChanged); }ComboBox切换后调整需要获取的信息：void AttributeWindow::onComboBoxIndexChanged(int index) { QVariant data_ = ui->comboBox_model_4->itemData(index); m_env_type = static_cast<EnvInfo>(data_.toInt()); if (m_env_type == EnvInfo::none) { } if (m_env_type == EnvInfo::Platform) { adjustLabels(true); ui->label_2->setText("id"); ui->label_4->setText("silent_control"); ui->label_5->setText("dof.\nheading"); ui->label_9->setText("coordinate.\nlatitude"); // ... } if (m_env_type == EnvInfo::Object) { adjustLabels(true); ui->label_2->setText("id"); ui->label_3->setText("device_type"); // ... } if (m_env_type == EnvInfo::Env) { adjustLabels(false); ui->label->setText("wind_speed"); ui->label_2->setText("ship_density"); // ... } }根据需要获取的信息，调整label显隐，因为代码过于冗余，单独拆分为函数：void AttributeWindow::adjustLabels(bool is_show) { if (!is_show) { ui->label_3->hide(); ui->textEdit_3->hide(); } else { ui->label_3->show(); ui->textEdit_3->show(); } }获取内容:UIContext AttributeWindow::GetContext() { int index_ = ui->comboBox_model_4->currentIndex(); QVariant data_ = ui->comboBox_model_4->itemData(index_); m_env_type =…

达梦数据库tips
type: item
date: 2025-09-12T17:18:00+08:00
description: 一点关于达梦VARCHAR(255)的理解。某涉及达梦数据库的项目中，从达梦数据库取出来数据后需要和本地的std::unordered_map比对，来和本地平台的enum class属性的类型进行匹配，但是std::string之间比对出现了问题，通过for (char c : str) std::cout << (int)c << " "; std::cout << std::endl;输出字符串的每个字符的 ASCII 值，发现ASCII 值后半部分不相同，大概率是达梦数据库针对VARCHAR(255)类型数据兼容性没有做到位，导致ODBC取出来字符串有隐藏的字符。解决方案如下。建立StringComparator类，处理std::string比对的情况。#include <string> #include <algorithm> #include <cctype> class StringComparator { public: enum CompareMode { CaseSensitive, // 区分大小写 IgnoreCase, // 忽略大小写 IgnoreAllSpaces, // 忽略所有空白 IgnoreCaseAndSpaces // 忽略大小写和所有空白 }; // 构造函数，设置比较模式 StringComparator(CompareMode mode = IgnoreCaseAndSpaces) : m_mode(mode) {} // 设置比较模式 void setMode(CompareMode mode) { m_mode = mode; } // 执行比较 bool compare(const std::string& a, const std::string& b) const { std::string sa = trim(a); std::string sb = trim(b); switch (m_mode) { case CaseSensitive: return sa == sb; case IgnoreCase: return toLower(sa) == toLower(sb); case IgnoreAllSpaces: return removeSpaces(sa) == removeSpaces(sb); case IgnoreCaseAndSpaces: return toLower(removeSpaces(sa)) == toLower(removeSpaces(sb)); default: return sa == sb; } } private: CompareMode m_mode; // 工具函数 static std::string trim(const std::string& s) { auto start = std::find_if_not(s.begin(), s.end(), ::isspace); auto end = std::find_if_not(s.rbegin(), s.rend(), ::isspace).base(); return (start < end ? std::string(start, end) : ""); } static std::string toLower(const std::string& s) { std::string result = s; std::transform(result.begin(), result.end(), result.begin(), ::tolower); return result; } static std::string removeSpaces(const std::string& s) { std::string result; std::copy_if(s.begin(), s.end(), std::back_inserter(result), [](char c){ return !std::isspace(c); }); return result; } }; 应用：StringComparator cmp(StringComparator::IgnoreCaseAndSpaces); std::string str1 = " Hello World "; std::string str2 = "hello world"; if (cmp.compare(str1, str2)) { std::cout << "good match" << std::endl; } else { std::cout << "bad match" << std:…

多线程简易笔记
type: item
date: 2025-02-11T13:25:00+08:00
description: 作为开发过程中手册浏览。1.1. 线程代码示例#include <thread> #include <iostream> // Linux -lpthread void ThreadMain() { std::cout << "begin sub thread main " << std::this_thread::get_id() << std::endl; for (int i = 0; i < 10; i++) { std::cout << "in thread " << i << std::endl; std::this_thread::sleep_for(std::chrono::seconds(1)); // 1000ms } std::cout << "end sub thread main " << std::this_thread::get_id() << std::endl; } int main(int argc, char* argv[]) { std::cout << "main thread ID " << std::this_thread::get_id() << std::endl; // 线程创建启动 std::thread th(ThreadMain); std::cout << "begin wait sub thread " << std::endl; // 阻塞等待子线程退出 th.join(); std::cout << "end wait sub thread " << std::endl; return 0; } 1.2. 对象生命周期和线程等待和分离#include <thread> #include <iostream> // Linux -lpthread bool is_exit = false; void ThreadMain() { std::cout << "begin sub thread main " << std::this_thread::get_id() << std::endl; for (int i = 0; i < 10; i++) { if (!is_exit) break; std::cout << "in thread " << i << std::endl; std::this_thread::sleep_for(std::chrono::seconds(1)); // 1000ms } std::cout << "end sub thread main " << std::this_thread::get_id() << std::endl; } int main(int argc, char *argv[]) { { std:: thread th(ThreadMain); //出错，thread对象被销毁 子线程还在运行 } { std::thread th(ThreadMain); th.detach(); // 子线程与主线程分离 守护线程 // 坑 ：主线程退出后 子线程不一定退出 // 可能造成的问题是主线程推出(主程序退出)，程序报错(主线程退出，全部资源释放) // 一般解决方案：通过 is_exit 来控制，起到通知作用 // 如果不做 detach，则必须维护 th 对象 } { std::thread th(ThreadMain); std::this_thread::sleep_for(std::chrono::seconds(1)); // 1000ms is_exit = true; // 通知子线程退出 std::cout << "主线程阻塞，等待子线程退出" << std::endl; th.join(); // 主线程阻塞，等待子线程退出 std::cout << "子线程已经退出！" << std::endl; } std::cin.get(); return 0; }课程中用的vs2019，可以看到thread类的源码，可以看到构造、析构的写法(若没有join则删除对象，释放空间)。如果调用detach，则分离，主线程中不用再关注该线程若join方式来精确控制子线程，则一般需要维系该线程对象，一般将th对象放在成员函数中当线程的执行结果不需要返回给主线程，或者可以通过其他方式通知主线程时，可以使用detach()。例如，一个后台日志记录线程，它只负责将日志信息写入文件，不需要和主线程交互，就可以使用detach()。当线程的执行时间不确定，或者可能比主线程更长时，可以使用detach()。例如，一个网络请求线程，它可能会因为网络延迟或者服务器故障而阻塞很久，如果使用join()，就会导致主线程也无法继续…

《C++ Concurrency In Action 2ed》（第3章 共享数据）
type: item
date: 2025-02-06T15:50:00+08:00
description: 《C++ Concurrency In Action 2ed》（第3章 共享数据）第2 章 线程管理3.1 使用互斥量当多个线程同时访问共享数据时，如果数据只读，则不会出现问题；但如果有线程需要修改共享数据，则可能引发复杂问题。1. 不变量的破坏不变量是数据结构在某一状态下的规则或约束。例如，双链表中每个节点的前后指针必须保持一致。在修改共享数据时，不变量可能会暂时被破坏，直到修改完成后才恢复稳定。如果在不变量被破坏的过程中，其他线程访问了共享数据，就可能导致错误。2. 条件竞争条件竞争是指多个线程对共享数据的访问顺序影响最终结果。良性条件竞争：线程执行顺序不同，但结果仍然可接受。恶性条件竞争：线程执行顺序导致不变量被破坏，可能导致数据损坏或程序崩溃。数据竞争是条件竞争的一种特殊形式，指多个线程同时修改一个独立对象，可能引发未定义行为。3. 恶性条件竞争的特性通常发生在对多个数据块的修改中，如双链表中两个指针的更新。错误难以复现，因为其发生概率较低，且与执行时间敏感。在系统负载高或执行次数增加时，问题出现的概率会提高。4. 避免恶性条件竞争的方法保护机制：使用某种机制（如互斥量）保护数据结构，确保其他线程无法看到不变量的中间状态。无锁编程：通过修改数据结构和不变量，使其变化过程不可分割，但这种方法复杂且容易出错。事务机制：通过事务日志记录和提交数据更新，避免条件竞争（如软件事务内存，STM）。但C++标准目前未直接支持这种方法。互斥量：C++标准库提供的最基本的方式，用于保护共享数据结构。5. 调试难点条件竞争问题可能在调试模式下消失，因为调试模式会影响程序的执行时间。通过这些方法和工具，可以有效避免恶性条件竞争，确保多线程程序的正确性和稳定性。[![top] Goto Top](#table-of-contents)3.2 使用互斥量3.2.1 互斥量核心概念互斥量（std::mutex）是C++中保护共享数据的最基础机制。它的作用是确保同一时刻只有一个线程可以访问共享数据，从而避免条件竞争和数据不一致的问题。使用方式手动锁与解锁 通过std::mutex的成员函数lock()和unlock()，可以手动对互斥量进行加锁和解锁操作，但这种方式容易出错，尤其是在代码中有多个出口（如异常处理）时，可能会导致忘记解锁。RAII机制：std::lock_guard 为了避免手动管理锁的复杂性，C++标准库提供了std::lock_guard，它是一种RAII（资源获取即初始化）机制的封装类。在构造时自动加锁，在析构时自动解锁，从而确保互斥量在任何情况下都能正确解锁。示例代码：使用std::mutex和std::lock_guard#include #include <mutex> #include <algorithm> std::list<int> some_list; // 1. 全局共享数据 std::mutex some_mutex; // 2. 保护共享数据的互斥量 // 添加元素到列表 void add_to_list(int new_value) { std::lock_guard<std::mutex> guard(some_mutex); // 3. 自动加锁 some_list.push_back(new_value); } // 检查列表中是否包含某个值 bool list_contains(int value_to_find) { std::lock_guard<std::mutex> guard(some_mutex); // 4. 自动加锁 return std::find(some_list.begin(), some_list.end(), value_to_find) != some_list.end(); }代码说明：some_list 是一个全局共享的std::list。some_mutex 是保护some_list的互斥量。在add_to_list()和list_contains()函数中，使用std::lock_guard对some_mutex进行加锁，确保线程安全。当线程访问共享数据时，互斥量会将其他线程阻塞，直到当前线程完成操作并解锁。C++17 的改进模板参数推导 在C++17中，std::lock_guard支持模板参数推导，因此可以省略类型声明，代码更加简洁。例如：std::lock_guard guard(some_mutex);std::scoped_lock C++17引入了std::scoped_lock，它是std::lock_guard的增强版，可以同时锁住多个互斥量，适用于需要同时操作多个共享数据的场景。例如：std::scoped_lock guard(some_m…

《C++ Concurrency In Action 2ed》（第2章 线程管理）
type: item
date: 2025-02-06T14:19:00+08:00
description: 《C++ Concurrency In Action 2ed》（第2章 线程管理）2.1 线程的基本操作线程的基本概念每个C++程序至少有一个主线程，负责执行main()函数。新线程可以通过std::thread对象启动，线程的生命周期由其关联的函数或任务决定。当线程的任务函数执行完毕，线程会自动退出。线程的启动使用std::thread对象启动线程：void do_some_work(); std::thread my_thread(do_some_work);线程启动后会执行do_some_work()。也可以使用可调用对象（如类的operator()）启动线程：class background_task { public: void operator()() const { do_something(); do_something_else(); } }; background_task f; std::thread my_thread(f);注意避免“最令人头痛的语法解析问题”（Most Vexing Parse）：std::thread my_thread(background_task()); // 被解析为函数声明解决方法：使用额外的括号：std::thread my_thread((background_task()));使用统一初始化语法：std::thread my_thread{background_task()};使用Lambda表达式：std::thread my_thread([] { do_something(); do_something_else(); });线程的等待与分离等待线程完成 (join)使用join()可以等待线程完成：std::thread t(do_some_work); t.join(); // 阻塞当前线程，直到`t`完成join()会清理线程相关的资源，调用后std::thread对象不再与线程关联。一个线程只能join一次。分离线程 (detach)使用detach()将线程与std::thread对象分离，线程会在后台运行：std::thread t(do_background_work); t.detach();分离的线程无法再通过std::thread对象控制，线程的资源会在其完成后自动回收。注意在std::thread对象销毁前，必须对其调用join()或detach()，否则会导致程序终止（调用std::terminate()）。线程生命周期管理避免局部变量的引用问题如果线程函数中使用了局部变量的引用，而线程未完成时局部变量已经销毁，会导致未定义行为：struct func { int& i; func(int& i_) : i(i_) {} void operator()() { for (unsigned j = 0; j < 1000000; ++j) { do_something(i); // 可能访问已销毁的局部变量 } } }; void oops() { int some_local_state = 0; func my_func(some_local_state); std::thread my_thread(my_func); my_thread.detach(); // 分离线程 } // some_local_state 被销毁，但线程仍在运行解决方法：将局部变量复制到线程中，而不是传引用。使用join()确保线程结束后再销毁局部变量。使用RAII管理线程RAII（资源获取即初始化）模式可以确保线程在异常或函数退出时正确管理：class thread_guard { std::thread& t; public: explicit thread_guard(std::thread& t_) : t(t_) {} ~thread_guard() { if (t.joinable()) { t.join(); } } thread_guard(const thread_guard&) = delete; thread_guard& operator=(const thread_guard&) = delete; }; void f() { int some_local_state = 0; func my_func(some_local_state); std::thread t(my_func); thread_guard g(t); // RAII管理线程 do_something_in_current_thread(); } // thread_guard析构时会调用t.join()异常安全在异常处理中也要确保线程正确join：voi…

《Effective STL》 Chapter 1: Containers（7-12）
type: item
date: 2025-01-22T22:40:00+08:00
description: 50 Specific Ways to Improve Your Use of the Standard Template Library.《Effective STL》 Chapter 1: Containers（1-6）Item 7: 在使用存储new出来的指针的容器时，记得在容器销毁之前delete这些指针When using containers of newed pointers, remember to delete the pointers before the container is destroyed.STL中的容器非常智能。它们可以提供用于正向和反向遍历的迭代器（通过begin、end、rbegin等函数）；它们可以告诉你容器中存储了什么类型的对象（通过它们的value_type typedef）；在插入和删除元素时，它们会自动处理必要的内存管理；它们可以报告容器中当前有多少元素以及最多可以容纳多少元素（分别通过size和max_size）；当然，当容器本身被销毁时，它们会自动销毁它们所包含的每个对象。考虑到这些智能容器，许多程序员就不再为清理资源而担忧了。他们会想：“嘿，我的容器会替我处理这些问题。” 在很多情况下，他们是对的，但当容器中存储的是通过new动态分配的指针时，他们的想法就不够正确了。是的，指针容器会在它们（容器）被销毁时销毁它们所包含的每个元素，但指针的“析构函数”什么都不会做！它当然不会调用delete。因此，以下代码会导致资源泄漏：void doSomething() { vector<Widget*> vwp; for (int i = 0; i < SOME_MAGIC_NUMBER; ++i) vwp.push_back(new Widget); ... // 使用vwp } // 在这里，Widget对象会泄漏！当vwp超出作用域时，它的每个元素都会被销毁，但这并不能改变这样一个事实：new出来的对象从未被delete。这种删除操作是你的责任，而不是容器的责任。这其实是特意设计的功能。只有你知道这些指针是否需要被删除。通常情况下，你希望它们被删除。当确实需要删除时，实现这一点看起来似乎很简单：void doSomething() { vector<Widget*> vwp; ... // 如前所述 for (vector<Widget*>::iterator i = vwp.begin(); i != vwp.end(); ++i) delete *i; }这段代码是可以工作的，但前提是你对“工作”并没有太高的要求。这里有两个问题。一个问题是，这个新的for循环实际上与for_each的功能几乎相同，但它并不像直接使用for_each那么清晰（参见第43条）。另一个问题是，这段代码并不是异常安全的。如果在填充vwp的指针到删除它们之间抛出了异常，资源仍然会泄漏。幸运的是，这两个问题都可以解决。要将你的类似for_each的循环变成真正的for_each调用，你需要将delete转化为一个函数对象。这是小菜一碟，假设你有一个喜欢玩STL的孩子：template<typename T> struct DeleteObject : public unary_function<const T*, void> { void operator()(const T* ptr) const { delete ptr; } };现在你可以这样写：void doSomething() { ... // 如前所述 for_each(vwp.begin(), vwp.end(), DeleteObject<Widget>()); }不幸的是，这样做需要你显式地指定DeleteObject将删除的对象的类型（在这里是Widget）。这很烦人。vwp已经是一个vector<Widget*>，所以显然DeleteObject会删除Widget*指针！这显得很多余。这种冗余不仅令人厌烦，还可能导致难以追踪的错误。例如，假设有人不明智地决定从string继承：class SpecialString : public string { ... };从一开始这就是危险的，因为string（和所有标准STL容器一样）没有虚析构函数，而从没有虚析构函数的类继承是C++中的一个重大禁忌。（详细信息可以参考任何一本好的C++书。在《Effective C++》中，可以参考第14条。）尽管如此，有些人仍然会这样做，所以让我们看看以下代码会如何表现：void doSomething() { deque<SpecialString*> dssp; ... for_each(dssp.begin(), dssp.end(), De…

《Effective STL》 Chapter 1: Containers（1-6）
type: item
date: 2025-01-08T22:12:00+08:00
description: 50 Specific Ways to Improve Your Use of the Standard Template Library.本文作为读书的记录，后续会陆续更新。Item 1: 谨慎选择你的容器Choose your containers with care您知道 C++ 提供了多种容器供您使用，但您是否意识到这些容器种类繁多？为了确保您没有忽略任何选项，下面简要回顾一下。标准 STL 序列容器、vector、string、deque 和 list。标准 STL 关联容器，set、multiset、map 和 multimap。非标准序列容器 slist 和 rope。slist 是单链表，而 rope 本质上是重载string。（“rope” 是重载“string”。明白了吗？）您可以在 Item 50 中找到这些非标准（但常用）容器的简要概述。非标准关联容器 hash_set、hash_multiset、hash_map 和 hash_multimap。我在 Item 25 中研究了这些广泛使用的基于哈希表的标准关联容器变体。vector<char> 替代了 string。第 13 条描述了这种替代在哪些条件下是合理的。vector 可以替代标准的关联容器。正如 Item 23 所明确指出的，有时 vector 在时间和空间上的表现都优于标准的关联容器。几个标准的非 STL 容器，包括arrays、bitset、valarray、stack、queue,和priority_queue。由于这些都是非 STL 容器，因此本书中我很少谈论它们，不过第 16 条提到了数组优于 STL 容器的情况，第 18 条解释了为什么位集可能比 vector<bool> 更好。还值得一提的是，arrays可以与 STL 算法一起使用，因为指针可以用作array迭代器。关于容器的选项非常丰富，选择时需要考虑的因素也非常多。遗憾的是，大多数关于 STL 的讨论都对容器世界持相当狭隘的看法，忽略了许多与选择最合适的容器相关的问题。甚至STL也参与其中，为在 vector、deque 和 list 之间进行选择提供了以下指导：vector、list 和 deque 为程序员提供了不同的复杂性权衡，应相应地使用它们。vector 是默认应使用的序列类型。当在序列中间频繁插入和删除时，应使用 list。当大多数插入和删除发生在序列的开头或结尾时，deque 是首选的数据结构。如果您主要关心的是算法复杂性，我想这是一个合理的建议，但还有很多值得关注的事情。首先我需要介绍一种对 STL 容器进行分类的方法，这种方法讨论得并不多。这就是连续内存容器和基于节点的容器之间的区别。连续内存容器（也称为基于数组的容器）将其元素存储在一个或多个（动态分配的）内存块中，每个内存块包含多个容器元素。如果插入新元素或删除现有元素，则同一内存块中的其他元素必须上移或下移，以便为新元素腾出空间或填充以前被删除元素占用的空间。这种移动会影响性能（参见第 5 和 14 条）和异常安全性（我们很快就会看到）。标准的连续内存容器是 vector、string 和 deque。非标准 rope也是连续内存容器。基于节点的容器每块（动态分配的）内存仅存储一个元素。插入或删除容器元素只会影响指向节点的指针，而不会影响节点本身的内容，因此插入或删除某些内容时无需移动元素值。表示链接列表的容器（如 list和slist）是基于节点的，所有标准关联容器也是如此。（它们通常实现为平衡树。）非标准哈希容器使用不同的基于节点的实现，如您将在条目 25 中看到的那样。您是否需要能够在容器中的任意位置插入新元素？如果是，则需要一个序列容器；关联容器不行。您是否关心元素在容器中的排序方式？如果不关心，散列容器将是一个可行的选择。否则，您将希望避免使用散列容器。容器必须是标准 C++ 的一部分吗？如果是，那就排除了散列容器、slist 和rope。你需要哪一类迭代器？如果它们必须是随机访问迭代器，那么从技术上讲，你只能使用 vector、deque 和 string，但你也许还想考虑 rope。有关rope的信息，请参阅。）如果需要双向迭代器，你必须避免使用 slist（参见 Item 50）以及散列容器的一种常见实现（参见 Item 25）。在进行插入或删除时，避免移动现有的容器元素是否重要？如果重要，你需要远离连续内存容器（参见条款 5）。在插入或删除时避免移动现有容器元素是否重要？如果重要，则需要远离连续内存容器（参见条目 5）。容器中的数据是否需要与 C 兼容布局？如果重要，则只能使用vector（参见条目 16）。查找速度是否是关键考虑因素？如果重要，则需要查看散列容器（参见条目 25）、…

git使用笔记
type: item
date: 2024-12-11T16:22:00+08:00
description: git使用规范在软件开发过程中，规范的分支管理能够有效提高团队协作和项目稳定性。下面是一种常见的分支管理策略——Git Flow，适用于大多数项目的版本管理。具体步骤如下：Git Flow 分支管理模型1. 主分支（Main or Master）主要分支用于保存发布的正式版本。任何提交到主分支的代码都应该是稳定且准备发布的代码。2. 开发分支（Develop）开发分支是所有开发动作的基础分支。这个分支包含最新的开发代码，能够稳定运行但可能未经过全面测试。3. 功能分支（Feature Branches）这些分支用于开发新功能，每个新功能创建一个独立的功能分支。这些分支从开发分支中派生，完成后合并回开发分支。命名规范： feature/功能名称创建：从开发分支创建 git checkout -b feature/功能名称 develop合并回开发分支：功能完成后合并回开发分支 git checkout develop 然后 git merge --no-ff feature/功能名称删除：合并完成后删除 git branch -d feature/功能名称4. 发布分支（Release Branches）当准备发布一个新的版本时，从开发分支创建一个发布分支。这些分支用于修正Bug和准备发布，包括更新版本号、文档等。完成后合并到主分支和开发分支。命名规范： release/版本号创建：从开发分支创建 git checkout -b release/版本号 develop合并到主分支：发布完成并合并到主分支 git checkout main 然后 git merge --no-ff release/版本号合并回开发分支：然后将这些改动合并回开发分支 git checkout develop 然后 git merge --no-ff release/版本号删除：合并完成后删除 git branch -d release/版本号5. 热修复分支（Hotfix Branches）这些分支用于修复生产环境中的紧急问题。它们直接从主分支创建，修复完成后合并回主分支和开发分支。命名规范： hotfix/修复描述创建：从主分支创建 git checkout -b hotfix/修复描述 main合并到主分支：修复完成并合并到主分支 git checkout main 然后 git merge --no-ff hotfix/修复描述合并回开发分支：然后将这些改动合并回开发分支 git checkout develop 然后 git merge --no-ff hotfix/修复描述删除：合并完成后删除 git branch -d hotfix/修复描述示例工作流程创建开发分支：git checkout -b develop main创建功能分支进行开发：git checkout -b feature/add-new-feature develop [进行开发...] git checkout develop git merge --no-ff feature/add-new-feature git branch -d feature/add-new-feature准备发布：git checkout -b release/v1.0 develop [修正Bug，准备发布...] git checkout main git merge --no-ff release/v1.0 git checkout develop git merge --no-ff release/v1.0 git branch -d release/v1.0紧急修复：git checkout -b hotfix/fix-critical-bug main [进行修复...] git checkout main git merge --no-ff hotfix/fix-critical-bug git checkout develop git merge --no-ff hotfix/fix-critical-bug git branch -d hotfix/fix-critical-bug通过使用Git Flow模型，你可以清晰地管理不同类型的工作并确保代码质量，减少合并冲突，同时保证主分支的稳定。功能分支（Feature Branch）命名一般遵循一定的规范，从名称中可以清楚地知道该分支的目的或正在进行的功能。常见的命名惯例是在分支名称前加上「feature/」，后接简要描述功能的关键词或JIRA号、任务编号等。下面是一些具体示例：基于功能描述：feature/user-loginfeature/payment-integrationfeature/update-profil…

Fast-DDS在Linux平台编译
type: item
date: 2024-11-04T17:07:00+08:00
description: 本文记录Fast DDS在国产芯片及国产系统下的编译，具体情况每个环境有个别差异，酌情参考，另外本文直接在目标机器编译，不涉及交叉编译。Fast DDS 是 eProsima 开发的一个高性能、实时的通信中间件，基于 Data Distribution Service (DDS) 标准。它是一种发布/订阅的架构，广泛用于需要高效率、低延迟通讯的系统中，如机器人、自动驾驶、工业自动化以及物联网。环境如下： 芯片： Phytium,D2000/8 E8C 架构： aarch64 CPU 运行模式： 32-bit, 64-bit OS版本： Linux kylin-pc 5.4.18-110-generic涉及到的第三方库版本如下：asio-1.30.2、Fast-CDR-2.2.5、tinyxml2-v10.0.0、foonathan_memory_vendor-v1.3.1 以及Fast-DDS-2.10.5第三方库准备Fast_CDRFast_CDR是做idl序列化和反序列化的核心库，他不依赖任何库。github下载源码，建议下载Fast-CDR-2.2.5的tag分支。#源码路径 mkdir build cmake .. -DCMAKE_INSTALL_PREFIX=/data/DDSDEV/thirdparty/Fast_CDR make -j8 make installTinyXML2github下载源码，建议下载 tinyxml2-v10.0.0 的tag分支。#源码路径 mkdir build cmake .. -DCMAKE_CXX_FLAGS="-fPIC" -DCMAKE_C_FLAGS="-fPIC" -DCMAKE_INSTALL_PREFIX=/data/DDSDEV/thirdparty/Fast_CDR make -j8 make install这里需要注意的是，需要加上-DCMAKE_CXX_FLAGS="-fPIC" -DCMAKE_C_FLAGS="-fPIC"，主要目的是为了编译生成使用 -fPIC 编译的静态库（libtinyxml2.a），如果未使用该标志位，则链接到生成共享对象（.so）的过程会产生错误。在 Linux 系统上，用于创建共享库的所有对象文件通常需要使用 -fPIC（Position Independent Code）编译。如果不用，则编译Fast-DDS时会报错：/usr/bin/ld: /data/DDSDEV/thirdparty/tinyxml2/lib/libtinyxml2.a(tinyxml2.cpp.o): relocation R_AARCH64_ADR_PREL_PG_HI21 against symbol `__stack_chk_guard@@GLIBC_2.17' which may bind externally can not be used when making a shared object; recompile with -fPIC /usr/bin/ld: /data/DDSDEV/thirdparty/tinyxml2/lib/libtinyxml2.a(tinyxml2.cpp.o)(.text+0x4a8): unresolvable R_AARCH64_ADR_PREL_PG_HI21 relocation against symbol `__stack_chk_guard@@GLIBC_2.17' /usr/bin/ld: 最后的链结失败: bad value collect2: error: ld returned 1 exit status make[2]: *** [src/cpp/CMakeFiles/fastdds.dir/build.make:3656：src/cpp/libfastdds.so.3.1.0] 错误 1 make[1]: *** [CMakeFiles/Makefile2:1158：src/cpp/CMakeFiles/fastdds.dir/all] 错误 2当然，也可以使用动态库：-DTINYXML2_LIBRARY=/data/DDSDEV/thirdparty/tinyxml2/lib/libtinyxml2.soAsioAsio是一个跨平台的C++库，用于网络和底层I/O编程。Asio库不用编译，直接下载源码，建议下载 asio-1.30.2 的tag分支。代码下载后解压到/data/DDSDEV/thirdparty下备用。Foonathan_memorygithub下载源码，建议下载 foonathan_memory_vendor-v1.3.1 tag分支。#源码路径 mkdir b…