constexpr

修饰变量的时候,可以把 constexpr 对象当作加强版的 const 对象。const 对象值不会改变,但不一定能够在编译期取得结果;constexpr 对象不仅值不会改变,而且保证能够在编译期取得结果。如果一个 const 变量能够在编译期求值,将其改为 constexpr 能够让代码更清晰易读。

constexpr 函数可以把运行期计算迁移至编译期,使得程序运行更快(但会增加编译时间)。但如果 constexpr 函数中存在无法在编译期求值的参数,则 constexpr 函数和普通一样在运行时求值,此时的返回值不是常量表达式。

尽管编译期运算会延长我们的编译时间,但是我们有些时候会用它来加快程序的运行速度。但是在使用时我们仍应该抱着谨慎的态度。太多的constexpr让代码中充斥着不必要的关键字,影响可读性,而且要权衡编译期带来的好处和坏处。

目前我在函数中几乎没用过constexpr,只对头文件中常用的枚举值和变量加了constexpr,暂时不做太多分析了。

参考:


判断文件是否存在
1
2
3
4
5
6
7
8
#include <fstream>

std::ifstream fin(file_name);
if (!fin) {
printf("file not exist");
return false;
}
fin.close();

C++17中有std::filesystem::path,相当于Boost的filesystem模块,只需要set(CMAKE_CXX_STANDARD 17)

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
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;


int main()
{
std::filesystem::path test_path("/home/user/test.yaml");
//判断路径是否存在
std::cout << "is_exist = " << std::filesystem::exists(test_path) << std::endl;

// 判断路径为绝对路径还是相对路径
std::cout << "is_abs = " << test_path.is_absolute() << std::endl;
std::cout << "is_relative = " << test_path.is_relative() << std::endl;
// 取得不带扩展名的文件名
std::cout << "stem = " << test_path.stem() << std::endl;
// 取得扩展名,如果没有,则为空
std::cout << "extension = " << test_path.extension() << std::endl;
// 取得文件名
std::cout << "filename = " << test_path.filename() << std::endl;

std::cout << "----------------------------------------" << std::endl;

std::filesystem::path test_dir("/home/user/Pictures");
//判断是文件还是文件夹
std::cout << "is_file = " << std::filesystem::is_regular_file(test_path) << std::endl;
std::cout << "is_dir = " << std::filesystem::is_directory(test_dir) << std::endl;

//关于路径拼接
std::filesystem::path new_path = test_dir / "test.txt";
std::cout << "new_path = " << new_path << std::endl;

//取得当前绝对工作路径 编译后可执行文件的路径文件夹
std::filesystem::path workPath = std::filesystem::current_path();
std::cout << "current exe path = " << workPath << std::endl;

//递归遍历指定路径下所有文件,文件夹名字也会返回
std::filesystem::recursive_directory_iterator iterDir(test_dir);
for (auto &it: iterDir)
{
//打印绝对路径
std::cout << it << std::endl;
// 不打印路径而是打印文件名
std::cout << it.path().filename() << std::endl;
}

//给一个相对路径,返回绝对路径
std::filesystem::path path_test("./");
std::cout << "abs_path = " << std::filesystem::absolute(path_test) << std::endl;


// 删除文件, 路径不存在不报错
// std::filesystem::remove(test_dir / "deepfakes"/"yolov7.pdf");

// 拷贝文件夹, 删除文件夹
// std::filesystem::copy(test_dir, test_dir.parent_path() / "works", std::filesystem::copy_options::recursive);
// std::filesystem::remove_all(test_dir.parent_path() / "works");
return 0;
}


exception的使用

一次使用时,出现报错:

1
2
3
4
5
6
warning: exception of type ‘std::bad_alloc’ will be caught
catch (bad_alloc e){ cout << e.what() << endl;}
^~~~~

warning: by earlier handler for ‘std::exception’
catch (exception e){ cout << e.what() << endl;}

catch语句块不能随意乱安排。派生类型的异常放在前面,基类异常应该放在最后

1
2
3
4
5
6
7
8
9
10
11
12
13
int main()
{
try {
throw DerivedException();
}
catch (DerivedException& e) { // Handle derived class first
std::cerr << "Caught DerivedException: " << e.what() << std::endl;
}
catch (BaseException& e) { // Then handle base class
std::cerr << "Caught BaseException: " << e.what() << std::endl;
}
return 0;
}

