-
【操作步骤&问题现象】参照https://gitee.com/ascend/samples,目前最新版本只提供了C++实现的基于caffe模型的示例,目前我要应用yolov3_resnet18模型,已经完成了转换,想应用在Atlas200DK上运行,不知道pb模型转化的om模型是否可以应用在C++示例中呢?如果可以的话,有哪些文件需要修改呢?
-
今天跟朋友聊天,他问我我们做算法的用什么语言,(他是c++的),我说用python,他反问,为什么要用python,用c++多好,又稳定,又通用,然后我就开始喷他了python脚本语言写起来简单容易,算法专注解决实际业务问题,需要写的的是上层逻辑,而不是底层逻辑,c++写上层逻辑,代码量太大,最关键的是Python历史上也一直都是科学计算和数据分析的重要工具,有numpy这样的底子,因为行业近似所以选择API binding语言的时候会首选Python,同时复用numpy这样的基础库既减少了开发工作量,也方便从业人员上手,所以在人工智能领域,python比c++更适用。,
-
摘要:近期踩到了一些比较隐晦的C++的坑,可把我们团队给坑惨了~~近期我们团队进行版本质量加固时,踩到了一些比较隐晦的C++的坑,特总结分享在此,供大家参考。1. string的字符串拼接,导致coredump该问题的核心点在于第9行,竟然是可以编译通过,其原因是x+"-",会被转成char*,然后与to_string叠加导致BUG。2. map的迭代器删除map要删除一个元素,通常通过erase()函数来完成,但是要注意,如果我们传入了一个iterator作为erase的参数来删除当前迭代器所指向的元素,删除完成后iterator会失效,产生未定义行为。正确的使用方法应该是接收erase()的返回值,让iterator指向被删除元素的下一个元素或者end()。for ( auto iter = m.begin(); iter != m.end(); iter++) { if (...) iter = m.erase(iter); } 但是上述代码仍然有错误,因为如果触发了删除,那么iter再下一轮循环时会指向下下个元素,所以正确的写法应该是: for ( auto iter = m.begin(); iter != m.end();) { if (...) { iter = m.erase(iter); continue ; } else { iter++; } } 3. stringstream的性能问题stringstream的清空是clear之后,置空。stringstream在任何情况下都比snprintf慢。memset是个很慢的函数,宁愿新创建对象。上述测试结果是单线程,改成多线程,同样成立。str += “a”, 比 str =str+ “a” 效率高很多,后者会创建新对象。4. 智能指针(shared_ptr)使用注意4.1尽量使用make_shared初始化提高性能std::shared_ptr<Widget> spw(newWidget);需要分配两次内存。每个std::shared_ptr都指向一个控制块,控制块包含被指向对象的引用计数以及其他东西。这个控制块的内存是在std::shared_ptr的构造函数中分配的。因此直接使用new,需要一块内存分配给Widget,还要一块内存分配给控制块autospw = std::make_shared<Widget>();一次分配就足够了。这是因为std::make_shared申请一个单独的内存块来同时存放Widget对象和控制块。这个优化减少了程序的静态大小,因为代码只包含一次内存分配的调用,并且这会加快代码的执行速度,因为内存只分配了一次。另外,使用std::make_shared消除了一些控制块需要记录的信息,这样潜在地减少了程序的总内存占用。异常安全processWidget(std::shared_ptr<Widget>( new Widget), //潜在的资源泄露 computePriority()); 上述代码存在内存泄漏的风险,上述代码执行分为3个步骤:1. new Widget2. shared_ptr构造3. computePriority 编译器不需要必须产生这样顺序的代码,但“new Widget”必须在std::shared_ptr的构造函数被调用前执行。如果编译器产生的顺序代码如下:1. new Widget2. 执行computePriority。3. 执行std::shared_ptr的构造函数。 如果执行步骤2:computePriority的时候程序出现异常,则在第一步动态分配的Widget就会泄露了,因为它永远不会被存放到在第三步才开始管理它的shared_ptr中4.2 父类之类智能指针转换C++中是允许裸指针,因此裸指针之间转换方法同C语言指针强转,智能指针转换不能通过上述方法进行强转,必须通过库提供转换函数进行转换。 C++11的方法是:std::dynamic_pointer_cast;boost中的方法是:boost::dynamic_pointer_cast#include <memory>#include <boost/shared_ptr.hpp>#include <boost/make_shared.hpp>#include <iostream>class Base { public : Base(){} virtual ~Base() {}};class D : public Base { public : D(){} virtual ~D() {}};int main(){ //方式一:先初始化子类智能指针,然后调用dynamic_pointer_cast转换成基类智能指针对象 std::shared_ptr<D> d1 = std::make_shared<D>(); std::shared_ptr<Base> b1 = std::dynamic_pointer_cast<Base>(d1); //方式二:先new子类D的指针,然后调用shared_ptr的构造函数初始化基类智能指针 std::shared_ptr<Base> b2 = shared_ptr<Base>( new D()); return 0;} 结论方式一和方式二均能够实现基类智能指针指向子类,但建议采用方式1,通过std::make_shared的方式构造智能指针,然后进行转换;5. map的安全查找办法即map[key]这种写法,就是会创建元素(且不一定初始化),因此在业务逻辑是希望查找的时候,就老老实实用find,不然会有脏数据写入。6. string 的指针构造std::string 的构造方式,除了与其它顺序容器相近的方式之外,提供了三种额外的构造方式:string s(cp, n): s 是cp指向的数组中前n个字符的拷贝,该数组至少应该包含n个字符string s(s2, pos2):s 是string s2从下标pos2开始的字符的拷贝,若pos2>s2.size(),构造函数的行为未定义string s(s2, pos2, len2):s 是string s2从下标pos2开始len2个字符的拷贝,若pos2>s2.size(),构造函数的行为未定义。不管len2的值是多少,构造函数至多拷贝s2.size()-pos2个字符 std::string 未提供 string(cp, pos2, len2) 这种构造方式,如果代码中使用了该方式,最终会将 cp 指向的数组构造成一个string,然后调用string(s2, pos2, len2)这种构造方式。不提供string(cp, pos2, len2)这种构造方式原因在于:使用这种方式构造容易出现问题,cp是一个指针,通常使用时,能获得其数组长度并检查传入参数;若传入两个参数,容易出现越界。7. 变量初始化变量初始化总是没错的,不管后面是否会修改该值。尤其是int等内建的类型,在类或struct中及容易忽略初始化,使变量成为随机值,产生不可预知的错误。变量请初始化!变量请初始化!!变量请初始化!!!
-
近期我们团队进行版本质量加固时,踩到了一些比较隐晦的C++的坑,特总结分享在此,供大家参考。1. string的字符串拼接,导致coredump。`该问题的核心点在于第9行,竟然是可以编译通过,其原因是x+"-",会被转成char*,然后与to_string叠加导致BUG。2. map的迭代器删除map要删除一个元素,通常通过erase()函数来完成,但是要注意,如果我们传入了一个iterator作为erase的参数来删除当前迭代器所指向的元素,删除完成后iterator会失效,产生未定义行为。正确的使用方法应该是接收erase()的返回值,让iterator指向被删除元素的下一个元素或者end()。 for (auto iter = m.begin(); iter != m.end(); iter++) { if (...) iter = m.erase(iter); }但是上述代码仍然有错误,因为如果触发了删除,那么iter再下一轮循环时会指向下下个元素,所以正确的写法应该是: for (auto iter = m.begin(); iter != m.end();) { if (...) { iter = m.erase(iter); continue; } else { iter++; } }3. stringstream的性能问题stringstream的清空是clear之后,置空。stringstream在任何情况下都比snprintf慢。memset是个很慢的函数,宁愿新创建对象。上述测试结果是单线程,改成多线程,同样成立。str += “a”, 比 str =str+ “a” 效率高很多,后者会创建新对象。4. 智能指针(shared_ptr)使用注意1.尽量使用make_shared初始化提高性能std::shared_ptr<Widget> spw(new Widget);需要分配两次内存。每个std::shared_ptr都指向一个控制块,控制块包含被指向对象的引用计数以及其他东西。这个控制块的内存是在std::shared_ptr的构造函数中分配的。因此直接使用new,需要一块内存分配给Widget,还要一块内存分配给控制块auto spw = std::make_shared<Widget>();一次分配就足够了。这是因为std::make_shared申请一个单独的内存块来同时存放Widget对象和控制块。这个优化减少了程序的静态大小,因为代码只包含一次内存分配的调用,并且这会加快代码的执行速度,因为内存只分配了一次。另外,使用std::make_shared消除了一些控制块需要记录的信息,这样潜在地减少了程序的总内存占用。异常安全processWidget(std::shared_ptr<Widget>(new Widget), //潜在的资源泄露 computePriority());上述代码存在内存泄漏的风险,上述代码执行分为3个步骤:1. new Widget 2. shared_ptr构造 3. computePriority编译器不需要必须产生这样顺序的代码,但“new Widget”必须在std::shared_ptr的构造函数被调用前执行。如果编译器产生的顺序代码如下:1. new Widget 2. 执行computePriority。 3. 执行std::shared_ptr的构造函数。如果执行步骤2:computePriority的时候程序出现异常,则在第一步动态分配的Widget就会泄露了,因为它永远不会被存放到在第三步才开始管理它的shared_ptr中2. 父类之类智能指针转换C++中是允许裸指针,因此裸指针之间转换方法同C语言指针强转,智能指针转换不能通过上述方法进行强转,必须通过库提供转换函数进行转换。 C++11的方法是:std::dynamic_pointer_cast;boost中的方法是:boost::dynamic_pointer_cast#include <memory> #include <boost/shared_ptr.hpp> #include <boost/make_shared.hpp> #include <iostream> class Base { public: Base(){} virtual ~Base() {} }; class D : public Base { public: D(){} virtual ~D() {} }; int main() { //方式一:先初始化子类智能指针,然后调用dynamic_pointer_cast转换成基类智能指针对象 std::shared_ptr<D> d1 = std::make_shared<D>(); std::shared_ptr<Base> b1 = std::dynamic_pointer_cast<Base>(d1); //方式二:先new子类D的指针,然后调用shared_ptr的构造函数初始化基类智能指针 std::shared_ptr<Base> b2 = shared_ptr<Base>(new D()); return 0; }结论方式一和方式二均能够实现基类智能指针指向子类,但建议采用方式1,通过std::make_shared的方式构造智能指针,然后进行转换;5. map的安全查找办法即map[key]这种写法,就是会创建元素(且不一定初始化),因此在业务逻辑是希望查找的时候,就老老实实用find,不然会有脏数据写入。6. string 的指针构造std::string 的构造方式,除了与其它顺序容器相近的方式之外,提供了三种额外的构造方式:string s(cp, n): s 是cp指向的数组中前n个字符的拷贝,该数组至少应该包含n个字符 string s(s2, pos2):s 是string s2从下标pos2开始的字符的拷贝,若pos2>s2.size(),构造函数的行为未定义 string s(s2, pos2, len2):s 是string s2从下标pos2开始len2个字符的拷贝,若pos2>s2.size(),构造函数的行为未定义。不管len2的值是多少,构造函数至多拷贝s2.size()-pos2个字符std::string 未提供 string(cp, pos2, len2) 这种构造方式,如果代码中使用了该方式,最终会将 cp 指向的数组构造成一个string,然后调用string(s2, pos2, len2)这种构造方式。不提供string(cp, pos2, len2)这种构造方式原因在于:使用这种方式构造容易出现问题,cp是一个指针,通常使用时,能获得其数组长度并检查传入参数;若传入两个参数,容易出现越界。7. 变量初始化变量初始化总是没错的,不管后面是否会修改该值。尤其是int等内建的类型,在类或struct中及容易忽略初始化,使变量成为随机值,产生不可预知的错误。变量请初始化变量请初始化变量请初始化
-
大家好:MindSpore在设计C++接口,开发人员在这里写了一些初步的想法:https://gitee.com/mindspore/mindspore/issues/I235H8想听听大家的需求和建议,可通过论坛回帖、Issue评论和微信群回复的方式参与,建议被采纳即可获得MindSpore定制T恤一件,截止时间为11月8日,希望大家一起参与,实现我们心目中理想的C++接口!微信扫描二维码加群
-
使用最版本的调优工具,对沙箱实验室里面的鲲鹏软件性能调优实践任务进行测试,失败,配置截图如下,麻烦专家分析下原因。
-
以下为译文:70年代初,贝尔实验室创建了C语言,它是开发UNIX的副产品。很快C就成为了最受欢迎的编程语言之一。但是对于Bjarne Stroustrup来说,C的表达能力还不够。于是,他在1983年的博士论文中扩展了C语言。于是,支持类的C语言诞生了。当时,Bjarne Stroustrup明白编程语言有许多组成部分,除了语言本身,还有编译器、链接器和各种库。提供熟悉的工具有助于语言被广泛接受。在这种历史背景下,在C语言的基础上开发C++也是有道理的。40年后,C和C++都在行业中得到了广泛使用。但是,互联网上的C开发人员认为C++是有史以来最糟糕的人类发明,而许多C++开发人员则希望有朝一日C语言灰飞烟灭。究竟发生了什么事?从表面上看,C和C++都可以满足相同的用例:高性能、确定性、原生但可移植的代码,可用于最广泛的硬件和应用程序。但是,更让C自豪的是它是一门低级语言,更接近汇编。而C++,从诞生第一天开始就充斥了各种奇怪的东西。例如析构函数这个黑魔法。自作主张的编译器。尽管很早C++就有了类型推断功能,但是80年代中期的开发人员还无法接受这个概念,因此Bjarne Stroustrup不得不删除了auto,直到C++ 11又重新添加回来。从那以后,C++就不断加入各种工具来实现抽象。很难说C++是一种低级语言还是高级语言。从设计目的上来说,C++两者都是。但是在不牺牲性能的情况下,建立高级抽象是很困难的。于是C++引入了各种工具来实现constexpr、move语义、模板和不断增长的标准库。从根本上讲,我认为C信任开发人员,而C++信任编译器。这是一个巨大的差异,单凭“两者的原生类型相同”、“while循环的语法相同”等简单一致是无法掩盖的。C++开发人员将有这些问题归咎于C,而C开发人员则认为C++过于疯狂。我觉得站在C的角度看C++,这种说法也很正确。作为C的超集,C++确实很疯狂。一个经验丰富的C开发人员面对C++可能没有熟悉的感觉。C++不是C,这就足以引发互联网上的激烈争论。然而,虽然我不喜欢C,但也没有权利取笑C。尽管我有一定的C++经验,但用C编写过的代码少之又少,而且肯定是很糟糕的代码。好的编程语言包括良好的实践、模式、惯用写法,这些都需要多年的学习。如果你尝试用编写C++的方式写C的代码,或者用C的方式编写C++的代码,那感觉一定很糟糕。即便你懂C,也不一定会C++,反之亦然,懂C++也不一定会用C编程。那么,我们是否应该停止说C/C++,为这两个不幸的命名而感到悲哀吗?也不至于。尽管C++的设计理念与C不一样,但是C++仍然是C的超集。也就是说,你可以在C++转换单元中包含C的头文件,这样依然可以通过编译。而这正是造成混乱的地方。C++不是C的扩展,它是由不同的委员会、不同的人独立设计的标准。从逻辑上讲,喜欢C++理念的人会参与C++社区以及C++标准化的过程,而其他人可能会尝试参与C。无论是C的委员会还是C++委员会,他们表达意图和方向的方式只能通过各自的最终产品:标准;而标准是众多投票的成果。然而,编译器很难知道它正在处理的是C头文件还是C++头文件。extern “C” 标记并没有得到广泛一致的使用,而且它只能影响修饰,而不会影响语法或语义。头文件仅对预处理器有影响,对于C++编译器而言,所有内容都是C++转换单元,因此也就是C++。然而,人们依然会在C++中包含C头文件,并期望它“正常工作”,而大多数时候也确实可以正常工作。那么,我们不禁想问:由不同地方的、不同的人开发的C++代码如何保持C的兼容性?恐怕很难。最近,一位同事让我想起了康威定律:"设计系统的架构受制于产生这些设计的组织的沟通结构。"根据这个逻辑,如果两个委员不互相合作,则他们创造的语言也不会互通。C++维护了一个与C及其标准库的不兼容列表。然而该列表似乎并未反映出许多C11和C18中添加、但在C++中不合法的功能。更清晰的介绍请参见这个维基本科页面(https://en.wikipedia.org/wiki/Compatibility_of_C_and_C%2B%2B)。然而,仅仅列出两种语言之间的不兼容性,并不足以衡量二者的不兼容性。那些存在于C++标准库中但主要声明来自C的函数,很难声明成constexpr,更难声明成noexcept。C的兼容性会导致性能成本,而C函数是优化的障碍。许多C的结构在C++中都是有效的,但无法通过代码审查(如NULL、longjmp、malloc、构造/析构函数、free、C风格的类型强制转换等)。在C看来,这些惯用写法可能问题不大,但在C++中可不行。C++具有更强大的类型系统,不幸的是,C的惯用写法在这个类型系统中凿了一个洞,因此实现C的兼容性需要在安全性方面付出代价。别误会,C++仍然关心C的兼容性,某种程度上。然而,有趣的是C也很关心C++,某种程度上。实话实说,C对C++的关心程度可能高于C++对C的关心。看来,每个委员会还是在乎另一个委员会的工作。但我们很不情愿。C++知道,许多基础库都是用C编写的,不仅包括libc,而且还有zip、png、curl、openssl(!)以及许多其他库,无数的C++项目都在使用这些库。C++不能破坏这些兼容性。但是最近,尤其是在过去的十年中,C++的规模已远远超过C。C++拥有更多的用户,并且社区更加活跃。也许这就是为什么如今C++委员会的规模是C委员会的10倍以上。C++是不可忽视的力量,因此C委员会必须考虑不破坏C++兼容性。如果非要说一个标准追随另一个标准对话,那么如今C++是领头者,而C是追随者。现在,C++处于稳定的三年周期中,无论是风雨还是烈日,抑或是致命的新疫情。而C每十年左右才发布一次主版本。不过这也很合理,因为作为一种较低级的语言,C不需要发展得那么快。C语言的环境也与C++完全不同。C多用于平台,更多地用于编译器。每个人(甚至他们的狗狗)都会编写C编译器,因为该语言的特性集很小,所以任何人都可以编写C编译器。而C++委员会真正考虑的实现只有四种,而且在每次会议上这四种实现都会出现。所以,C语言中的许多功能都是与实现有关的,或者是可选支持的,这样各种编译器不需要做太多努力就可以声称自己遵从了标准,据说这样委员会的人会比较高兴。如今,C++更加侧重于可移植性,而不是实现的自由。这又是一个理念的不同。因此,你的提议破坏了C的兼容性我提议的P2178的一部分理论上会影响与C的兼容性。这样的话所有方案都不会令人满意。有人可能会说,你可以先向C委员会提议你的新特性。这意味着需要召开更多会议。C会议的严格出席规则可能导致你无法参加会议,这就将那些不愿意花上数千美元成为ISO会员的个人拒之门外。这是因为C委员会必须遵守ISO的规则。而且,如果新的标准刚刚发布,那么可能还需要等待十年时间,你的提案才会被考虑。最重要的是,如果C委员不理解或不在乎你正在努力解决的问题,那么你的提案就石沉大海了。或者他们可能没有精力来处理这个问题。而且,可能你也没有精力来处理C。毕竟,你的本意是要改进C++。实际上,哪怕会议上无人反对你的提议(尽管不太可能发生),如果有人让你先去跟C委员会的人讨论,就等于给你的提议判了死刑。另一种可能的情况是,C委员会接受与C++中存在的版本略有不同的版本。true只能做一个宏来实现。char16_t需要通过typedef。char32_t不一定是UTF-32。static_assert对应的是 _Static_assert。这类的情况还有很多,我们应该责备C吗?可能不应该。他们的委员会只是在尽力将C语言做好。反之亦然。在C++20中,指定的初始化器就受到了C的启发,但采取了略微不同的规则,因为如果完全一样的话就不符合C++的初始化规则。对于这个问题,我也有责任。C有VLA。如果当时我在,我一定会反对在标准C++中采用它,因为它导致了太多安全性问题。我也会坚决反对将_Generic添加到C++中的提议。也许_Generic的目的是减少由于缺乏模板或缺乏重载而导致的问题,但是C++有这两个功能,从我的角度来看,_Generic并不适合我想象中的C++。这两个委员会似乎对于对方语言的关心程度也不一样。有时我们会遇到兼容性非常好的情况(std::complex),有时完全不在乎兼容性(静态数组参数)。这没有办法。别忘了每个委员会都是一群人,他们在不同的时间、不同的地点投票,而试图控制结果会导致投票毫无意义。将这些人放在同一个房间也不现实。ISO可能会反对,参与者的不平衡会导致C的人处于极大的劣势。C的兼容性不重要如果你是C开发人员,那么肯定会把C视为一种简洁的编程语言。但对于我们其他人而言,C的印象完全不同。C是通用的、跨语言的胶水,可以将一切紧密地结合在一起。对于C++用户而言,C就是他们的API。从这一点来看,C的价值在于其简单性。请记住,C++关心的那一部分C是出现在接口(头文件)中的C。我们关心的是声明,而不是定义。C++需要调用C库中的函数(Python、Fortran、Rust、D、Java等语言也一样,在所有情况下都可以在接口边界使用C)。因此,C是一种接口定义语言。向C添加的内容越多,定义接口就越困难。这些接口随着时间的推移保持稳定的可能性较小。那么,C++中缺少<threads.h>是否重要?可能并不重要,因为这不太可能出现在公共接口中。如今大家都在谈论C过去,C的兼容性是C++的一大卖点。但如今,每个人(甚至他们的金鱼)都懂C。Rust可以调用C函数,Python、Java、一切语言都可以!甚至怪异的Javascript都可以在WebAssemby中调用C函数。但是在这些语言中,接口是显式的。该语言提供的工具可以公开特定的C声明。当然,这比较麻烦。但这可以让接口非常非常清晰。而且还是有界的。例如,在rust中,调用C函数并不会迫使Rust牺牲某些设计来容纳C子集。实际上C是被包含进去的。mod confinment {use std::os::raw::{c_char};extern "C" {pub fn puts(txt: *const c_char);}}pub fn main() {unsafe {confinment::puts(std::ffi::CString::new("Hello, world!").expect("failed!").as_ptr());}}
-
该测试demo适用于C++。demo和使用指导见附件。新版本链接:https://bbs.huaweicloud.com/forum/thread-87162-1-1.html
-
2020-09-26:请问rust中的&和c++中的&有哪些区别?#福大大架构师每日一题#
-
python全部报如下错误,C++可以正常运行,读取摄像头正常,使用的是gitee上的源码https://gitee.com/Atlas200DK[2020-08-11 11:46:07][LIBMEDIA_ERROR] libmedia_process_test_lock flock failed[2020-08-11 11:46:07][LIBMEDIA_ERROR] OpenCamera ERROR! Camera[0] has been opened or should run at SU modeOpen camera 0 failedOpen camera 0 failed
-
一、导语C++是一门被广泛使用的系统级编程语言,更是高性能后端标准开发语言;C++虽功能强大,灵活巧妙,但却属于易学难精的专家型语言,不仅新手难以驾驭,就是老司机也容易掉进各种陷阱。本文结合作者的工作经验和学习心得,对C++语言的一些高级特性,做了简单介绍;对一些常见的误解,做了解释澄清;对比较容易犯错的地方,做了归纳总结;希望借此能增进大家对C++语言了解,减少编程出错,提升工作效率。二、陷阱我的程序里用了全局变量,为何进程退出会莫名其妙的core掉?Rule:C++在不同模块(源文件)里定义的全局变量,不保证构造顺序;但保证在同一模块(源文件)里定义的全局变量,按定义的先后顺序构造,按定义的相反次序析构。我们程序在a.cpp里定义了依次全局变量X和Y;按照规则:X先构造,Y后构造;进程停止执行的时候,Y先析构,X后析构;但如果X的析构依赖于Y,那么core的事情就有可能发生。结论:如果全局变量有依赖关系,那么就把它们放在同一个源文件定义,且按正确的顺序定义,确保依赖关系正确,而不是定义在不同源文件;对于系统中的单件,单件依赖也要注意这个问题。std::sort()的比较函数有很强的约束,不能乱来相信工作5年以上至少50%的C/C++程序员都被它坑过,我已经听到过了无数个悲伤的故事,《圣斗士星矢》,《仙剑》,还有别人家的项目《天天爱消除》,都有人掉坑,程序运行几天莫名奇妙的Crash掉,一脸懵逼。如果要用,要自己提供比较函数或者函数对象,一定搞清楚什么叫“严格弱排序”,一定要满足以下3个特性:非自反性非对称性传递性尽量对索引或者指针sort,而不是针对对象本身,因为如果对象比较大,交换(复制)对象比交换指针或索引更耗费。注意操作符短路考虑游戏玩家回血回蓝(魔法)刷新给客户端的逻辑。玩家每3秒回一点血,玩家每5秒回一点蓝,回蓝回血共用一个协议通知客户端,也就是说只要有回血或者回蓝就要把新的血量和魔法值通知客户端。玩家的心跳函数heartbeat()在主逻辑线程被循环调用void GamePlayer::Heartbeat(){ if (GenHP() || GenMP()) { NotifyClientHPMP(); }}如果GenHP回血了,就返回true,否则false;不一定每次调用GenHP都会回血,取决于是否达到3秒间隔。如果GenMP回蓝了,就返回true,否则false;不一定每次调用GenMP都会回血,取决于是否达到5秒间隔。实际运行发现回血回蓝逻辑不对,Word麻,原来是操作符短路了,如果GenHP()返回true了,那GenMP()就不会被调用,就有可能失去回蓝的机会。你需要修改程序如下:void GamePlayer::Heartbeat(){ bool hp = GenHP(); bool mp = GenMP(); if (hp || mp) { NotifyClientHPMP(); }}逻辑与(&&)跟逻辑或(||)有同样的问题, if (a && b) 如果a的表达式求值为false,b表达式也不会被计算。有时候,我们会写出 if (ptr != nullptr && ptr->Do())这样的代码,这正是利用了操作符短路的语法特征。别让循环停不下来for (unsigned int i = 5; i >=0; --i){ //...}程序跑到这,WTF?根本停不下来啊?问题很简单,unsigned永远>=0,是不是心中一万只马奔腾?解决这个问题很简单,但是有时候这一类的错误却没这么明显,你需要罩子放亮点。内存拷贝小心内存越界memcpy,memset有很强的限制,仅能用于POD结构,不能作用于stl容器或者带有虚函数的类。带虚函数的类对象会有一个虚函数表的指针,memcpy将破坏该指针指向。对非POD执行memset/memcpy,免费送你四个字:自求多福注意内存重叠内存拷贝的时候,如果src和dst有重叠,需要用memmov替代memcpy。理解user stack空间很有限不能在栈上定义过大的临时对象。一般而言,用户栈只有几兆(典型大小是4M,8M),所以栈上创建的对象不能太大。用sprintf格式化字符串的时候,类型和符号要严格匹配因为sprintf的函数实现里是按格式化串从栈上取参数,任何不一致,都有可能引起不可预知的错误; /usr/include/inttypes.h里定义了跨平台的格式化符号,比如PRId64用于格式化int64_t用c标准库的安全版本(带n标识)替换非安全版本比如用strncpy替代strcpy,用snprintf替代sprintf,用strncat代替strcat,用strncmp代替strcmp,memcpy(dst, src, n)要确保[dst,dst+n]和[src, src+n]都有有效的虚拟内存地址空间。多线程环境下,要用系统调用或者库函数的安全版本代替非安全版本(_r版本),谨记strtok,gmtime等标准c函数都不是线程安全的。STL容器的遍历删除要小心迭代器失效vector,list,map,set等各有不同的写法:int main(int argc, char *argv[]){ //vector遍历删除 std::vector v(8); std::generate(v.begin(), v.end(), std::rand); std::cout << "after vector generate...\n"; std::copy(v.begin(), v.end(), std::ostream_iterator(std::cout, "\n")); for (auto x = v.begin(); x != v.end(); ) { if (*x % 2) x = v.erase(x); else ++x; } std::cout << "after vector erase...\n"; std::copy(v.begin(), v.end(), std::ostream_iterator(std::cout, "\n")); //map遍历删除 std::map m = {{1,2}, {8,4}, {5,6}, {6,7}}; for (auto x = m.begin(); x != m.end(); ) { if (x->first % 2) m.erase(x++); else ++x; } return 0;}有时候遍历删除的逻辑不是这么明显,可能循环里调了另一个函数,而该函数在某种特定的情况下才会删除当前元素,这样的话,就是很长一段时间,程序都运行得好好的,而当你正跟别人谈笑风生的时候,忽然crash,这就尴尬了。圣斗士星矢项目曾经遭遇过这个问题,基本规律是一个礼拜game server crash一次,折磨团队将近一个月。比较low的处理方式可以把待删元素放到另一个容器WaitEraseContainer里保存下来,再走一趟单独的循环,删除待删元素。当然,我们推荐在遍历的同时删除,因为这样效率更高,也显得行家里手。三、性能空间换取时间通过空间换取时间是提高性能的惯用法,bitmap,int map[]这些惯用法要了然于胸。减少拷贝 & COW了解Copy On Write。只要可能就应该减少拷贝,比如通过共享,比如通过引用指针的形式传递参数和返回值。延迟计算和预计算比如游戏服务器端玩家的战力,由属性a,b决定,也就是说属性a,b任何一个变化,都需要重算战力;但如果ModifyPropertyA(),ModifyPropertyB()之后,都重算战力却并非真正必要,因为修改属性A之后有可能马上修改B,两次重算战力,显然第一次重算的结果会很快被第二次的重算覆盖。而且很多情况下,我们可能需要在心跳里,把最新的战力值推送给客户端,这样的话,ModifyPropertyA(),ModifyPropertyB()里,我们其实只需要把战力置脏,延迟计算,这样就能避免不必要的计算。在GetFightValue()里判断FightValueDirtyFlag,如果脏,则重算,清脏标记;如果不脏,直接返回之前计算的结果。预计算的思想类似。分散计算分散计算是把任务分散,打碎,避免一次大计算量,卡住程序。哈希减少字符串比较,构建hash,可能会多费一点存储空间,但收益可观,信我。日志节制日志的开销不容忽视,要分级,可以把日志作为debug手段,但要release干净。编译器为什么不给局部变量和成员变量做默认初始化因为效率,C++被设计为系统级的编程语言,效率是优先考虑的方向,c++秉持的一个设计哲学是“不为不必要的操作付出任何额外的代价”。所以它有别于java,不给成员变量和局部变量做默认初始化,如果需要赋初值,那就由程序员自己去保证。结论:从安全的角度出发,不应使用未初始化的变量,定义变量的时候赋初值是一个好的习惯,很多错误皆因未正确初始化而起,C++11支持成员变量定义的时候直接初始化,成员变量尽量在成员初始化列表里初始化,且要按定义的顺序初始化。理解函数调用的性能开销(栈帧建立和销毁,参数传递,控制转移),性能敏感函数考虑inlineX86_64体系结构因为通用寄存器数目增加到16个,所以64位系统下参数数目不多的函数调用,将会由寄存器传递代替压栈方式传递参数,但栈帧建立、撤销和控制转移依然会对性能有所影响。递归的优点、缺点虽然递归函数能简化程序编写,但也常常带来运行速度变慢的问题,所以需要预估好递归深度,优先考虑非递归实现版本。递归函数要有退出条件且不能递归过深,不然有爆栈危险。四、数据结构和容器了解std::vector的方方面面和底层实现vector是动态扩容的,2的次方往上翻,为了确保数据保存在连续空间,每次扩充,会将原member悉数拷贝到新的内存块; 不要保存vector内对象的指针,扩容会导致其失效 ;可以通过保存其下标index替代。运行过程中需要动态增删的vector,不宜存放大的对象本身 ,因为扩容会导致所有成员拷贝构造,消耗较大,可以通过保存对象指针替代。resize()是重置大小;reserve()是预留空间,并未改变size(),可避免多次扩容; clear()并不会导致空间收缩 ,如果需要释放空间,可以跟空的vector交换,std::vector .swap(v),c++11里shrink_to_fit()也能收缩内存。理解at()和operator[]的区别 :at()会做下标越界检查,operator[]提供数组索引级的访问,在release版本下不会检查下标,VC会在Debug版本会检查;c++标准规定:operator[]不提供下标安全性检查。C++标准规定了std::vector的底层用数组实现,认清这一点并利用这一点。常用数据结构数组:内存连续,随机访问,性能高,局部性好,不支持动态扩展,最常用。链表:动态伸缩,脱离插入极快,特别是带前后驱指针,内存通常不连续(当然可以通过从固定内存池分配规避),不支持随机访问。查找:3种:bst,hashtable,基于有序数组的bsearch。二叉搜索树(RBTree),这个从begin到end有序,最坏查找速度logN,坏处内存不连续,节点有额外空间浪费;hashtable,好的hash函数不好选,搜索最坏退化成链表,难以估计捅数量,开大了浪费内存,扩容会卡一下,无序;基于有序数组的bsearch,局部性好,insert/delete慢。五、最佳实践对于在启动时加载好,运行中不变化的查询结构,可以考虑用sorted array替代map,hash表等因为有序数组支持二分查找,效率跟map差不多。对于只需要在程序启动的时候构建(排序)一次的查询结构,有序数组相比map和hash可能有更好的内存命中性(局部命中性)。运行过程中,稳定的查询结构(比如配置表,需要根据id查找配置表项,运行过程中不增删),有序数组是个不错的选择;如果不稳定,则有序数组的插入删除效率比map,hashtable差,所以选用有序数组需要注意适用场合。std::map or std::unorder_map?想清楚他们的利弊,map是用红黑树做的,unorder_map底层是hash表做的,hash表相对于红黑树有更高的查找性能。hash表的效率取决于hash算法和冲突解决方法(一般是拉链法,hash桶),以及数据分布,如果负载因子高,就会降低命中率,为了提高命中率,就需要扩容,重新hash,而重新hash是很慢的,相当于卡一下。而红黑树有更好的平均复杂度,所以如果数据量不是特别大,map是胜任的。积极的使用const理解const不仅仅是一种语法层面的保护机制,也会影响程序的编译和运行。const常量会被编码到机器指令。理解四种转型的含义和区别避免用错,尽量少用向下转型(可以通过设计加以改进)static_cast, dynamic_cast,const_cast,reinterpret_cast,傻傻分不清?C++砖家说:一句话,尽量少用转型,强制类型转换是C Style,如果你的C++代码需要类型强转,你需要去考虑是否设计有问题。理解字节对齐字节对齐能让存储器访问速度更快。字节对齐跟cpu架构相关,有些cpu访问特定类型的数据必须在一定地址对齐的储存器位置,否则会触发异常。字节对齐的另一个影响是调整结构体成员变量的定义顺序,有可能减少结构体大小,这在某些情况下,能节省内存。牢记3 rules和5 rules,当然C++11又多了&&的copy ctor和op=版本只在需要接管的时候才自定义operator=和copy constructor,如果编译器提供的默认版本工作的很好,不要去自找麻烦,自定义的版本勿忘拷贝每一个成分,如果要接管就要处理好。组合优先于继承,继承是一种最强的类间关系典型的适配器模式有类适配器和对象适配器,一般而言,建议用对象适配的方式,而非用基于继承的类适配方式。减少依赖,注意隔离最大限度的减少文件间的依赖关系,用前向声明拆解相互依赖。了解pimpl技术。头文件要自给自足,不要图省事all.h,不要包含不必要的头文件,也不要把该包含的头文件推给user去包含,一句话,头文件包含要不多不少刚刚好。严格配对打开的句柄要关闭,加锁/解锁,new/delete,new[]/delete[],malloc/free要配对,可以使用RAII技术防止资源泄露,编写符合规范的代码Valgrind对程序的内存使用方式有期望,需要干净的释放,所以规范编程才能写出valgrind干净的代码,不然再好的工具碰到不按规划写的代码也是武功尽废啊。理解多继承潜在的问题,慎用多继承多继承会存在菱形继承的问题,多个基类有相同成员变量会有问题,需要谨慎对待。有多态用法抽象基类的析构函数要加virtual关键字主要是为了基类的析构函数能得到正确的调用。virtual dtor跟普通虚函数一样,基类指针指向子类对象的时候,delete ptr,根据虚函数特征,如果析构函数是普通函数,那么就调用ptr显式(基类)类型的析构函数;如果析构函数是virtual,则会调用子类的析构函数,然后再调用基类析构函数。避免在构造函数和析构函数里调用虚函数构造函数里,对象并没有完全构建好,此时调用虚函数不一定能正确绑定,析构亦如此。从输入流获取数据,要做好数据不够的处理,要加try catch;没有被吞咽的exception,会被传播从网络数据流读取数据,从数据库恢复数据都需要注意这个问题。协议尽量不要传float,如果传float要了解NaN的概念,要做好检查,避免恶意传播可以考虑用整数替代浮点,比如万分之五(5%%),就保存5。定义宏要遵循常规要对每个变量加括弧,有时候需要加do {} while(0)或者{},以便能将一条宏当成一个语句。要理解宏在预处理阶段被替换,不用的时候要#undef,要防止污染别人的代码。了解智能指针和指针的误用理解基于引用计数法的智能指针实现方式,了解所有权转移的概念,理解shared_ptr和unique_ptr的区别和适用场景考虑用std::shared_ptr管理动态分配的对象。指针能带来弹性,但不要误用,它的弹性指一方面它能在运行时改变指向,可以用来做多态,另一方面对于不能固定大小的数组可以动态伸缩,但很多时候,我们对固定大小的array,也在init里new/malloc出来,其实没必要,而且会多占用sizeof(void*)字节,而且增加一层间接访问。size_t到底是个什么?我该用有符号还是无符号整数?size_t类型是被设计来保存系统存储器上能保存的对象的最大个数。32位系统,一个对象最小的单位是一个字节,那2的32次方内存,最多能保存的对象数目就是4G/1字节,正好一个unsigned int能保存下来(typedef unsigned int size_t)。同样,64位系统,unsigned long是8字节,所以size_t就是unsigned long的类型别名。对于像索引,位置这样的变量,是用有符号还是无符号呢?像money这样的属性呢?一句话:要讲道理,用最自然,最顺理成章的类型。比如索引不可能为负用size_t,账户可能欠钱,则money用int。比如:template <class T> class vector{ T& operator(size_t index) {}};标准库给出了最好的示范,因为如果是有符号的话,你需要这样判断if (index < 0 || index >= max_num) throw out_of_bound();而如果是无符号整数,你只需要判断 if (index >= max_num),你认可吗?整型一般用int,long就很好,用short,char需要很谨慎,要防止溢出整型包括int,short,long,long long和char,没错,char也是整型,float是实型。绝大多数情况下,用int,long就很好,long一般等于机器字长,能直接放到寄存器,硬件处理起来速度也通常更快。很多时候,我们希望用short,char达到减少结构体大小的目的。但是由于字节对齐的原因,可能并不能真正减少大小,而且1,2个字节的整型位数太少,一不小心就溢出了,需要特别注意。所以,除非在db、网络这些对存储大小非常敏感的场合,我们才需要考虑是否以short,char替代int,long。其他情况下,就相当于为省电而不开楼道的灯,省不了多少钱却冒着摔断腿的危险。局部变量更没有必要用(unsigned) short,char等,栈是自动伸缩的,它既不节省空间,还危险,还慢。六、扩展了解c++高阶特性模板和泛型编程,union,bitfield,指向成员的指针,placement new,显式析构,异常机制,nested class,local class,namespace,多继承、虚继承,volatile,extern "C"等有些高级特性只有在特定情况下才会被用到,但技多不压身,平时还是需要积累和了解,这样在需求出现时,才能从自己的知识库里拿出工具来对付它。了解C++新标准关注新技术,c++11/14/17、lambda,右值引用,move语义,多线程库等c++98/03标准到c++11标准的推出历经13年,13年来程序设计语言的思想得到了很大的发展,c++11新标准吸收了很多其他语言的新特性,虽然c++11新标准主要是靠引入新的库来支持新特征,核心语言的变化较少,但新标准还是引入了move语义等核心语法层面的修改,每个CPPer都应该了解新标准。OOD设计原则并不是胡扯设计模式六大原则(1):单一职责原则设计模式六大原则(2):里氏替换原则设计模式六大原则(3):依赖倒置原则设计模式六大原则(4):接口隔离原则设计模式六大原则(5):迪米特法则设计模式六大原则(6):开闭原则熟悉常用设计模式,活学活用,不生搬硬套神化设计模式和反设计模式,都不是科学的态度,设计模式是软件设计的经验总结,有一定的价值;GOF书上对每一个设计模式,都用专门的段落讲它的应用场景和适用性,限制和缺陷,在正确评估得失的情况下,是鼓励使用的,但显然,你首先需要准确get到她。
-
【问题描述与分析】模型转换过程中遇到如下报错:主要原因是系统默认安装的c++库libstdc++.so.6的版本过低,导致多个GLIB不满足(下图是默认安装的libstdc++.so.6包含的GLIB,可以看到,GLIBCXX只包含到3.4.20)【解决方法】网上下载最新的ibstdc++.so.6版本libstdc++.so.6.0.26(文件已经上传了,可直接下载),包含了上述所缺的GLIB,并copy到/usr/lib64下,重新创建软连接:ln -s libstdc++.so.6.0.26 libstdc++.so.6再次执行模型转换命令,问题解决。
上滑加载中
推荐直播
-
GaussDB管理平台TPOPS,DBA高效运维的一站式解决方案
2024/12/24 周二 16:30-18:00
Leo 华为云数据库DTSE技术布道师
数据库的复杂运维,是否让你感到头疼不已?今天,华为云GaussDB管理平台将彻底来改观!本期直播,我们将深入探索GaussDB管理平台的TPOPS功能,带你感受一键式部署安装的便捷,和智能化运维管理的高效,让复杂的运维、管理变得简单,让简单变得可靠。
回顾中 -
DTT年度收官盛典:华为开发者空间大咖汇,共探云端开发创新
2025/01/08 周三 16:30-18:00
Yawei 华为云开发工具和效率首席专家 Edwin 华为开发者空间产品总监
数字化转型进程持续加速,驱动着技术革新发展,华为开发者空间如何巧妙整合鸿蒙、昇腾、鲲鹏等核心资源,打破平台间的壁垒,实现跨平台协同?在科技迅猛发展的今天,开发者们如何迅速把握机遇,实现高效、创新的技术突破?DTT 年度收官盛典,将与大家共同探索华为开发者空间的创新奥秘。
回顾中
热门标签