-
c++相对于python的话应该是要快的,可是我用python实现出来的前处理比用c++实现的快。主要是有将python的切片操作用c++实现了一下,然后就慢了很多。
-
请问是否有基于ACL推理架构的使用ModelArts训练的yolov3_resnet18目标检测C++开发案例可供参考?
-
【功能模块】LiteOS在linux(debian)系统下编译【操作步骤&问题现象】1、从gitee中clone master 无任何修改,默认target是 Cloud_STM32F429IGTx_FIRE2、直接运行make尝试过在menuconfig中去除c++支持,编译后会出现其他错误【日志信息】(可选,上传日志内容或者附件)thy1037@debian:~/program/liteos/LiteOS$ make/home/ehe/program/liteos/LiteOS/targets/bsp/common/los_config.cmake[1]: Entering directory '/home/ehe/program/liteos/LiteOS/arch/arm/cortex_m'make[1]: Leaving directory '/home/ehe/program/liteos/LiteOS/arch/arm/cortex_m'make[1]: Entering directory '/home/ehe/program/liteos/LiteOS/targets/bsp'make[1]: Leaving directory '/home/ehe/program/liteos/LiteOS/targets/bsp'make[1]: Entering directory '/home/ehe/program/liteos/LiteOS/targets/Cloud_STM32F429IGTx_FIRE'make[1]: Leaving directory '/home/ehe/program/liteos/LiteOS/targets/Cloud_STM32F429IGTx_FIRE'make[1]: Entering directory '/home/ehe/program/liteos/LiteOS/kernel'make[2]: Entering directory '/home/ehe/program/liteos/LiteOS/kernel/base'make[2]: Leaving directory '/home/ehe/program/liteos/LiteOS/kernel/base'make[2]: Entering directory '/home/ehe/program/liteos/LiteOS/kernel/extended'make[3]: Entering directory '/home/ehe/program/liteos/LiteOS/kernel/extended/cpup'make[3]: Leaving directory '/home/ehe/program/liteos/LiteOS/kernel/extended/cpup'make[3]: Entering directory '/home/ehe/program/liteos/LiteOS/kernel/extended/cppsupport'make[3]: Leaving directory '/home/ehe/program/liteos/LiteOS/kernel/extended/cppsupport'make[2]: Leaving directory '/home/ehe/program/liteos/LiteOS/kernel/extended'make[1]: Leaving directory '/home/ehe/program/liteos/LiteOS/kernel'make[1]: Entering directory '/home/ehe/program/liteos/LiteOS/compat'make[2]: Entering directory '/home/ehe/program/liteos/LiteOS/compat/cmsis'make[2]: Leaving directory '/home/ehe/program/liteos/LiteOS/compat/cmsis'make[1]: Leaving directory '/home/ehe/program/liteos/LiteOS/compat'make[1]: Entering directory '/home/ehe/program/liteos/LiteOS/lib'make[2]: Entering directory '/home/ehe/program/liteos/LiteOS/lib/libsec'make[2]: Leaving directory '/home/ehe/program/liteos/LiteOS/lib/libsec'make[2]: Entering directory '/home/ehe/program/liteos/LiteOS/lib/libc'make[2]: Leaving directory '/home/ehe/program/liteos/LiteOS/lib/libc'make[2]: Entering directory '/home/ehe/program/liteos/LiteOS/lib/huawei_libc'make[2]: Leaving directory '/home/ehe/program/liteos/LiteOS/lib/huawei_libc'make[2]: Entering directory '/home/ehe/program/liteos/LiteOS/lib/libc/src/math'make[2]: Leaving directory '/home/ehe/program/liteos/LiteOS/lib/libc/src/math'make[2]: Entering directory '/home/ehe/program/liteos/LiteOS/lib/zlib'make[2]: Leaving directory '/home/ehe/program/liteos/LiteOS/lib/zlib'make[1]: Leaving directory '/home/ehe/program/liteos/LiteOS/lib'make[1]: Entering directory '/home/ehe/program/liteos/LiteOS/osdepends'make[1]: Leaving directory '/home/ehe/program/liteos/LiteOS/osdepends'make[1]: Entering directory '/home/ehe/program/liteos/LiteOS/components'make[2]: Entering directory '/home/ehe/program/liteos/LiteOS/components/connectivity'make[3]: Entering directory '/home/ehe/program/liteos/LiteOS/components/connectivity/nb_iot'make[3]: Leaving directory '/home/ehe/program/liteos/LiteOS/components/connectivity/nb_iot'make[2]: Leaving directory '/home/ehe/program/liteos/LiteOS/components/connectivity'make[2]: Entering directory '/home/ehe/program/liteos/LiteOS/components/lib'make[3]: Entering directory '/home/ehe/program/liteos/LiteOS/components/lib/cjson'make[3]: Leaving directory '/home/ehe/program/liteos/LiteOS/components/lib/cjson'make[2]: Leaving directory '/home/ehe/program/liteos/LiteOS/components/lib'make[2]: Entering directory '/home/ehe/program/liteos/LiteOS/components/log'make[2]: Leaving directory '/home/ehe/program/liteos/LiteOS/components/log'make[2]: Entering directory '/home/ehe/program/liteos/LiteOS/components/net'make[3]: Entering directory '/home/ehe/program/liteos/LiteOS/components/net/sal'make[3]: Leaving directory '/home/ehe/program/liteos/LiteOS/components/net/sal'make[3]: Entering directory '/home/ehe/program/liteos/LiteOS/components/net/at_device'make[3]: Leaving directory '/home/ehe/program/liteos/LiteOS/components/net/at_device'make[2]: Leaving directory '/home/ehe/program/liteos/LiteOS/components/net'make[1]: Leaving directory '/home/ehe/program/liteos/LiteOS/components'make[1]: Entering directory '/home/ehe/program/liteos/LiteOS/demos'make[1]: Leaving directory '/home/ehe/program/liteos/LiteOS/demos'make[1]: Entering directory '/home/ehe/program/liteos/LiteOS/shell'make[1]: Leaving directory '/home/ehe/program/liteos/LiteOS/shell'arm-none-eabi-ld -nostartfiles -static --gc-sections -L/usr/lib/gcc/arm-none-eabi/7.3.1//thumb/v7e-m+fp/hard -L/home/ehe/program/liteos/LiteOS/tools/scripts/ld -L/home/ehe/program/liteos/LiteOS/targets/Cloud_STM32F429IGTx_FIRE -L/home/ehe/program/liteos/LiteOS/out/Cloud_STM32F429IGTx_FIRE/lib -L/home/ehe/program/liteos/LiteOS/out/Cloud_STM32F429IGTx_FIRE/lib/obj -L/home/ehe/program/liteos/LiteOS/tools/build -L/usr/lib/gcc/arm-none-eabi/7.3.1/ -L/usr/arm-none-eabi/lib/ -T/home/ehe/program/liteos/LiteOS/targets/Cloud_STM32F429IGTx_FIRE/liteos.ld -utask_shellcmd -ucpup_shellcmd -uhelp_shellcmd -ufree_shellcmd -usem_shellcmd -umutex_shellcmd -uqueue_shellcmd -usysteminfo_shellcmd -uswtmr_shellcmd -uhwi_shellcmd -ufindsym_shellcmd -uzbar_scan_shellcmd -udmesg_shellcmd -udate_shellcmd -ureadExcInfo_shellcmd -uwatch_shellcmd -udeadlock_shellcmd -uarp_shellcmd -utelnet_shellcmd -ucd_shellcmd -uformat_shellcmd -upartition_shellcmd -uwriteproc_shellcmd -upartinfo_shellcmd -uumount_shellcmd -umount_shellcmd -uvirstatfs_shellcmd -ulsfd_shellcmd -ufatfsck_shellcmd -uiperf_shellcmd -ulwip_dump_shellcmd -ureset_shellcmd -ustartap_shellcmd -uhimd_shellcmd -uhiddrs_shellcmd -unand_bad_shellcmd -ui2c_read_shellcmd -ussp_read_shellcmd -uuart_config_shellcmd -uusb_debug_shellcmd -uramfs_fsmap -unfs_fsmap -ufat_fsmap -uyaffs_fsmap -uromfs_fsmap -ug_fsmap -ui2c_init -ugpio_init -uregulator_init -umtd_init_list -uhispi_init -uhifmc100_init -uhisfc350_init -unand_hifmc100_init -uhifmc100_parallel_init -usd_mci_init -uhi_mci_init -upl011_init -uhinfc620_init -uhisnfc100_init -uregulator_machine_init -uhisimeidia_regulator_init -ucpufreq_init -uhisilicon_cpufreq_init -ucpufreq_machine_init -udevfreq_init -umedia_devfreq_init -udevfreq_machine_init -uhieth_machine_init -uhigmac_machine_init -ujffs_fsmap -uprocfs_fsmap -ug_fsmap_wow -ui2c_init -ugpio_init -uregulator_init -umtd_init_list -uhispi_init -uhifmc100_init -uhisfc350_init -unand_hifmc100_init -uhifmc100_parallel_init -usd_mci_init -uhi_mci_init -upl011_init -uhinfc620_init -uhisnfc100_init -uregulator_machine_init -uhisimeidia_regulator_init -ucpufreq_init -uhisilicon_cpufreq_init -ucpufreq_machine_init -udevfreq_init -umedia_devfreq_init -udevfreq_machine_init -uhieth_machine_init -uhigmac_machine_init -ug_fsmap_scatter -ui2c_init -ugpio_init -uregulator_init -umtd_init_list -uhispi_init -uhifmc100_init -uhisfc350_init -unand_hifmc100_init -uhifmc100_parallel_init -usd_mci_init -uhi_mci_init -upl011_init -uhinfc620_init -uhisnfc100_init -uregulator_machine_init -uhisimeidia_regulator_init -ucpufreq_init -uhisilicon_cpufreq_init -ucpufreq_machine_init -udevfreq_init -umedia_devfreq_init -udevfreq_machine_init -uhieth_machine_init -uhigmac_machine_init -Map=/home/ehe/program/liteos/LiteOS/out/Cloud_STM32F429IGTx_FIRE/Huawei_LiteOS.map -o /home/ehe/program/liteos/LiteOS/out/Cloud_STM32F429IGTx_FIRE/Huawei_LiteOS.elf --start-group -lgcc -lcortex-m4 -lsupc++ -lstdc++ -lcortex-m4 -lbsp -lCloud_STM32F429IGTx_FIRE -lbase -lcpup -lcppsupport -lcmsis -lsec -lc -lposix -lm -lz -losdepends -lnb_iot -lcjson -llog -lsal -lat_device -lCloud_STM32F429IGTx_FIRE -lshell --end-grouparm-none-eabi-ld: cannot find -lsupc++arm-none-eabi-ld: cannot find -lstdc++make: *** [Makefile:74: Huawei_LiteOS] Error 1
-
【C++ API 】MindSpore有提供 C++ API的应用示例吗?
-
截取子数组std::array并未提供输入一个指定区间来建立新容器的构造函数,但是借助上面的数组生成器,我们可以写个辅助函数来实现子数组生成操作(这里再次用上了lambda函数作为生成算法)。template<size_t N, typename T>constexpr auto SubArray(T&& t, size_t base) noexcept{ return GenerateArray<N>([base, t = forward<T>(t)](size_t i) { return t[base + i]; });} template<size_t N, typename T, size_t M>constexpr auto SubArray(const T (&t)[M], size_t base) noexcept{ return GenerateArray<N>([base, &t](size_t i) { return t[base + i]; });} int main(){ // 以std::initializer_list字面量为原始数据 constexpr auto x = SubArray<3>({1, 2, 3, 4, 5, 6}, 2); // 下标为2开始,取3个元素 static_assert(Equals(x, {3, 4, 5})); // 以std::array为原始数据 constexpr auto x1 = SubArray<2>(x, 1); // 下标为1开始,取2个元素 static_assert(Equals(x1, {4, 5})); // 以原生数组为原始数据 constexpr uint8_t a[] = {9, 8, 7, 6, 5}; constexpr auto y = SubArray<2>(a, 3); static_assert(Equals(y, {6, 5})); // 下标为3开始,取2个元素 // 以字符串为原始数据,注意生成的数组不会自动加上'\0' constexpr const char* str = "Hello world!"; constexpr auto z = SubArray<5>(str, 6); static_assert(Equals(z, {'w', 'o', 'r', 'l', 'd'})); // 下标为6开始,取5个元素 // 以std::vector为原始数据,非编译期计算 vector<int32_t> v{10, 11, 12, 13, 14}; size_t n = 2; auto d = SubArray<3>(v, n); // 运行时生成数组 assert(Equals(d, {12, 13, 14})); // 注意不能用static_assert,不是编译期常量 return 0;}使用SubArray时,模板参数N是要截取的子数组大小,入参t是任意能支持下标操作的类型,入参base是截取元素的起始位置。由于std::array的大小在编译期是确定的,因此N必须是编译期常量,但参数base可以是运行时变量。当所有入参都是编译期常量时,生成的子数组也是编译期常量。SubArray提供了两个版本,目的也是为了让std::initializer_list字面量可以作为参数传入。拼接多个数组采用类似的方式可以做多个数组的拼接,这里同样用了lambda作为生成函数。template<typename T>constexpr auto TotalLength(const T& arr) noexcept{ return size(arr);} template<typename P, typename... T>constexpr auto TotalLength(const P& p, const T&... arr) noexcept{ return size(p) + TotalLength(arr...);} template<typename T>constexpr auto PickElement(size_t i, const T& arr) noexcept{ return arr[i];} template<typename P, typename... T>constexpr auto PickElement(size_t i, const P& p, const T&... arr) noexcept{ if (i < size(p)) { return p[i]; } return PickElement(i - size(p), arr...);} template<typename... T>constexpr auto ConcatArrays(const T&... arr) noexcept{ return GenerateArray<TotalLength(arr...)>([&arr...](size_t i) { return PickElement(i, arr...); });} int main(){ constexpr int32_t a[] = {1, 2, 3}; // 原生数组 constexpr auto b = to_typed_array<int32_t>({4, 5, 6}); // std::array constexpr auto c = DeclareArray<int32_t, 7, 8>(); // std::array constexpr auto x = ConcatArrays(a, b, c); // 把3个数组拼接在一起 static_assert(Equals(x, {1, 2, 3, 4, 5, 6, 7, 8})); return 0;}和之前一样,ConcatArrays使用了模板参数来同时兼容原生数组和std::array,它甚至可以接受任何编译期确定长度的自定义类型参与拼接。ConcatArrays函数因为可变参数的语法限制,没有再对std::initializer_list字面量进行适配,这导致std::initializer_list字面量不能再直接作为参数:constexpr auto x = ConcatArrays(a, {4, 5, 6}); // 编译错误但是我们有办法规避这个问题:利用前面介绍过的工具把std::initializer_list先转成std::array就可以了:constexpr auto x = ConcatArrays(a, to_array({4, 5, 6})); // OK编译期拼接字符串std::array适合用来表示字符串么?回答这个问题前,我们先看看原生数组是否适合表示字符串:char str[] = "abc"; // str数组大小为4,包括结尾的'\0'上面是很常见的写法。由于数组名可退化为指针,str可用于各种需要字符串的场合,如传给cout打印输出。std::array作为对原生数组的替代,自然也适合用来表示字符串。有人可能会觉得std::array没法直接作为字符串类型使用,不太方便。但实际上只要调用data方法,std::array就会返回能作为字符串使用的指针:constexpr auto str = to_array("abc"); // to_array可以将字符串转换为std::arraystatic_assert(str.size() == 4);static_assert(Equals(str, "abc")); // Equals也可以接受字符串字面量cout << str.data(); // 打印字符串内容由于字符串字面量是char[]类型,因此前面所编写的工具函数,都可以将字符串作为输入参数。上面的Equals只是其中一个例子。那之前写的数组拼接函数ConcatArrays能用于拼接字符串么?能,但结果和我们想的有差异:constexpr auto str = ConcatArrays("abc", "def");static_assert(str.size() == 8); // 长度不是7?static_assert(Equals(str, {'a', 'b', 'c', '\0', 'd', 'e', 'f', '\0'}));因为每个字符串结尾都有'\0'结束符,用数组拼接方法把它们拼到一起时,中间的'\0'没有被去掉,导致结果字符串被切割为了多个C字符串。这个问题解决起来也很容易,只要在拼接数组时把所有数组的最后一个元素('\0')去掉,并且在返回数组的末尾加上'\0'就可以了。下面的代码实现了字符串拼接功能,非类型参数E是字符串的结束符,通常为'\0',但是也允许定制。我们甚至可以利用它来拼接结束符为其他值的对象,比如消息、报文等。// 最后一个字符,放入结束符template<auto E>constexpr auto PickChar(size_t i){ return E;} template<auto E, typename P, typename... T>constexpr auto PickChar(size_t i, const P& p, const T&... arr){ if (i < (size(p) - 1)) { if (p[i] == E) { // 结束符不允许出现在字符串中间 throw "terminator in the middle"; } return p[i]; } if (p[size(p) - 1] != E) { // 结束符必须是最后一个字符 throw "terminator not at end"; } return PickChar<E>(i - (size(p) - 1), arr...);} template<typename... T, auto E = '\0'>constexpr auto ConcatStrings(const T&... str){ return GenerateArray<TotalLength(str...) - sizeof...(T) + 1>([&str...](size_t i) { return PickChar<E>(i, str...); });} int main(){ constexpr char a[] = "I "; // 原生数组形式的字符串 constexpr auto b = to_array("love "); // std::array形式的字符串 constexpr auto str = ConcatStrings(a, b, "C++"); // 拼接 数组 + std::array + 字符串字面量 static_assert(Equals(str, "I love C++")); return 0;}这段代码中用了两个throw,这是为了校验输入的参数是否都为合法的字符串,即:字符串长度=容器长度-1。如果不符合该条件,会导致拼接结果的长度计算错误。当编译期的计算抛出异常时,只会出现编译错误,因此只要不在运行时调用ConcatStrings,这两个throw语句不会有更多影响。但因为这个校验的存在,强烈不建议在运行期调用ConcatStrings做拼接,何况运行期也没必要用这种方法——std::string的加法操作它不香么?有人会想:能否在编译期计算字符串的实际长度,而不是用容器的长度呢?这个方法看似可行,定义一个编译期计算字符串长度的函数确实很容易:template<typename T, auto E = '\0'>constexpr size_t StrLen(const T& str) noexcept{ size_t i = 0; while (str[i] != E) { ++i; } return i;} constexpr const char* g_str = "abc"; int main(){ // 利用StrLen把一个字符串按实际长度转成std::array constexpr auto str = SubArray<StrLen(g_str) + 1>(g_str, 0); static_assert(Equals(str, "abc")); return 0;}但是,一旦你试图把StrLen放到ConcatStrings的内部去声明数组长度,就会产生问题:C++的constexpr机制要求只有在能看到输入参数的constexpr属性的地方,才允许StrLen的返回结果确定为constexpr。而在函数内部时,看到的参数类型并不是constexpr。当然我们可以变通一下,做出一些有趣的工具,比如使用万恶的宏:// 把一个字符串按实际长度转成std::array#define StrToArray(x) SubArray<StrLen(x) + 1>(x, 0) constexpr const char* g_str = "abc"; int main(){ // 使用宏,可以让constexpr指针类型也参与编译期字符串的拼接 constexpr auto str = ConcatStrings(StrToArray(g_str), "def"); static_assert(Equals(str, "abcdef")); return 0;}使用宏以后,ConcatStrings连编译期不确定大小的指针类型都可以间接作为输入了[3]。如果你狠得下心使用变参宏,甚至可以定义出按实际字符串长度计算结果数组长度的更通用拼接函数。但我严重怀疑这种需求的必要性——毕竟我们只是做编译期的拼接,而编译期的字符串不应该会有结束符位置不在末尾的场景。看到这里的人,或多或少该佩服一下std::array的强大了。上面这些编译期操作,用原生数组很难完成吧?展望C++20——打破更多的枷锁我在文章中说了多少次“至少在C++20之前如此”?不记得了,但是能确定的是:C++20会带来很多美好的东西:std::array会有constexpr版本的比较运算符;函数可以用consteval限定只在编译期调用;模板非类型参数允许更多的类型;STL容器对象可以作为constexpr常量……所有这一切,都只是C++20的minor更新而已,在绝大多数的特性介绍中,它们连提都不会被提到!可想而知,用上C++20以后,编程会发生多大的变化。那时我们再来找找更多有趣的用法尾注[1] to_array定义了两个版本,分别以左值引用和右值引用作为参数类型。按照C++11的最优实践,这样的函数本应该只定义一个版本并且使用完美转发。但是to_array的场景如果用万能引用会带来一个问题:C++禁止std::initializer_list字面量{...}被推导为模板类型参数,完美转发方案会导致std::initializer_list字面量不能作为to_array的入参。在后面内容中我们会看到多次这个限制所带来的影响。[2] C++20加入了consteval修饰符,可以指定函数只允许在编译期调用。[3] 需要注意的是:constexpr用于修饰指针时,表示的是指针本身为常量(而不是其指向的对象)。和const不同,constexpr并不允许放在类型声明表达式的中间。因此如果要在编译期计算一个constexpr指针指向的字符串长度,这个字符串必须位于静态数据区里,不能位于栈或者堆上(否则其地址无法在编译期确定)。
-
编译期生成数组C++11中新增的constexpr修饰符可以在编译期完成很多计算工作。但是一般constexpr函数只能返回单个值,一旦你想用它返回一串对象的集合,就会遇到麻烦:STL容器都有动态内存申请功能,不能作为编译期常量(至少在C++20之前如此);而原生数组作为返回值会退化为指针,导致返回悬空的指针。即使是返回数组的引用也是不行的,会产生悬空的引用。constexpr int* Func() noexcept{ int a[] = {1, 2, 3, 4}; return a; // 严重错误!返回局部对象的地址}直到std::array的出现,这个问题才得到较好解决。std::array既可以作为编译期常量,又可以作为函数返回值。于是,它成为了编译期返回集合数据的首选。在上面to_array等工具函数的实现中,我们已经见过了编译期返回数组是怎么做的。这里我们再大胆一点,写一个编译期冒泡排序:template<typename T, size_t N>constexpr std::array<T, N> Sort(const std::array<T, N>& numbers) noexcept{ std::array<T, N> sorted(numbers); for (int i = 0; i < N; ++i) { for (int j = N - 1; j > i; --j) { if (sorted[j] < sorted[j - 1]) { T t = sorted[j]; sorted[j] = sorted[j - 1]; sorted[j - 1] = t; } } } return sorted;} int main(){ constexpr std::array<int, 4> before{4, 2, 3, 1}; constexpr std::array<int, 4> after = Sort(before); static_assert(after[0] == 1); static_assert(after[1] == 2); static_assert(after[2] == 3); static_assert(after[3] == 4); return 0;}因为整个排序算法都是在编译期完成,所以我们没有必要太关注冒泡排序的效率问题。当然,只要你愿意,完全可以写出一个编译期快速排序——毕竟constexpr函数也可以在运行期使用,不好说会不会有哪个憨憨在运行时调用它。 在编写constexpr函数时,有两点需要注意: 1. constexpr函数中不能调用非constexpr函数。因此在交换元素时不能用std::swap,排序也不能直接调用std::sort。 2. 传入的数组是constexpr的,因此参数类型必须加上const,也不能对数据进行就地排序,必须返回一个新的数组。 虽然限制很多,但编译期算法的好处也是巨大的:如果运算中有数组越界等未定义行为,编译将会失败。相比起运行时的测试,编译期测试constexpr函数能有效的提前拦截问题。而且只要编译通过就意味着测试通过,比起额外跑白盒测试用例方便多了。 上面的一大串static_assert语句让人看了不舒服。这么写的原因是std::array的operator==函数并非constexpr(至少在C++20前如此)。但是我们也可以自己定义一个模板函数用于判断两个数组是否相等: template<typename T, typename U, size_t M, size_t N> constexpr bool EqualsImpl(const T& lhs, const U& rhs) { static_assert(M == N); for (size_t i = 0; i < M; ++i) { if (lhs[i] != rhs[i]) { return false; } } return true; } template<typename T, typename U> constexpr bool Equals(const T& lhs, const U& rhs) { return EqualsImpl<T, U, size(lhs), size(rhs)>(lhs, rhs); } template<typename T, typename U, size_t N> constexpr bool Equals(const T& lhs, const U (&rhs)[N]) { return EqualsImpl<T, const U (&)[N], size(lhs), N>(lhs, rhs); } int main() { constexpr std::array<int, 4> before{4, 2, 3, 1}; constexpr std::array<int, 4> after = Sort(before); static_assert(Equals(after, {1, 2, 3, 4})); // 比较std::array和原生数组 static_assert(!Equals(before, after)); // 比较两个std::array return 0; } 我们定义的Equals比std::array的比较运算符更强大,甚至可以在std::array和原生数组之间进行比较。 对于Equals有两点需要说明: 1. std::size是C++17提供的工具函数,对各种容器和数组都能返回其大小。当然,这里的Equals只会允许编译期确定大小的容器传入,否则触发编译失败。 2. Equals定义了两个版本,这是被C++的一个限制所逼的迫不得已:C++禁止{...}这种std::initializer_list字面量被推导为模板参数类型,因此我们必须提供一个版本声明参数类型为数组,以便{1, 2, 3, 4}这种表达式能作为参数传进去。 编译期排序是一个启发性的尝试,我们可以用类似的方法生成其他的编译期集合常量,比如指定长度的自然数序列: template<typename T, size_t N> constexpr auto NaturalNumbers() noexcept { array<T, N> arr{0}; // 显式初始化不能省 for (size_t i = 0; i < N; ++i) { arr[i] = i + 1; } return arr; } int main() { constexpr auto arr = NaturalNumbers<uint32_t, 5>(); static_assert(Equals(arr, {1, 2, 3, 4, 5})); return 0; } 这段代码的编译运行都没有问题,但它并不是推荐的做法。原因是在NaturalNumbers函数中,先定义了一个内容全0的局部数组,然后再挨个修改它的值,这样没有直接返回指定值的数组效率高。有人会想能不能把arr的初始化给去掉,但这样会导致编译错误——constexpr函数中不允许定义没有初始化的局部变量。 可能有人觉得这些计算都是编译期完成的,对运行效率没影响——但是不要忘了constexpr函数也可以在运行时调用。更好的做法可以参见前面to_array函数的实现,让数组的初始化一气呵成,省掉挨个赋值的步骤。 我们用这个新思路,写一个通用的数组生成器,它可以接受一个函数对象作为参数,通过调用这个函数对象来生成数组每个元素的值。下面的代码还演示了下如何用这个生成器在编译期生成奇数序列和斐波那契数列。 template<typename T> constexpr T OddNumber(size_t i) noexcept { return i * 2 + 1; } template<typename T> constexpr T Fibonacci(size_t i) noexcept { if (i <= 1) { return 1; } return Fibonacci<T>(i - 1) + Fibonacci<T>(i - 2); } template<typename T, size_t N, typename F, size_t... I> constexpr array<std::remove_cv_t<T>, N> GenerateArrayImpl(F f, std::index_sequence<I...>) noexcept { return { {f(I)...} }; } template<size_t N, typename F, typename T = invoke_result_t<F, size_t>> constexpr array<T, N> GenerateArray(F f) noexcept { return GenerateArrayImpl<T, N>(f, std::make_index_sequence<N>{}); } int main() { constexpr auto oddNumbers = GenerateArray<5>(OddNumber<uint8_t>); static_assert(Equals(oddNumbers, {1, 3, 5, 7, 9})); constexpr auto fiboNumbers = GenerateArray<5>(Fibonacci<uint32_t>); static_assert(Equals(fiboNumbers, {1, 1, 2, 3, 5})); // 甚至可以传入lambda来定制要生成的数字序列(限定C++17) constexpr auto specified = GenerateArray<3>([](size_t i) { return i + 10; }); static_assert(Equals(specified, {10, 11, 12})); return 0; } 最后那个传入lambda来定制数组的做法存在一个疑问:lambda是constexpr函数吗?答案为:可以是,但需要C++17支持。 GenerateArray这个数组生成器将会在后面发挥重大作用,继续往下看。
-
摘要:在这篇文章里,将从各个角度介绍下std::array的用法,希望能带来一些启发。td::array是在C++11标准中增加的STL容器,它的设计目的是提供与原生数组类似的功能与性能。也正因此,使得std::array有很多与其他容器不同的特殊之处,比如:std::array的元素是直接存放在实例内部,而不是在堆上分配空间;std::array的大小必须在编译期确定;std::array的构造函数、析构函数和赋值操作符都是编译器隐式声明的……这让很多用惯了std::vector这类容器的程序员不习惯,觉得std::array不好用。但实际上,std::array的威力很可能被低估了。在这篇文章里,我会从各个角度介绍下std::array的用法,希望能带来一些启发。本文的代码都在C++17环境下编译运行。当前主流的g++版本已经能支持C++17标准,但是很多版本(如gcc 7.3)的C++17特性不是默认打开的,需要手工添加编译选项-std=c++17。自动推导数组大小很多项目中都会有类似这样的全局数组作为配置参数:uint32_t g_cfgPara[] = {1, 2, 5, 6, 7, 9, 3, 4};当程序员想要使用std::array替换原生数组时,麻烦来了:array<uint32_t, 8> g_cfgPara = {1, 2, 5, 6, 7, 9, 3, 4}; // 注意模板参数“8”程序员不得不手工写出数组的大小,因为它是std::array的模板参数之一。如果这个数组很长,或者经常增删成员,对数组大小的维护工作恐怕不是那么愉快的。有人要抱怨了:std::array的声明用起来还没有原生数组方便,选它干啥?但是,这个抱怨只该限于C++17之前,C++17带来了类模板参数推导特性,你不再需要手工指定类模板的参数:array g_cfgPara = {1, 2, 5, 6, 7, 9, 3, 4}; // 数组大小与成员类型自动推导看起来很美好,但很快就会有人发现不对头:数组元素的类型是什么?还是std::uint32_t吗?有人开始尝试只提供元素类型参数,让编译器自动推导长度,遗憾的是,它不会奏效。array<uint32_t> g_cfgPara = {1, 2, 5, 6, 7, 9, 3, 4}; // 编译错误好吧,暂时看起来std::array是不能像原生数组那样声明。下面我们来解决这个问题。用函数返回std::array问题的解决思路是用函数模板来替代类模板——因为C++允许函数模板的部分参数自动推导——我们可以联想到std::make_pair、std::make_tuple这类辅助函数。巧的是,C++标准真的在TS v2试验版本中推出过std::make_array,然而因为类模板参数推导的问世,这个工具函数后来被删掉了。但显然,用户的需求还是存在的。于是在C++20中,又新增了一个辅助函数std::to_array。别被C++20给吓到了,这个函数的代码其实很简单,我们可以把它拿过来定义在自己的C++17代码中[1]。template<typename R, typename P, size_t N, size_t... I>constexpr array<R, N> to_array_impl(P (&a)[N], std::index_sequence<I...>) noexcept{ return { {a[I]...} };} template<typename T, size_t N>constexpr auto to_array(T (&a)[N]) noexcept{ return to_array_impl<std::remove_cv_t<T>, T, N>(a, std::make_index_sequence<N>{});} template<typename R, typename P, size_t N, size_t... I>constexpr array<R, N> to_array_impl(P (&&a)[N], std::index_sequence<I...>) noexcept{ return { {move(a[I])...} };} template<typename T, size_t N>constexpr auto to_array(T (&&a)[N]) noexcept{ return to_array_impl<std::remove_cv_t<T>, T, N>(move(a), std::make_index_sequence<N>{});}细心的朋友会注意到,上面这个定义与C++20的推荐实现有所差异,这是有目的的。稍后我会解释这么干的原因。现在让我们尝试下用新方法解决老问题:auto g_cfgPara = to_array<int>({1, 2, 5, 6, 7, 9, 3, 4}); // 类型不是uint32_t?不对啊,为什么元素类型不是原来的std::uint32_t?这是因为模板参数推导对std::initializer_list的元素拒绝隐式转换,如果你把to_array的模板参数从int改为uint32_t,会得到如下编译错误:D:\Work\Source_Codes\MyProgram\VSCode\main.cpp:51:61: error: no matching function for call to 'to_array<uint32_t>(<brace-enclosed initializer list>)' auto g_cfgPara = to_array<uint32_t>({1, 2, 5, 6, 7, 9, 3, 4});D:\Work\Source_Codes\MyProgram\VSCode\main.cpp:34:16: note: candidate: 'template<class T, long long unsigned int N> constexpr auto to_array(T (&)[N])' constexpr auto to_array(T (&a)[N]) noexcept ^~~~~~~~D:\Work\Source_Codes\MyProgram\VSCode\main.cpp:34:16: note: template argument deduction/substitution failed:D:\Work\Source_Codes\MyProgram\VSCode\main.cpp:51:61: note: mismatched types 'unsigned int' and 'int' auto g_cfgPara = to_array<uint32_t>({1, 2, 5, 6, 7, 9, 3, 4});D:\Work\Source_Codes\MyProgram\VSCode\main.cpp:46:16: note: candidate: 'template<class T, long long unsigned int N> constexpr auto to_array(T (&&)[N])' constexpr auto to_array(T (&&a)[N]) noexcept ^~~~~~~~D:\Work\Source_Codes\MyProgram\VSCode\main.cpp:46:16: note: template argument deduction/substitution failed:D:\Work\Source_Codes\MyProgram\VSCode\main.cpp:51:61: note: mismatched types 'unsigned int' and 'int'Hoho,有点惨是不,绕了一圈回到原点,还是不能强制指定类型。这个时候,之前针对std::array做的修改派上用场了:我给to_array_impl增加了一个模板参数,让输入数组的元素和返回std::array的元素用不同的类型参数表示,这样就给类型转换带来了可能。为了实现转换到指定的类型,我们还需要添加两个工具函数:template<typename R, typename P, size_t N>constexpr auto to_typed_array(P (&a)[N]) noexcept{ return to_array_impl<R, P, N>(a, std::make_index_sequence<N>{});} template<typename R, typename P, size_t N>constexpr auto to_typed_array(P (&&a)[N]) noexcept{ return to_array_impl<R, P, N>(move(a), std::make_index_sequence<N>{});}这两个函数和to_array的区别是:它带有3个模板参数:第一个是要返回的std::array的元素类型,后两个和to_array一样。这样我们就可以通过指定第一个参数来实现定制std::array元素类型了。auto g_cfgPara = to_typed_array<uint32_t>({1, 2, 5, 6, 7, 9, 3, 4}); // 自动把元素转换成uint32_t这段代码可以编译通过和运行,但是却有类型转换的编译告警。当然,如果你胆子够大,可以在to_array_impl函数中放一个static_cast来消除告警。但是编译告警提示了我们一个不能忽视的问题:如果万一输入的数值溢出了怎么办?auto g_a = to_typed_array<uint8_t>({256, -1}); // 数字超出uint8_t范围编译器还是一样的会让你编译通过和运行,g_a中的两个元素的值将分别为0和255。如果你不明白为什么这两个值和入参不一样,你该复习下整型溢出与回绕的知识了。显然,这个方案还不完美。但我们可以继续改进。编译期字面量数值合法性校验首先能想到的做法是在to_array_impl函数中放入一个if判断之类的语句,对于超出目标数值范围的输入抛出异常或者做其他处理。这当然可行,但要注意的是这些工具函数是可以在运行期调用的,对于这种常用的基础函数来说,性能至关重要。一旦在里面加入了错误判断,意味着运行时的每一次调用性能都会下降。理想的设计是:只有在编译期生成的数组才进行校验,并且报编译错误。但运行时调用函数时不要加入任何校验。可惜的是,至少在C++20之前,没有办法指定函数只允许在编译期执行[2]。那有没有其他手段呢?熟悉C++的人知道:C++的编译期处理大多可以用模板的trick来完成——因为模板参数一定是编译期常量。因此我们可以用模板参数来完成编译期处理——只要把数组元素全部作为模板的非类型参数就可以了。当然,这里有个问题:模板的非类型参数的类型怎么确定?正好C++17提供了auto模板参数的功能,可以派上用场:template<typename T>constexpr void CheckIntRanges() noexcept {} // 用于终结递归template<typename T, auto M, auto... N>constexpr void CheckIntRanges() noexcept{// 防止无符号与有符号比较static_assert(!((std::numeric_limits<T>::min() >= 0) && (M < 0))); // 范围校验 static_assert((M >= std::numeric_limits<T>::min()) &&(M <= std::numeric_limits<T>::max()));CheckIntRanges<T, N...>();}template<typename T, auto... N>constexpr auto DeclareArray() noexcept{CheckIntRanges<T, N...>();array<T, sizeof...(N)> a{{static_cast<T>(N)...}};return a;};注意这个函数中,所有的校验都通过static_assert完成。这就保证了校验一定只会发生在编译期,不会带来任何运行时开销。DeclareArray的使用方法如下:constexpr auto a1 = DeclareArray<uint8_t, 1, 2, 3, 4, 255>(); // 声明一个std::array<uint8_t, 5>,元素分别为1, 2, 3, 4, 255static_assert(a1.size() == 5);static_assert(a1[3] == 4);auto a2 = DeclareArray<uint8_t, 1, 2, 3, -1>(); // 编译错误,-1超出uint8_t范围auto a3 = DeclareArray<uint16_t, 1, 2, 3, 65536>(); // 编译错误,65536超出uint16_t范围这里有一个误区需要说明:有些人可能会把DeclareArray声明成这样:template<typename T, T... N> // 注意N的类型为Tconstexpr auto DeclareArray() noexcept这么做的话,会发现对数值的校验总是能通过——因为模板参数在进入校验之前就已经被转换为T类型了。如果你的编译器不支持C++17的auto模板参数,那么可以通过使用std::uint64_t、std::int64_t这些“最大”的类型来间接达到目的。另一点要说明的是,C++对于非类型模板参数的允许类型存在限制,DeclareArray的方法只能用于数组元素为基本类型的场景(至少在C++20以前如此)。但是这也足够了。如果数组的元素是自定义类型,就可以通过自定义的构造函数等方法来控制类型转换。如果你看到这里觉得有点意思了,那就对了,后面还有更过瘾的。
-
首先说一下实验沙箱的优点:可以使用云服务器上的环境练习,改变了传统本地部署,减少工作量,提高了效率。本实验目标: 1、掌握代码扫描工具的使用 2、掌握crcutil迁移组件的使用 3、熟悉编译安装的过程实验步骤: 第一步:预置实验环境 第二步:使用上图里的账户名、用户名、密码复制粘贴到相应位置,点击“登录”实验账号 第三步:使用“Xfce终端”登陆云服务器,注意:登录命令:LANG=en_us.UTF-8 ssh root@124.71.129.5 第四步:代码扫描工具的使用 安装和使用代码扫描工具 第五步:crcutil组件迁移 编译脚本和源码的修改、源码修改、源码编译安装、组件测试 注意:5.2.2步骤(如下图)里,56行虽然已经有#endif,担仍需要在55行处添加#endif语句,不然后面会报错。 总结: 1、通过实验可以完全模拟真实环境,可以熟悉实验步骤,可以查找操作错误,避免真实项目中出现问题 2、linux基本操作:cd 、make、vim的使用编辑 3、文件配置需要细心、小心
-
摘要:C++调用Go方法时,字符串参数的内存管理需要由Go侧进行深度值拷贝。现象在一个APP技术项目中,子进程按请求加载Go的ServiceModule,将需要拉起的ServiceModule信息传递给Go的Loader,存在C++调用Go方法,传递字符串的场景。方案验证时,发现有奇怪的将std::string对象的内容传递给Go方法后,在Go方法协程中取到的值与预期不一致。经过一段时间的分析和验证,终于理解问题产生的原因并给出解决方案,现分享如下。背景知识Go有自己的内存回收GC机制,通过make等申请的内存不需要手动释放。C++中为std::string变量赋值新字符串后,.c_str()和.size()的结果会联动变化,尤其是.c_str()指向的地址也有可能变化。go build -buildmode=c-shared .生成的.h头文件中定义了C++中Go的变量类型的定义映射关系,比如GoString、GoInt等。其中GoString实际是一个结构体,包含一个字符指针和一个字符长度。原理及解释通过代码示例方式解释具体现象及原因,详见注释C++侧代码://// Created by w00526151 on 2020/11/5.// #include <string>#include <iostream>#include <unistd.h>#include "libgoloader.h" /*** 构造GoString结构体对象* @param p* @param n* @return*/GoString buildGoString(const char* p, size_t n){ //typedef struct { const char *p; ptrdiff_t n; } _GoString_; //typedef _GoString_ GoString; return {p, static_cast<ptrdiff_t>(n)};} int main(){ std::cout<<"test send string to go in C++"<<std::endl; std::string tmpStr = "/tmp/udsgateway-netconftemplateservice"; printf("in C++ tmpStr: %p, tmpStr: %s, tmpStr.size:%lu \r\n", tmpStr.c_str(), tmpStr.c_str(), tmpStr.size()); { //通过new新申请一段内存做字符串拷贝 char *newStrPtr = NULL; int newStrSize = tmpStr.size(); newStrPtr = new char[newStrSize]; tmpStr.copy(newStrPtr, newStrSize, 0); //调用Go方法,第一个参数直接传std::string的c_str指针和大小,第二个参数传在C++中单独申请的内存并拷贝的字符串指针,第三个参数和第一个一样,但是在go代码中做内存拷贝保存。 //调用Go方法后,通过赋值修改std::string的值内容,等待Go中新起的线程10s后再将三个参数值打印出来。 LoadModule(buildGoString(tmpStr.c_str(), tmpStr.size()), buildGoString(newStrPtr, newStrSize), buildGoString(tmpStr.c_str(),tmpStr.size())); //修改tmpStr的值,tmpStr.c_str()得到的指针指向内容会变化,tmpStr.size()的值也会变化,Go中第一个参数也会受到影响,前几位会变成新字符串内容。 //由于在Go中int是值拷贝,所以在Go中,第一个参数的长度没有变化,因此实际在Go中已经出现内存越界访问,可能产生Coredump。 tmpStr = "new string"; printf("in C++ change tmpStr and delete newStrPtr, new tmpStr: %p, tmpStr: %s, tmpStr.size:%lu \r\n", tmpStr.c_str(), tmpStr.c_str(), tmpStr.size()); //释放新申请的newStrPtr指针,Go中对应第二个string变量内存也会受到影响,产生乱码。 // 实际在Go中,已经在访问一段在C++中已经释放的内存,属于野指针访问,可能产生Coredump。 delete newStrPtr; } pause();}Go侧代码: package main import "C" import ( "fmt" "time" ) func printInGo(p0 string, p1 string, p2 string){ time.Sleep(10 * time.Second) fmt.Printf("in go function, p0:%s size %d, p1:%s size %d, p2:%s size %d", p0, len(p0), p1, len(p1), p2, len(p2)) } //export LoadModule func LoadModule(name string, version string, location string) int { //通过make的方式,新构建一段内存来存放从C++处传入的字符串,深度拷贝防止C++中修改影响Go tmp3rdParam := make([]byte, len(location)) copy(tmp3rdParam, location) new3rdParam := string(tmp3rdParam) fmt.Println("in go loadModule,first param is",name,"second param is",version, "third param is", new3rdParam) go printInGo(name, version, new3rdParam); return 0 } Go侧代码通过-buildmode=c-shared的方式生成libgoloader.so及libgoloader.h供C++编译运行使用 go build -o libgoloader.so -buildmode=c-shared . 程序执行结果: test send string to go in C++ in C++ tmpStr: 0x7fffe1fb93f0, tmpStr: /tmp/udsgateway-netconftemplateservice, tmpStr.size:38 # 将C++的指针传给Go,一开始打印都是OK的 in go loadModule,first param is /tmp/udsgateway-netconftemplateservice second param is /tmp/udsgateway-netconftemplateservice third param is /tmp/udsgateway-netconftemplateservice # 在C++中,将指针指向的内容修改,或者删掉指针 in C++ change tmpStr and delete newStrPtr, new tmpStr: 0x7fffe1fb93f0, tmpStr: new string, tmpStr.size:10 # 在Go中,参数1、参数2对应的Go string变量都受到了影响,参数3由于做了深度拷贝,没有受到影响。 in go function, p0:new string eway-netconftemplateservice size 38, p1: p netconftemplateservice size 38, p2:/tmp/udsgateway-netconftemplateservice size 38 结论 结论:C++调用Go方法时,字符串参数的内存管理需要由Go侧进行深度值拷贝。即参数三的处理方式 原因:传入的字符串GoString,实际是一个结构体,第一个成员p是一个char*指针,第二个成员n是一个int长度。 在C++代码中,任何对成员p的char*指针的操作,都将直接影响到Go中的string对象的值。 只有通过单独的内存空间开辟,进行独立内存管理,才可以避免C++中的指针操作对Go的影响。 ps:不在C++中进行内存申请释放的原因是C++无法感知Go中何时才能真的已经没有对象引用,无法找到合适的时间点进行内存释放。
-
你好,我使用 CANN 20.1 A200-3000-sdk_20.1.0.zip 的固件刷机(atlas 200 RC模式)成功,acllib使用的是 Ascend-cann-nnrt_20.1.rc1_linux-aarch64.run 中提供的,在编译运行程序时遇到如下错误:编译使用 acllib/lib64/stub下的so库,可以成功编译。部署到开发版后设置LD_LIBRARY_PATH 使可执行程序链接到 acllib/lib64 下的 so 库,运行时提示如下错误:acllib/lib64/libruntime.so: undefined symbol: _ZN6Msprof6Engine4InitERKSsPKNS0_10EngineIntfE该symble通过c++filt后为:Msprof::Engine::Init(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Msprof::Engine::EngineIntf const*)libruntime.so 链接了系统中的 /usr/lib64/libmsprof.so ,查看其符号表中有:_ZN6Msprof6Engine4InitERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEPKNS0_10EngineIntfE该symble通过c++filt后为:Msprof::Engine::Init(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Msprof::Engine::EngineIntf const*)是否两者编译发布时使用的_GLIBCXX_USE_CXX11_ABI不一致谢谢
-
线下使用的C++ 语言来构建的AI算法模型,如果要使用ModelArts的话又不想更换语言,这个有大神支招不?
-
该demo适用于esdk C++。demo和使用指导见附件。Win_Demo.part01~Win_Demo.part11为C++ Demo源码Demo_win32和Demo_win64是可直接用于测试的Demo
上滑加载中
推荐直播
-
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 年度收官盛典,将与大家共同探索华为开发者空间的创新奥秘。
回顾中
热门标签