catch(...) 是一个通配符,能够捕获任何未被其他 catch 块处理的异常。避免滥用 catch(...),尽量捕获特定类型的异常,避免掩盖问题。


yaml-cpp的使用

使用yaml-cpp读文件

1
2
3
4
5
6
7
8
9
10
YAML::Node gps_root;
try {
gps_root = YAML::LoadFile("/home/user/111.yaml");
} catch (YAML::ParserException &ex) {
std::cerr << "gps.yaml parse failed: " << ex.what() << std::endl;
exit(-1);
} catch (YAML::BadFile &ex) {
std::cerr << "gps.yaml load failed: " << ex.what() << std::endl;
exit(-1);
}

所用函数的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Node LoadFile(const std::string& filename) {
std::ifstream fin(filename.c_str());
if (!fin) {
throw BadFile();
}
return Load(fin);
}

Node Load(std::istream& input)
{
Parser parser(input);
NodeBuilder builder;
if (!parser.HandleNextDocument(builder)) {
return Node();
}

return builder.Root();
}

如果读文件失败,会抛出异常,不会运行到Load,所以无法用IsDefined函数判断是否读文件成功。


无法到达目标时,搜索新的目标点
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
double planner::distance(double x1,double y1,double x2,double y2)
{
return sqrt( (x1-x2) * (x1-x2) + (y1-y2) * (y1-y2) );
}

bool planner::isAroundFree(unsigned int mx, unsigned int my)
{
if(mx <= 1 || my <= 1 || mx >= this->costmap_->getSizeInCellsX()-1 || my >= this->costmap_->getSizeInCellsY()-1)
return false;
int x,y;
for(int i=-1;i<=1;i++)
{
for(int j=-1;j<=1;j++)
{
x = static_cast<int>(mx) + i;
y = static_cast<int>(my) + j;
if(this->costmap_->getCost(static_cast<unsigned int>(x),static_cast<unsigned int>(y)) != costmap_2d::FREE_SPACE)
return false;
}
}
return true;
}

// tolerance:搜索半径,单位米。
// in:输入的点
// out:搜索到的可行点
void planner::getNearFreePoint(const geometry_msgs::PoseStamped in,
geometry_msgs::PoseStamped& out,
double tolerance)
{
out = in;
unsigned int grid_size = static_cast<unsigned int>(tolerance/costmap_->getResolution() + 0.5);
if(grid_size<1)
{
out = in;
return;
}

unsigned int mx0,my0;
if(costmap_->worldToMap(in.pose.position.x,in.pose.position.y,mx0,my0))
{
if(this->isAroundFree(mx0,my0))
return;
unsigned int minx,maxx,miny,maxy;
double wx = 0.0,wy = 0.0;
double min_move_cost = 10000000.0;
minx = mx0-grid_size>0?mx0-grid_size:0;
maxx = mx0+grid_size<costmap_->getSizeInCellsX()?mx0+grid_size:costmap_->getSizeInCellsX();
miny = my0-grid_size>0?my0-grid_size:0;
maxy = my0+grid_size<costmap_->getSizeInCellsY()?my0+grid_size:costmap_->getSizeInCellsY();
for(unsigned int i=minx;i<=maxx;i++)
{
for(unsigned int j=miny;j<=maxy;j++)
{
costmap_->mapToWorld(i,j,wx,wy);
double current_move_cost = this->distance(in.pose.position.x,in.pose.position.y,wx,wy);
if(!this->isAroundFree(i,j) || current_move_cost > tolerance)
continue;
if(min_move_cost > current_move_cost)
{
min_move_cost = current_move_cost;
out.pose.position.x = wx;
out.pose.position.y = wy;
}
}
}
}
}

ZeroMQ的学习

使用zmqpp-4.2.0,但是用函数zmq_version发现版本是 4.3.4

ZeroMQ在CMake的设置

1
2
3
4
5
6
7
8
9
10
11
12
cmake_minimum_required(VERSION 3.5)

