C++:完美转发(一)(std::forward)
一、理解引用折叠
(一)引用折叠
1. 在C++中,“引用的引用”是非法的。像 auto& &rx = x;
(注意两个&之间有空格)这种直接定义引用的引用是不合法的,但是编译器在通过类型别名或模板参数推导等语境中,会间接定义出“引用的引用”,这时引用会形成“折叠”。
2. 引用折叠会发生在模板实例化、auto类型推导、创建和运用typedef和别名声明、以及decltype语境中。
(二)引用折叠规则
1. 两条规则
(1)所有右值引用折叠到右值引用上仍然是一个右值引用。如X&& &&折叠为X&&。
(2)所有的其他引用类型之间的折叠都将变成左值引用。如X& &, X& &&, X&& &折叠为X&。可见左值引用会传染,沾上一个左值引用就变左值引用了。根本原因:在一处声明为左值,就说明该对象为持久对象,编译器就必须保证此对象可靠(左值)。
2. 利用引用折叠进行万能引用初始化类型推导
(1)当万能引用(T&& param)绑定到左值时,由于万能引用也是一个引用,而左值只能绑定到左值引用。因此,T会被推导为T&类型。从而param的类型为T& &&,引用折叠后的类型为T&。
(2)当万能引用(T&& param)绑定到右值时,同理,右值只能绑定到右值引用上,故T会被推导为T类型。从而param的类型就是T&&(右值引用)。
以下是一个例子:
#include <iostream>using namespace std;
class Widget {};
template <typename T> void func(T &¶m) {}// Widget工厂函数
Widget widgetFactory() { return Widget(); }//类型别名
template <typename T> class Foo {public:typedef T &&RvalueRefToT;
};int main() {int x = 0;int &rx = x;// 1. 引用折叠发生的语境1——模板实例化Widget w1;func(w1); // w1为左值,T被推导为Widget&。代入得void func(Widget& && param);//引用折叠后得void func(Widget& param)func(widgetFactory()); //传入右值,T被推导为Widget,代入得void func(Widget&&// param) 注意这里没有发生引用的折叠。// 2. 引用折叠发生的语境2——auto类型推导auto &&w2 = w1; // w1为左值auto被推导为Widget&,代入得Widget& &&// w2,折叠后为Widget& w2auto &&w3 = widgetFactory(); //函数返回Widget,为右值,auto被推导为Widget,代入得Widget//&&w3// 3. 引用折叠发生的语境3——tyedef和usingFoo<int &> f1; // T被推导为 int&,代入得typedef int& &&// RvalueRefToT;折叠后为typedef int& RvalueRefToT// 4. 引用折叠发生的语境3——decltypedecltype(x) &&var1 = 10; //由于x为int类型,代入得int&& rx。decltype(rx) &&var2 =x; //由于rx为int&类型,代入得int& && var2,折叠后得int& var2return 0;
}
二、完美转发
(一)std::forward 原型
//左值版本
template<typename T>
T&& forward(typename remove_reference<T>::type& param)
{return static_cast<T&&>(param); //可能会发生引用折叠!
}//右值版本
template<typename T>
T&& forward(typename remove_reference<T>::type&& param)
{return static_cast<T&&>(param);
}
以上是 C++ 中 std::forward
函数模板的实现,这是完美转发的关键机制。完美转发是指在模板函数中将参数维持原样(保持其值类别——左值或右值)传递给另一个函数的技术。std::forward
通常在实现需要将参数转发到其他函数的模板中使用,尤其是在构造函数、函数模板和其他接受任意参数的场景中。
这段代码定义了两个重载版本的 forward
函数模板,一个用于左值,另一个用于右值。
左值版本
template<typename T>
T&& forward(typename remove_reference<T>::type& param)
{return static_cast<T&&>(param); //可能会发生引用折叠!
}
这里,T
是模板参数,而 typename remove_reference<T>::type&
表示去除 T
的引用部分后再加上左值引用。这确保了 param
是一个左值引用。
- 作用:这个版本的
forward
用于将一个左值以保持其原始类型(左值或右值)的方式传递。当你传递一个左值给forward
时,param
会匹配到这个重载版本。 - 引用折叠:在
return static_cast<T&&>(param);
中使用T&&
可以通过引用折叠规则处理左值和右值。如果T
是左值引用类型,比如int&
,那么T&&
折叠为int&
;如果T
是非引用类型或右值引用类型,比如int
或int&&
,那么T&&
折叠为int&&
。
右值版本
template<typename T>
T&& forward(typename remove_reference<T>::type&& param)
{return static_cast<T&&>(param);
}
这里,typename remove_reference<T>::type&&
也去除了 T
的引用部分,但这次它是被右值引用修饰。由于这是一个右值引用到右值引用的匹配,所以它只会被右值触发。
- 作用:这个版本的
forward
用于将一个右值以保持其类型(即右值)的方式传递。这是真正实现完美转发的关键部分,因为它确保了只有真正的右值才会被识别和处理为右值。 - 使用场景:当
forward
被用于一个右值时,它会触发这个重载,并且param
将传递为一个右值。
这两个版本的 forward
函数共同支持在模板中进行完美转发,**即不改变传入参数的值类别(左值或右值)。**这对于编写通用代码库、实现委托构造函数或任何需要保持参数值类别不变的场合至关重要。
(二)完美转发的必要性
完美转发的主要优势在于**它允许我们编写可接收任意参数类型(包括其值类别)的函数模板,并且能够将这些参数原封不动地转发到其他函数。**这种技术特别适用于那些函数行为依赖于参数的值类别(左值或右值)的场景。如果没有完美转发,我们可能需要为不同类型的参数(如左值和右值)编写多个函数重载,这会使得代码更加冗长和复杂。
以下是一个使用完美转发的实例,展示它在实际编程中的应用和优势:
示例:泛型包装器
假设我们正在编写一个泛型包装器类,它可以封装任意类型的对象,并提供一个通用的接口来访问这些对象。我们希望能够直接在包装器内部构造这些对象,而不是先构造一个对象再将其复制到包装器中。
template<typename T>
class Wrapper {
public:T value;template<typename... Args>Wrapper(Args&&... args) : value(std::forward<Args>(args)...) {}
};struct ExpensiveToCopy {ExpensiveToCopy() {}ExpensiveToCopy(const ExpensiveToCopy&) {std::cout << "Copy constructor called!" << std::endl;}ExpensiveToCopy(ExpensiveToCopy&&) noexcept {std::cout << "Move constructor called!" << std::endl;}
};int main() {ExpensiveToCopy etc;Wrapper<ExpensiveToCopy> w1(etc); // 应调用复制构造Wrapper<ExpensiveToCopy> w2(std::move(etc)); // 应调用移动构造
}
在这个例子中,Wrapper
的构造函数使用完美转发来接收任意数量和类型的参数,并将它们转发给它封装的值的构造函数。这保证了当我们传入一个右值时,将使用移动构造函数而不是复制构造函数,从而提高效率。
如果没有完美转发,是达不到我们预期的效果的:
在不使用 std::forward
的情况下,尽管 Args&&... args
使用的是通用引用(也称作转发引用),它可以绑定到左值和右值。但在没有显式地指定 std::forward
来保持参数的左右值属性的情况下,所有通过 args...
传递的参数在构造函数体内都会被当作左值处理。这是因为 args
是具名的变量,而具名的变量都是左值。 理解这一点很重要:
template<typename T>
class Wrapper {
public:T value;template<typename... Args>Wrapper(Args&&... args) : value(args...) {} // 没有完美转发
};struct ExpensiveToCopy {ExpensiveToCopy() {}ExpensiveToCopy(const ExpensiveToCopy&) {std::cout << "Copy constructor called!" << std::endl;}ExpensiveToCopy(ExpensiveToCopy&&) noexcept {std::cout << "Move constructor called!" << std::endl;}
};int main() {ExpensiveToCopy etc;Wrapper<ExpensiveToCopy> w1(etc); // 会调用复制构造Wrapper<ExpensiveToCopy> w2(std::move(etc)); // 也会调用复制构造,而不是移动构造
}
在这个修改后的示例中:
Wrapper<ExpensiveToCopy> w1(etc);
显然调用复制构造函数,因为etc
是一个左值。Wrapper<ExpensiveToCopy> w2(std::move(etc));
即使原始参数etc
被转化为右值,但在Wrapper
构造函数中args
仍然是左值。因此,尽管etc
最初被转为右值,它在传递到ExpensiveToCopy
的构造函数时又被当作左值处理,结果依然调用复制构造函数而不是移动构造函数。
没有使用 std::forward
,即使参数原本是右值,一旦传入 Wrapper
的构造函数,就丢失了其右值性质,从而导致不必要的复制。这说明了完美转发的重要性,特别是在需要保留参数原始属性(如移动语义)的场合。完美转发确保参数的值类别被保留和正确处理,从而可以有效利用 C++ 的移动语义,减少不必要的性能开销。
三、std::move和std::forward
(一)两者比较
1. move和forward都是仅仅执行强制类型转换的函数。std::move
无条件地将实参强制转换成右值。而std::forward
则仅在某个特定条件满足时(传入func的实参是右值时)才执行强制转换(本来都是具名参数,都是左值)。
2. std::move
并不进行任何移动,std::forward
也不进行任何转发。这两者在运行期都无所作为。它们不会生成任何可执行代码,连一个字节都不会生成。
(二)使用时机
1. 针对右值引用的最后一次使用实施std::move
,针对万能引用的最后一次使用实施std::forward
。最后一次使用的意思是,在一个对象的生命周期中,你确定之后不再需要读取或修改这个对象的状态时。在这个时间点之后,对象的任何资源(如动态内存)都可以安全地转让给另一个对象。
2. 在按值返回的函数中,如果返回的是一个绑定到右值引用或万能引用的对象时,可以实施std::move
或std::forward
。因为如果原始对象是一个右值,它的值就应当被移动到返回值上,而如果是左值,就必须通过复制构造出副本作为返回值。这种情况可以用这个例子来进行解释:
#include <iostream>
#include <string>
#include <utility>
#include <vector>// 返回局部右值引用对象,使用 std::move
std::vector<int> createVector() {std::vector<int> localVec = {1, 2, 3, 4, 5};return std::move(localVec); // 移动 localVec 到返回值
}// 接收万能引用,返回相同类型
template <typename T>
T relay(T &&obj) {// std::forward 确保 obj 的值类别保持不变return std::forward<T>(obj);
}int main() {auto vec = createVector(); // vec 通过移动构造器获取 localVec 的资源for (auto v : vec) {std::cout << v << " ";}std::cout << "\n";std::string str = "Hello, World!";auto result = relay(std::move(str)); // str 被视为右值,使用移动语义std::cout << "Result: " << result << " addr:" << &str << "\n";std::cout << "Original string: " << str << " addr:" << &str << " (moved)\n";std::string anotherStr = "Another test";auto anotherResult = relay(anotherStr); // anotherStr 仍为左值,使用复制语义std::cout << "AnotherStr " << anotherResult <<" addr:" << &anotherStr << "\n";std::cout << "Another result: " << anotherResult <<" addr:" << &anotherResult << "\n";}
运行结果:
./main
1 2 3 4 5
Result: Hello, World! addr:0x16d592db8
Original string: addr:0x16d592db8 (moved)
AnotherStr Another test addr:0x16d592d88
Another result: Another test addr:0x16d592d70
从打印结果可以看到,result 的内存地址(资源地址)和 str 的相同,而anotherstr 与 another 的资源地址不同,这说明前者使用的是移动语义,后者使用的是赋值语义。
至于 vec,情况有点复杂,在不考虑返回值优化的情况时(后面后提到,事实上 move 操作会抑制返回值优化,是否会 ROV,取决于编译器),它会将 createVector()
的局部变量移动到临时变量返回值,然后利用移动语义构造 vec。总之,移动语义和 ROV 都很重要。
(三)返回值优化(RVO)
1.两个前提条件
(1)局部对象类型和函数返回值类型相同;
(2)返回的就是局部对象本身(含局部对象或作为return 语句中的临时对象等)
2. 注意事项
(1)在RVO的前提条件被满足时,要么避免复制,要么会自动地用std::move隐式实施于返回值。
(2)按值传递的函数形参,把它们作为函数返回值时,情况与返回值优化类似。编译器这里会选择第2种处理方案,即返回时将形参转为右值处理。
(3)如果局部变量有资格进行RVO优化,就不要把std::move
或std::forward
用在这些局部变量中。因为这可能会让返回值丧失优化的机会。
用下面的例子来解释这些。
版本一
#include <iostream>
using namespace std;
class A {int data;public:A(int d = 0) : data(d) {}~A() { cout << "destructor called for object " << this << endl; }
};
A creat() {A a;cout << "a_addr " << &a << endl;return move(a);
}
int main() {A aa = creat();cout << "a_addr " << &aa << endl;return 0;
}
运行结果:
g++ 3.cxx -o main -std=c++11
./main
a_addr 0x16bdc6de4
destructor called for object 0x16bdc6de4
a_addr 0x16bdc6e28
destructor called for object 0x16bdc6e28
可以看到,这里析构了两次,第一次是 a,然后由于 ROV,直接在 creat() 调用位置得到了 aa,也就是通过 a 移动后得到的临时返回返回对象。
版本二
仅仅去掉 move():
A creat() {A a;cout << "a_addr " << &a << endl;return a;
}
运行结果:
g++ 3.cxx -o main -std=c++11
./main
a_addr 0x16d652e28
a_addr 0x16d652e28
destructor called for object 0x16d652e28
这次ROV 直接放开了,直接在 creat()调用处构建对象,从这两个例子可以看到,如果可以 ROV,尽量不要使用 move(),这会降低性能。
版本三
这次不修改代码,仅仅关闭返回值优化。
A creat() {A a;cout << "a_addr " << &a << endl;return move(a);
}
编译运行:
g++ -fno-elide-constructors 3.cxx -o main -std=c++11
./main
a_addr 0x16db0edd4
destructor called for object 0x16db0edd4
destructor called for object 0x16db0ee24
a_addr 0x16db0ee28
destructor called for object 0x16db0ee28
可以看到这次析构了三次。第一次是临时变量 a(0x16db0edd4),然后通过移动语义创建的返回值临时变量(0x16db0ee24),接着是利用复制构造函数构造的对象 aa(0x16db0ee28)。可以看到这里的 move 仅仅增加了一点点性能(移动语义创建临时变量上)。
版本四
这次不修改代码,仅仅关闭返回值优化。
A creat() {A a;cout << "a_addr " << &a << endl;return a;
}
编译运行:
g++ -fno-elide-constructors 3.cxx -o main -std=c++11
./main
a_addr 0x16d542dd4
destructor called for object 0x16d542dd4
destructor called for object 0x16d542e24
a_addr 0x16d542e28
destructor called for object 0x16d542e28
这个和上面的区别就是,没有使用移动语义创建临时返回对象,其它都一样。
(四)总结以上四个版本
这四个例子非常好地说明了 C++ 中关于返回值优化(RVO)、移动语义以及它们对程序性能的影响。下面是每个例子的详细分析和它们所揭示的关键概念:
版本一:使用 std::move
返回局部对象
- 编译和运行结果: 对象
a
的地址在creat()
和main()
函数中不同,说明发生了一次移动操作。 - 性能影响: 显式使用
std::move
禁止了 RVO 的应用。尽管利用了移动语义,但仍然有额外的移动构造调用,导致两次析构:一次是a
的析构,一次是aa
的析构。
版本二:正常返回局部对象
- 编译和运行结果:
a
的地址在creat()
和main()
中相同,表明直接在aa
的存储位置构造了a
,没有发生复制或移动。 - 性能影响: RVO 完全生效,避免了任何复制或移动操作,只有一次析构,即
aa
的析构。
版本三:使用 std::move
且禁用 RVO
- 编译和运行结果: 出现三次析构,首先是局部变量
a
,然后是由std::move(a)
生成的临时对象,最后是aa
。 - 性能影响: 禁用 RVO 后,必须通过移动构造函数生成返回值的临时对象和最终的
aa
对象。这增加了构造和析构的调用次数,降低了性能。
版本四:正常返回局部对象且禁用 RVO
- 编译和运行结果: 与版本三类似,有三次析构,但所有对象都是通过复制构造函数创建,没有使用移动构造函数。
- 性能影响: 禁用 RVO 后,每次返回都会创建一个新的对象实例。由于没有使用
std::move
,所有对象都是通过复制构造,而不是移动构造,这通常更加耗费资源。
这些例子强调了几个重要点:
- 返回值优化的重要性: RVO 可以显著提高性能,通过避免不必要的复制和移动操作。
std::move
的谨慎使用: 在返回局部对象时,通常应避免使用std::move
,以允许编译器执行 RVO。使用std::move
可能会阻止这种优化,除非确实需要(如返回类成员或函数参数)。- 编译器优化的智能性: 现代编译器非常擅长优化,通常最好的做法是写出清晰直接的代码,让编译器为我们优化。
相关文章:

C++:完美转发(一)(std::forward)
一、理解引用折叠 (一)引用折叠 1. 在C中,“引用的引用”是非法的。像 auto& &rx x;(注意两个&之间有空格)这种直接定义引用的引用是不合法的,但是编译器在通过类型别名或模板参数推导等语境…...

西部首个全域直播基地,打造西部直播基地领军形象
天府锋巢直播产业基地作为西部直播产业的领军者,以其前瞻性的战略布局和卓越的服务体系,正加速推动全域直播的快速发展,助力直播产业实现新升级。该基地作为成都规模最大的直播基地,以加快全域直播为核心目标,通过促进…...

钟表——蓝桥杯十三届2022国赛大学B组真题
问题分析 这个问题的关键有两点:1.怎么计算时针,分针,秒针之间的夹角,2.时针,分针,秒针都是匀速运动的,并非跳跃性的。问题1很好解决看下面的代码就能明白,我们先考虑问题2…...

CSS 之 圆形波浪进度条效果
一、简介 本篇博客讲述了如何实现一个圆形波浪进度条的样式效果,具体效果参考下方GIF图。该样式的加载进度条可以用在页面跳转或数据处理等情况下的加载动画,比起普通的横条进度条来说,样式效果更生动美观。 实现思路: 这…...

按下鼠标进行拖拽,让元素跟随鼠标进行移动,鼠标抬起,元素停止移;js鼠标拖拽 (鼠标按下事件:onmousedown、鼠标移动事件:onmousemove、鼠标抬起事件:onmouseup)
需求如下: 按下鼠标进行拖拽,让元素跟随鼠标进行移动,鼠标抬起,元素停止移动。 解析: 鼠标按下事件:onmousedown 鼠标移动事件:onmousemove 鼠标抬起事件:onmouseup <!DOCT…...

第十二章 项目采购管理
12.1 规划采购管理 12.2 实施采购 12.3 控制采购 项目经理通常没有签订合同的权限,但必须熟悉正规的采购流程; 协议是采购的核心文件,关于协议我们要知道: 协议包括:合同、服务水平协议、谅解、协议备忘录或采购订单 ❗…...

PSFR-GAN复现
写在前面:本博客仅作记录学习之用,部分图片来自网络,如需引用请注明出处,同时如有侵犯您的权益,请联系删除! 文章目录 前言快速开始安装依赖权重下载及复原 训练网络数据集训练脚本 代码详解训练BaseOptio…...

函数和数组
一、函数 1.函数使用方法 定义函数再引用函数 2.基本函数格式 基本格式1: function 函数名{ 命令序列 } 基本格式2: 函数名(){ 命令序列 } 基本格式3: function func_name () {…...

docker安装时报错:Error: Nothing to do
安装docker时报以下错误 解决方法: 1.下载关于docker的相关依赖环境 yum -y install yum-utils device-mapper-persistent-data lvm22.设置下载Docker的镜像源 yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo3…...

白盒测试:覆盖测试及测试用例设计
白盒测试:覆盖测试及测试用例设计 一、实验目的 1、掌握白盒测试的概念。 2、掌握逻辑覆盖法。 二、实验任务 某工资计算程序功能如下:若雇员月工作小时超过40小时,则超过部分按原小时工资的1.5倍的加班工资来计算。若雇员月工作小时超过…...

Java高级开发2024高频面试提问题目
1、请先简单自我介绍一下自己?(一般不超过5min) 2、你最熟悉的项目是哪一个,讲一下用了哪些技术栈?(尽量讲出系统架构图使用到的技术组件和为什么选型这个组件?) 3、你项目中使用什…...

Kamailio openssl 3.0.x 需要注意的事项
我们留意到 Debian Bookworm 安装的 openssl 版本是 3.0.x 这里有几个地方要注意: modparam("tls", "init_mode", 1)核心参数 tls_threads_mode 配置为 1 或者 配置为 2,默认为 0版本建议用 5.8.1,貌似 5.7.x 也行 参…...

SpringAMQP Work Queue 工作队列
消息模型: 代码模拟: 相较于之前的基础队列,该队列新增了消费者 不再是一个,所以我们通过代码模拟出两个consumer消费者。在原来的消费者类里写两个方法 其中消费者1效率高 消费者2效率低 RabbitListener(queues "simple.queue")public voi…...

一分钟带你了解什么是等保测评
等保测评,即网络安全等级保护测评,是依据国家信息安全等级保护制度规定,对信息系统进行安全技术测评和安全管理测评,以确定系统的安全保护水平是否达到预定的安全等级要求。以下是等保测评的相关知识点总结: 测评概述&…...

宝塔面板怎么解决nginx跨域问题
1.找到宝塔的nginx配置文件 宝塔有一点不同,nginx配置文件不在nginx的安装目录中,应当去/www/server/panel/vhost/nginx找到 2.添加你要跨域的地址 location /api {proxy_pass http://localhost:8080;proxy_set_header Host $host;proxy_set_header X-…...

Python 自动化脚本系列:第1集
昨天写了一篇介绍如何使用Python实现自动化任务的,文章末尾介绍了一个简单的自动化脚本,因此今天编号从2开始。顺便附上昨天的文章链接: Python 自动化脚本系列:介绍 欢迎关注博主,持续输出更多Python相关内容&#…...

基于PHP开发的图片高清无损在线压缩源码系统 带完整源代码以及搭建教程
系统概述 高清无损在线压缩源码系统基于PHP语言开发,结合GD库和ImageMagick等图像处理工具,实现了对JPEG、PNG、GIF等多种图片格式的高清无损压缩。系统采用B/S架构,用户只需通过浏览器访问系统界面,即可实现图片的上传、压缩、预…...

Linux提权--SUDO(CVE-2021-3156)Polkit(CVE-2021-4034)
免责声明:本文仅做技术学习与交流... 目录 SUDO(CVE-2021-3156) 影响版本 -判断: -利用: Polkit(CVE-2021-4034) -判断: -利用: 添加用户 SUDO(CVE-2021-3156) another: SUDO权限配置不当. 影响版本 由系统的内核和发…...

nodejs里面的 http 模块介绍和使用
Node.js的HTTP模块是一个核心模块,它提供了很多功能来创建HTTP服务器和发送HTTP请求。 http.Server是一个基于事件的http服务器,内部是由c实现的,接口是由JavaScript封装。 http.request是一个http客户端工具。 用户向服务器发送数据。 创…...

MVC框架简易实现【精细】
目录 mvc 的架构 MVC 框架 传统web开发的弊端 1.创建一个maven项目 2.添加maven依赖 3.创建TomCatService类 3.1 创建TomcatService类 3.2 TomcatService类讲解 3.3 安装项目到本地仓库,给其他项目使用 4.测试启动Tomcat 4.1 创建一个新的maven项目 4.2 引用…...

Java入门基础学习笔记18——赋值运算符
赋值运算符: 就是“”,就是给变量赋值的,从右边往左边看。 int a 10; // 把数据赋值给左边的变量a存储。 扩展赋值运算符: 注意:扩展的赋值运算符隐含了强制类型转换。 package cn.ensource.operator;public class…...

csv 可视化 python代码
excel查看csv后,csv就被锁定了,不能修改,用pyqt写一个csv查看工具,拖拽查看,非常方便 目录 第2版,提升加载速度 选中单元格统计个数,求和,平均值...

HashMap 和 Hashtable区别的底层原理
一、容器键值对: 1.HashMap 的 key 和 value 都允许为 null , HashMap 在 key 为 null 的时候,值必须为null。 2.Hashtable 的 key 和 value 都不允许为 null 。 Hashtable 遇到key或value为 null时 ,将抛出 NullPointerException…...

代码随想录35期Day32-Java
Day32题目 LeetCode122.买股票的最佳时机 核心思想:很简单,只要第二天比第一天贵,就第一天买入,第二天卖出 class Solution {public int maxProfit(int[] prices) {// 只要后一天比这一天价钱高就买,然后第二天卖出…...

ROS 2边学边练(45)-- 构建一个能动的机器人模型
前言 在上篇中我们搭建了一个机器人模型(其由各个关节(joint)和连杆(link)组成),此篇我们会通过设置关节类型来实现机器人的活动。 在ROS中,关节一般有无限旋转(continuous),有限旋转…...

【第66例】IPD体系进阶:华为IPD发展历程
目录 简介 样例 作者简介 简介 想要引入 IPD,首先就要思考一些问题: 跟我的企业适配吗? 流程会不会太重了? 一定要引入吗,有没有其他方式? 从目前大的环境来说。 中国制造业正在由“中国制造”向“中国创造”转变。 这也是企业价值转移的趋势。 宏碁集团创始人施振…...

websevere服务器从零搭建到上线(四)|muduo网络库的基本原理和使用
文章目录 muduo源码编译安装muduo框架讲解muduo库编写服务器代码示例代码解析用户连接的创建和断开回调函数用户读写事件回调 使用vscode编译程序配置c_cpp_properties.json配置tasks.json配置launch.json编译 总结 muduo源码编译安装 muduo依赖Boost库,所以我们应…...

C语言笔记10
1.用指针打印一维数组 //1.用指针打印一维数组 #include <stdio.h> int main() {//int arr[] { 1,2,3,4,5 };int arr[5] { 0 };int* p &arr[0];int sz sizeof(arr) / sizeof(arr[0]);for (int i 0; i < sz; i){scanf("%d", &arr[i]);}//int* …...

BMS-HiL主要功能
BMS HIL 系统中 PC 机中安装实验管理软件用于测试过程管理和测试序列编辑,通过以太网与 PXI 机箱中的处理器进行连接,处理器中运行实时系统(Real Time)并安装 Veristand 终端引擎,通过与 PC 中的数据传输,对…...

idea无法识别加载pom.xml文件
有时idea无法识别加载pom.xml文件,直接打开pom.xml文件,然后添加到maven就行...