project(untitled LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

include_directories(zmqpp-4.2.0/src/zmqpp)
LINK_DIRECTORIES(zmqpp-4.2.0/lib/)

add_executable(untitled main.cpp)
target_link_libraries(untitled zmq)

查看版本

1
2
3
4
5
6
7
8
9
#include <stdio.h>
#include <zmq.h>
// 显示当前的zeromq版本
int main()
{
int major, minor, patch;
zmq_version(&major, &minor, &patch);
printf("Current ZeroMQ version is %d.%d.%d\n", major, minor, patch);
}

发布和订阅

pub端:

  • 创建context
  • 创建socket,设置ZMQ_PUB模式
  • bind端口
  • 循环发布消息send

sub端:

  • 创建context
  • 创建socket,设置ZMQ_SUB模式
  • connect到pub端
  • setsockopt设置ZMQ_SUBSCRIBE订阅的消息
  • 循环接收recv

注意事项:

  1. 我们在pub中bind,在sub中connect,在zmq的使用中无论是在pub还是sub中都可以bind,但是一般我们在pub中bind,在sub中connect。反之sub端可能收不到消息
  2. zmq_setsockopt – 设置zmq的socket属性,sub端必须使用此方法,否则是收不到消息的。
  3. pub端不能使用recv函数,sub端不能使用send函数
  4. pub端socket的zmq_send()函数永远不会阻塞

zmq_msg_init_size

使用一个指定的空间大小初始化ZMQ消息对象。原型int zmq_msg_init_size (zmq_msg_t *msg, size_t size);

分配任何被请求的资源来存储一个size大小字节的消息,并且初始化msg参数指定的消息,用来表示新分配到的消息。

在函数执行的时候,会选择是否把消息存储在栈里面(小消息),还是堆里面(大消息)。考虑到性能原因,函数不会清除消息数据。

永远不要直接对zmq_msg_t对象进行直接操作,而是要使用zmq_msg函数族进行操作。

zmq_msg_init(), zmq_msg_init_data()zmq_msg_init_size()这三个函数是互斥的。永远不要把一个zmq_msg_t对象初始化两次。

执行成功时返回0。否则返回 -1,并且设置errno的值为下列定义的值。

zmq_msg_data

原型void *zmq_msg_data (zmq_msg_t *msg); 返回msg参数指定的消息内容的指针。

函数执行成功返回0,否则返回 -1

参考:
zmq pub/sub使用详解


proto基本使用

安装

安装Protobuf需要两个zip文件,以 3.0.0 为例,到github下载页面下载 protoc-3.0.0-linux-x86_64.zipprotobuf-cpp-3.0.0.zip

安装前者:

1
2
3
4
5
6
7
8
9
10
# Unzip
unzip protoc-3.2.0-linux-x86_64.zip -d protoc3

sudo mv protoc3/bin/* /usr/local/bin/

sudo mv protoc3/include/* /usr/local/include/

# change owner,也可不执行
sudo chwon [user] /usr/local/bin/protoc
sudo chwon -R [user] /usr/local/include/google

再安装后者

1
2
3
4
5
6
7
8
9
10
11
12
13
# 解压后执行
./autogen.sh

./configure # 或者 ./configure --prefix=/usr

make
sudo make install
sudo ldconfig

protoc --version
# 输出:libprotoc 3.0.0

locate libprotobuf.so


所用protobuf的版本是 3.12.4

Test.proto文件:

1
2
3
4
5
6
7
8
9
syntax = "proto3";
// 包名:在生成对应的C++文件时,将被替换为名称空间,在代码中会有体现
package Test;

message Pose {
float x = 1;
float y = 2;
float yaw = 3;
}

根据proto文件生成.h 和 .cc文件

1
./protoc --proto_path=/home/user/qt_projects --cpp_out=/home/user/qt_projects/proto_gen /home/user/qt_projects/Test.proto

简略形式: protoc message.proto --cpp_out=.


CMake中的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cmake_minimum_required(VERSION 3.5)

project(test_proto LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

include_directories(/home/user/zmqpp-4.2.0/src/zmqpp)
include_directories(/home/user/protobuf/src)

LINK_DIRECTORIES(/home/user/x86/zmqpp-4.2.0/lib/)
LINK_DIRECTORIES(/home/user/x86/protobuf/lib)
# 上面生成的cc文件
add_executable(test_proto main.cpp Test.pb.cc)
target_link_libraries(test_proto zmq protobuf)

注意 #include "Test.pb.h"

可以这样使用

1
2
3
4
Test::Pose*  pose_msg = new Test::Pose();
pose_msg->set_x(1.5);
pose_msg->set_y(2.7);
pose_msg->set_yaw(0);


ccache加速编译

ccache

先使用apt-get install ccache进行安装。

1
2
3
4
sudo vim /etc/environment
PATH='/usr/lib/ccache:keep the rest'

source /etc/environment

输入which gcc,应该得到/usr/lib/ccache/gcc

修改CMakeLists.txt

1
2
set(CMAKE_CXX_COMPILER_LAUNCHER ccache)
set(CMAKE_C_COMPILER_LAUNCHER ccache)

但是在ROS环境下,没发现起作用

distcc

Setting up distcc (server)

So, go ahead and install distcc on the server computer (in my case, the desktop)

And then spin up the server. (The following assumes I’m on the 10.8.33.0 subnet, and I’m allowing all hosts on that subnet to send jobs)

1
sudo distccd --log-file=/tmp/distccd.log --daemon -a 10.8.33.0/24

Setting up distcc (client) So, now you have to tell ccache to use the distcc distributed compilation servers. To do this, just add the following line to your ~/.bashrc file.
1
export CCACHE_PREFIX=distcc

Next, we have to tell distcc where to go to find the server. This also, just add to the bottom of your ~/.bashrc (my desktop is at 10.8.33.182 and it has 8 cores, my laptop is at localhost and has 4)
1
export DISTCC_HOSTS='localhost/4 10.8.33.182/8'


在另一个终端,使用下面命令检验ccache的运行效果

1
watch -n1 'ccache -s -v'

或者watch -n1 'ccache --show-stats -v'

编译时,使用top看 distcc process starts spooling up its threads.


Linux程序运行时被killed

linux程序运行时突然出现Killed,可以使用dmesg | egrep -i -B100 'killed process 查看原因,输出最近killed的信息。

参考: linux 程序被Killed,查看原因


有用的程序 1
  • 不断判断随机数的大小,如果它能大于某个值维持一段时间,输出时间,否则重新计时。
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
int main()
{
int num = 0;
int count = 0;
std::chrono::steady_clock::time_point start, end;
double duration;

while(1)
{
std::srand(time(0));
count = rand() % 1000000;
if(count > 680000)
{
if(num==0)
start = std::chrono::steady_clock::now();

num++;
end = std::chrono::steady_clock::now();
duration = std::chrono::duration<double>(end - start).count();
cout << "count: " << count << endl;
cout << "time elapsed " << static_cast<int>(duration) << endl;
}
else
{
duration = 0;
num = 0;
cout << endl;
}
}
return 0;
}
  • 判断一个点是否在多边形中

opencv函数: double pointPolygonTest(InputArray contour, Point2f pt, bool measureDist)

measureDist设置为true时,返回实际距离值。若返回值为正,表示点在多边形内部,返回值为负,表示在多边形外部,返回值为0,表示在多边形上。
当measureDist设置为false时,返回 -1、0、1三个固定值。若返回值为+1,表示点在多边形内部,返回值为-1,表示在多边形外部,返回值为0,表示在多边形上。

  • 计算点到直线的距离
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
* @tparam T point type
* @param first Given the starting point of a line segment
* @param last The endpoint of a given line segment
* @param third Given point
* @return T Distance from point to line
*/
template <typename T>
static T PointToLineDistance(const Point<T>& first, const Point<T>& last, const Point<T>& third) {
float dis_suqare =
((first.y - last.y) * third.x + (last.x - first.x) * third.y +
(first.x * last.y - last.x * first.y)) *
((first.y - last.y) * third.x + (last.x - first.x) * third.y +
(first.x * last.y - last.x * first.y)) /
((last.x - first.x) * (last.x - first.x) + (last.y - first.y) * (last.y - first.y));
return std::sqrt(dis_suqare);
}
  • 求图形的外接圆

OpenCV绘制最小外接矩形、最小外接圆
pointPolygonTest函数