找回密码
 会员注册
查看: 28|回复: 0

C++常见避坑指南

[复制链接]

5

主题

0

回帖

16

积分

新手上路

积分
16
发表于 2024-9-20 15:49:13 | 显示全部楼层 |阅读模式
作者:gouglegouC++从入门到放弃?本文主要总结了在C++开发或review过程中常见易出错点做了归纳总结,希望借此能增进大家对C++的了解,减少编程出错,提升工作效率,也可以作为C++开发的避坑攻略。空指针调用成员函数会crash??当调用一个空指针所指向的类的成员函数时,大多数人的反应都是程序会crash。空指针并不指向任何有效的内存地址,所以在调用成员函数时会尝试访问一个不存在的内存地址,从而导致程序崩溃。事实上有点出乎意料,先来看段代码:class MyClass {public:  static void Test_Func1() {    cout << "Handle Test_Func1!" << endl;  }  void Test_Func2() {    cout << "Handle Test_Func2!" << endl;  }  void Test_Func3() {    cout << "Handle Test_Func3! value:" << value << endl;  }  virtual void Test_Func4() {    cout << "Handle Test_Func4!" << endl;  }  int value = 0;};int main() {  MyClass* ptr = nullptr;  ptr->Test_Func1(); // ok, print Handle Test_Func1!  ptr->Test_Func2(); // ok, print Handle Test_Func2!  ptr->Test_Func3(); // crash  ptr->Test_Func4(); // crash  return 0;}上面例子中,空指针对Test_Func1和Test_Func2的调用正常,对Test_Func3和Test_Func4的调用会crash。可能很多人反应都会crash,实际上并没有,这是为啥?类的成员函数并不与具体对象绑定,所有的对象共用同一份成员函数体,当程序被编译后,成员函数的地址即已确定,这份共有的成员函数体之所以能够把不同对象的数据区分开来,靠的是隐式传递给成员函数的this指针,成员函数中对成员变量的访问都是转化成"this->数据成员"的方式。因此,从这一角度说,成员函数与普通函数一样,只是多了this指针。而类的静态成员函数只能访问静态成员变量,不能访问非静态成员变量,所以静态成员函数不需要this指针作为隐式参数。因此,Test_Func1是静态成员函数,不需要this指针,所以即使ptr是空指针,也不影响对Test_Fun1的正常调用。Test_Fun2虽然需要传递隐式指针,但是函数体中并没有使用到这个隐式指针,所以ptr为空也不影响对Test_Fun2的正常调用。Test_Fun3就不一样了,因为函数中使用到了非静态的成员变量,对num的调用被转化成this->num,也就是ptr->num,而ptr是空指针,因此会crash。Test_Fun4是虚函数,有虚函数的类会有一个成员变量,即虚表指针,当调用虚函数时,会使用虚表指针,对虚表指针的使用也是通过隐式指针使用的,因此Test_Fun4的调用也会crash。同理,以下std::shared_ptr的调用也是如此,日常开发需要注意,记得加上判空。std::shared_ptr url_handler;...if(url_handler->IsUrlNeedHandle(data)) {  url_handler->HandleUrl(param);}字符串相关字符串查找对字符串进行处理是一个很常见的业务场景,其中字符串查找也是非常常见的,但是用的不好也是会存在各种坑。常见的字符串查找方法有:std::string::find、std::string::find_first_of、std::string::find_first_not_of、std::string::find_last_of,各位C++Engineer都能熟练使用了吗?先来段代码瞧瞧:bool IsBlacklistDllFromSrv(const std::string& dll_name) {    try {        std::string target_str = dll_name; std::transform(target_str.begin(), target_str.end(), target_str.begin(), ::tolower);        if (dll_blacklist_from_srv.find(target_str) != std::string::npos) {            return true;        }    }    catch (...) {    }    return false;}上面这段代码,看下来没啥问题的样子。但是仔细看下来,就会发现字符串比对这里逻辑不够严谨,存在很大的漏洞。std::string::find只是用来在字符串中查找指定的子字符串,只要包含该子串就符合,如果dll_blacklist_from_srv="abcd.dll;hhhh.dll;test.dll"是这样的字符串,传入d.dll、hh.dll、dll;test.dll也会命中逻辑,明显是不太符合预期的。这里顺带回顾下C++std::string常见的字符串查找的方法:std::string::find用于在字符串中查找指定的子字符串。如果找到了子串,则返回子串的起始位置,否则返回std::string::npos。用于各种字符串操作,例如判断子字符串是否存在、获取子字符串的位置等。通过结合其他成员函数和算法,可以实现更复杂的字符串处理逻辑。std::string::find_first_of用于查找字符串中第一个与指定字符集合中的任意字符匹配的字符,并返回其位置。可用来检查字符串中是否包含指定的某些字符或者查找字符串中第一个出现的特定字符std::string::find_first_not_of用于查找字符串中第一个不与指定字符集合中的任何字符匹配的字符,并返回其位置。std::string::find_last_of用于查找字符串中最后一个与指定字符集合中的任意字符匹配的字符,并返回其位置。可以用来检查字符串中是否包含指定的某些字符,或者查找字符串中最后一个出现的特定字符std::string::find_last_not_of用于查找字符串中最后一个不与指定字符集合中的任何字符匹配的字符,并返回其位置。除了以上几个方法外,还有查找满足指定条件的元素std::find_if,std::find_if是C++标准库中的一个算法函数,用于在指定范围内查找第一个满足指定条件的元素,并返回其迭代器。需要注意的是,使用std::find_if函数时需要提供一个可调用对象(例如lambda表达式或函数对象),用于指定查找条件。std::vector vec = {1, 2, 3, 4, 5};auto it = std::find_if(vec.begin(), vec.end(), [](int x) { return x % 2 == 0; });if (it != vec.end()) {    std::cout << "Found even number: " << *it << std::endl;} 此外,在业务开发有时候也会遇到需要C++ boost库支持的starts_with、ends_with。如果用C++标准库来实现,常规编写方法可如下:bool starts_with(const std::string& str, const std::string& prefix) {    return str.compare(0, prefix.length(), prefix) == 0;}bool ends_with(const std::string& str, const std::string& suffix) {    if (str.length() < suffix.length()) {        return false;    } else {        return str.compare(str.length() - suffix.length(), suffix.length(), suffix) == 0;    }}以上代码中,starts_with>高级保存选项->编码来更改当前代码页的编码)。下面是一些示例代码,演示了如何进行正确的转换,针对Windows平台,官方提供了相应的系统Api(MultiByteToWideChar):std::wstring Utf8ToUnicode(const std::string& str) {    int len = str.length();    if (0 == len)      return L"";    int nLength = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), len, 0, 0);    std::wstring buf(nLength + 1, L'\0');    MultiByteToWideChar(CP_UTF8, 0, str.c_str(), len, &buf[0], nLength);    buf.resize(wcslen(buf.c_str()));    return buf;}std::string UnicodeToUtf8(const std::wstring& wstr) {    if (wstr.empty()) {      return std::string();    }    int size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], static_cast(wstr.size()), nullptr, 0, nullptr, nullptr);    std::string str_to(size_needed, 0);    WideCharToMultiByte(CP_UTF8, 0, &wstr[0], static_cast(wstr.size()), &str_to[0], size_needed, nullptr, nullptr);    return str_to;}如果使用C++标准库来实现,常规写法可以参考下面:#include #include #include #include // 从窄字符串到宽字符串的转换std::wstring narrowToWide(const std::string& narrowStr) {    try {        std::wstring_convert> converter;        return converter.from_bytes(narrowStr);    } catch (...) { // 如果传进来的字符串不是utf8编码的,这里会抛出std::range_error异常        return {};    }}// 从宽字符串到窄字符串的转换std::string wideToNarrow(const std::wstring& wideStr) {    try {        std::wstring_convert> converter;        return converter.to_bytes(wideStr);    } catch (...) {        return {};    }}//utf8字符串转成stringstd::string utf8ToString(const char8_t* str) {    std::wstring_convert, char16_t> convert;    std::u16string u16str = convert.from_bytes(        reinterpret_cast(str),        reinterpret_cast(str + std::char_traits::length(str)));    return std::wstring_convert, char16_t>{}.to_bytes(u16str);}int main(){    {        std::wstring wideStr = L"Hello, 你好!";        std::string narrowStr = wideToNarrow(wideStr);        std::wstring convertedWideStr = narrowToWide(narrowStr);    } {        //std::string narrowStr = "Hello, 你好!"; (1)        std::string narrowStr = utf8ToString(u8"Hello, 你好!"); //(2)        std::wstring wideStr = narrowToWide(narrowStr);        std::string convertedNarrowStr = wideToNarrow(wideStr);    }        return 0;}(1)首先std::string不理解编码,在CPP官方手册里面也解释了,std::string处理字节的方式与所使用的编码无关,如果用于处理多字节或可变长度字符的序列(例如UTF-8),则此类的所有成员以及它的迭代器仍然以字节(而不是实际的编码字符)为单位进行操作,如果用来处理包含中文的字符串就可能出现乱码。这里直接将包含中文的字符串赋值给std::string,无法保证是UTF8编码,进行转换时会提示std::range_error异常;此外,std::wstring是会理解编码的,其中的字符串通常使用UTF-16或UTF-32编码,这取决于操作系统和编译器的实现。(2)这里由于使用u8""构造了UTF8编码字符串,但是不能直接用来构造std::string,所以进行转了下utf8ToString;全局静态对象大家有没有在工程代码中发现有下面这种写法,将常量字符串声明为静态全局的。staticconststd::stringkVal="hahahhaha";staticconststd::wstringkxxConfigVal="hahahhaha";优点:可读性好:使用有意义的变量名,可以清晰地表达变量的含义和用途,提高了代码的可读性。安全性高:由于使用了const关键字,这个字符串变量是不可修改的,可以避免意外的修改和安全问题。生命周期长:静态变量的生命周期从程序启动到结束,不受函数的调用和返回影响。缺点:构造开销:静态变量的初始化发生在程序启动时也就是执行main()之前,会增加程序启动的时间和资源消耗。大量的这种静态全局对象,会拖慢程序启动速度静态变量共享:静态变量在整个程序中只有一份实例,可能会导致全局状态共享和难以调试的问题。此外,静态变量的初始化顺序可能会受到编译单元(源文件)中其他静态变量初始化顺序的影响,因此在跨编译单元的情况下,静态变量的初始化顺序可能是不确定的。在实际编程中,还是不太建议使用全局静态对象,建议的写法:要声明全局的常量字符串,可以使用const关键字和extern关键字的组合:// constants.hextern const char* GLOBAL_STRING;// constants.cpp\#include "constants.h"const char* GLOBAL_STRING = "Hello, world!";constexpr char* kVal="hahhahah";使用 constexpr 关键字来声明全局的常量字符串:// constants.hconstexpr const char* GLOBAL_STRING = "Hello, world!";迭代器删除在处理缓存时,容器元素的增删查改是很常见的,通过迭代器去删除容器(vector/map/set/unordered_map/list)元素也是常有的,但这其中使用不当也会存在很多坑。std::vector numbers = { 88, 101, 56, 203, 72, 135 };auto it = std::find_if(numbers.begin(), numbers.end(), [](int num) {    return num > 100 & num % 2 != 0;});vec.erase(it);上面代码,查找std::vector中大于100并且为奇数的整数并将其删除。std::find_if将从容器的开头开始查找,直到找到满足条件的元素或者遍历完整个容器,并返回迭代器it,然后去删除该元素。但是这里没有判断it为空的情况,直接就erase了,如果erase一个空的迭代器会引发crash。很多新手程序员会犯这样的错误,随时判空是个不错的习惯。删除元素不得不讲下std::remove和std::remove_if,用于从容器中移除指定的元素,函数会将符合条件的元素移动到容器的末尾,并返回指向新的末尾位置之后的迭代器,最后使用容器的erase来擦除从新的末尾位置开始的元素。std::vector vecs = { "A", "", "B", "", "C", "hhhhh", "D" };vecs.erase(std::remove(vecs.begin(), vecs.end(), ""), vecs.end());// 移除所有偶数元素vec.erase(std::remove_if(vec.begin(), vec.end(), [](int x) { return x % 2 == 0; }), vec.end());这里的erase不用判空,其内部实现已经有判空处理。_CONSTEXPR20 iterator erase(const_iterator _First, const_iterator _Last) noexcept(        is_nothrow_move_assignable_v) /* strengthened */ {    const pointer _Firstptr = _First._Ptr;    const pointer _Lastptr  = _Last._Ptr;    auto& _My_data          = _Mypair._Myval2;    pointer& _Mylast        = _My_data._Mylast;    // ....    if (_Firstptr != _Lastptr) { // something to do, invalidate iterators        _Orphan_range(_Firstptr, _Mylast);        const pointer _Newlast = _Move_unchecked(_Lastptr, _Mylast, _Firstptr);        _Destroy_range(_Newlast, _Mylast, _Getal());        _Mylast = _Newlast;    }    return iterator(_Firstptr, _STD addressof(_My_data));}此外,STL容器的删除也要小心迭代器失效,先来看个vector、list、map删除的例子:// vector、list、map遍历并删除偶数元素std::vector elements = { 1, 2, 3, 4, 5 };for (auto it = elements.begin(); it != elements.end();) { if (*it % 2 == 0) {        elements.erase(it++);    } else {        it++;    }}// Errorstd::list cont{ 88, 101, 56, 203, 72, 135 };for (auto it = cont.begin(); it != cont.end(); ) {    if (*it % 2 == 0) {        cont.erase(it++);    } else {        it++;    }}// Ok std::map myMap = { {1, "one"}, {2, "two"}, {3, "three"}, {4, "four"}, {5, "five"} };// 遍历并删除键值对,删除键为偶数的元素for (auto it = myMap.begin(); it != myMap.end(); ) {    if (it->first % 2 == 0) {        myMap.erase(it++);    } else {        it++;    }}// Ok上面几类容器同样的遍历删除元素,只有vector报错crash了,map和list都能正常运行。其实vector调用erase()方法后,当前位置到容器末尾元素的所有迭代器全部失效了,以至于不能再使用。迭代器的失效问题:对容器的操作影响了元素的存放位置,称为迭代器失效。迭代器失效的情况:●当容器调用erase()方法后,当前位置到容器末尾元素的所有迭代器全部失效。●当容器调用insert()方法后,当前位置到容器末尾元素的所有迭代器全部失效。●如果容器扩容,在其他地方重新又开辟了一块内存,原来容器底层的内存上所保存的迭代器全都失效。迭代器失效有三种情况,由于底层的存储数据结构,分三种情况:序列式迭代器失效,序列式容器(std::vector和std::deque),其对应的数据结构分配在连续的内存中,对其中的迭代器进行insert和erase操作都会使得删除点和插入点之后的元素挪位置,进而导致插入点和删除掉之后的迭代器全部失效。可以利用erase迭代器接口返回的是下一个有效的迭代器。链表式迭代器失效,链表式容器(std::list)使用链表进行数据存储,插入或者删除只会对当前的节点造成影响,不会影响其他的迭代器。可以利用erase迭代器接口返回的是下一个有效的迭代器,或者将当前的迭代器指向下一个erase(iter++)。关联式迭代器失效,关联式容器,如map,set,multimap,multiset等,使用红黑树进行数据存储,删除当前的迭代器,仅会使当前的迭代器失效。erase迭代器的返回值为void(C++11之前),可以采用erase(iter++)的方式进行删除。值得一提的是,在最新的C++11标准中,已经新增了一个map::erase函数执行后会返回下一个元素的iterator,因此可以使用erase的返回值获取下一个有效的迭代器。在实现上有两种模板,其一是通过erase获得下一个有效的iterator,使用于序列式迭代器和链表式迭代器(C++11开始关联式迭代器也可以使用)for (auto it = elements.begin(); it != elements.end(); ) {    if (ShouldDelete(*it)) {        it = elements.erase(it); // erase删除元素,返回下一个迭代器    } else {        it++;    }}其二是,递增当前迭代器,适用于链表式迭代器和关联式迭代器。for (auto it = elements.begin(); it != elements.end(); ) {    if (ShouldDelete(*it)) {        elements.erase(it++);     } else {        it++;    }}对象拷贝在众多编程语言中C++的优势之一便是其高性能,可是开发者代码写得不好(比如:很多不必要的对象拷贝),直接会影响到代码性能,接下来就讲几个常见的会引起无意义拷贝的场景。for循环:std::vector vec;for(std::string s: vec) {}// orfor(auto s: vec) {}这里每个string都会被拷贝一次,为避免无意义拷贝可以将其改成:for(constauto&s:vec)或者for(conststd::string&s:vec)lambda捕获// 获取对应消息类型的内容std::string GetRichTextMessageXxxContent(const std::shared_ptr& message, const std::map>& related_user_names, const model::UserId& login_userid, bool for_message_index) { // ... // 解析RichText内容 return DecodeRichTextMessage(message, [=](uint32_t item_type, const std::string& data) {  std::string output_text;  // ...  return output_text;  });}上述代码用于解析获取文本消息内容,涉及到富文本消息的解析和一些逻辑的计算,高频调用,他在解析RichText内容的callback中直接简单粗暴的按值捕获了所有变量,将所有变量都拷贝了一份,这里造成不必要的性能损耗,尤其上面那个std::map。这里可以改成按引用来捕获,规避不必要的拷贝。lambda函数在捕获时会将被捕获对象拷贝,如果捕获的对象很多或者很占内存,将会影响整体的性能,可以根据需求使用引用捕获或者按需捕获:autofunc=&a{};autofunc=a=std::move(a){};(限C++14以后)隐式类型转换std::map myMap = {{1, "One"}, {2, "Two"}, {3, "Three"}};for (const std::pair& pair : myMap) {    //...}这里在遍历关联容器时,看着是const引用的,心想着不会发生拷贝,但是因为类型错了还是会发生拷贝,std::map中的键值对是以std::pair的形式存储的,其中key是常量。因此,在每次迭代时,会将当前键值对拷贝到临时变量中。在处理大型容器或频繁遍历时,这种拷贝操作可能会产生一些性能开销,所以在遍历时推荐使用constauto&,也可以使用结构化绑定:for(constauto&[key,value]:map){}(限C++17后)函数返回值优化RVO是ReturnValueOptimization的缩写,即返回值优化,NRVO就是具名的返回值优化,为RVO的一个变种,此特性从C++11开始支持。为了更清晰的了解编译器的行为,这里实现了构造/析构及拷贝构造、赋值操作函数,如下:class Widget {public: Widget() {  std::cout << "Widget: Constructor" << std::endl; }    Widget(const Widget& other) {        name = other.name;        std::cout << "Widget: Copy construct" << std::endl;    }    Widget& operator=(const Widget& other) {        std::cout << "Widget: Assignment construct" << std::endl;        name = other.name;        return *this;    }    ~Widget() {        std::cout << "Widget: Destructor" << std::endl;    }public:    std::string name;};Widget GetMyWidget(int v) {    Widget w;    if (v % 2 == 0) {        w.name = 1;        return w;    } else {        return w;    }}int main(){    const Widget& w = GetMyWidget(2); // (1)    Widget w = GetMyWidget(2); // (2)    GetMyWidget(2); // (3)    return 0;}运行上面代码,跑出的结果:未优化:(msvc 2022, C++14)Widget: ConstructorWidget: Copy constructWidget: DestructorWidget: Destructor优化后:Widget: ConstructorWidget: Destructor针对上面(1)(2)(3)的调用,我之前也是有点迷惑,以为要减少拷贝必须得用常引用来接,但是发现编译器进行返回值优化后(1)(2)(3)运行结果都是一样的,也就是日常开发中,针对函数中返回的临时对象,可以用对象的常引用或者新的一个对象来接,最后的影响其实可以忽略不计的。不过个人还是倾向于对象的常引用来接,一是出于没有优化时(编译器不支持或者不满足RVO条件)可以减少一次拷贝,二是如果返回的是对象的引用时可以避免拷贝。但是也要注意不要返回临时对象的引用。// pb协议接口实现inline const :B::XXXConfig& XXConfigRsp::config() const {   //...}void XXSettingView::SetSettingInfo(const B::XXConfigRsp& rsp){ const auto config = rsp.config(); // 内部返回的是对象的引用,这里没有引用来接导致不必要的拷贝}当遇到上面这种返回对象的引用时,外部最好也是用对象的引用来接,减少不必要的拷贝。此外,如果Widget的拷贝赋值操作比较耗时,通常在使用函数返回这个类的一个对象时也是会有一定的讲究的。// style 1Widget func(Args param);// style 2bool func(Widget* ptr, Args param);上面的两种方式都能达到同样的目的,但直观上的使用体验的差别也是非常明显的:style 1只需要一行代码,而style 2需要两行代码,可能大多数人直接无脑style 1// style 1Widget obj = func(params);// style 2Widget obj;func(&obj, params);但是,能达到同样的目的,消耗的成本却未必是一样的,这取决于多个因素,比如编译器支持的特性、C++语言标准的规范强制性等等。看起来style 2虽然需要写两行代码,但函数内部的成本却是确定的,只会取决于你当前的编译器,外部即使采用不同的编译器进行函数调用,也并不会有多余的时间开销和稳定性问题。使用style 1时,较复杂的函数实现可能并不会如你期望的使用RVO优化,如果编译器进行RVO优化,使用style 1无疑是比较好的选择。利用好编译器RVO特性,也是能为程序带来一定的性能提升。函数传参使用对象的引用effective C++中也提到了:以pass-by-reference-to-const替换pass-by-value指在函数参数传递时,将原本使用"pass-by-value"(按值传递)的方式改为使用 "pass-by-reference-to-const"(按常量引用传递)的方式。在 "pass-by-value" 中,函数参数会创建一个副本,而在 "pass-by-reference-to-const" 中,函数参数会成为原始对象的一个引用,且为了避免修改原始对象,使用了常量引用。通过使用 "pass-by-reference-to-const",可以避免在函数调用时进行对象的拷贝操作,从而提高程序的性能和效率;还可以避免对象被切割问题:当一个派生类对象以传值的方式传入一个函数,但是该函数的形参是基类,则只会调用基类的构造函数构造基类部分,派生类的新特性将会被切割。此外,使用常量引用还可以确保函数内部不会意外地修改原始对象的值。std::shared_ptr线程安全对shared_ptr相信大家都很熟悉,但是一提到是否线程安全,可能很多人心里就没底了,借助本节,对shared_ptr线程安全方面的问题进行分析和解释。shared_ptr的线程安全问题主要有两种:1. 引用计数的加减操作是否线程安全; 2. shared_ptr修改指向时是否线程安全。引用计数shared_ptr中有两个指针,一个指向所管理数据的地址,另一个指向执行控制块的地址。执行控制块包括对关联资源的引用计数以及弱引用计数等。在前面我们提到shared_ptr支持跨线程操作,引用计数变量是存储在堆上的,那么在多线程的情况下,指向同一数据的多个shared_ptr在进行计数的++或--时是否线程安全呢?引用计数在STL中的定义如下:_Atomic_word _M_use_count;   // #shared_Atomic_word _M_weak_count;  // #weak + (#shared != 0)当对shared_ptr进行拷贝时,引入计数增加,实现如下:template <>inline bool _Sp_counted_base::_M_add_ref_lock_nothrow() noexcept {    // erform lock-free add-if-not-zero operation.    _Atomic_word __count = _M_get_use_count();    do {        if (__count == 0) return false;        // Replace the current counter value with the old value + 1, as        // long as it's not changed meanwhile.    } while (!__atomic_compare_exchange_n(&_M_use_count, &__count, __count + 1, true, __ATOMIC_ACQ_REL,                                          __ATOMIC_RELAXED));    return true;}template inline void _Sp_counted_base::_M_add_ref_copy() {    ++_M_use_count;}对引用计数的增加主要有以下2种方法:_M_add_ref_copy函数,对_M_use_count+1,是原子操作。_M_add_ref_lock函数,是调用__atomic_compare_exchange_n``实现的``,主要逻辑仍然是_M_use_count+1,而该函数是线程安全的,和_M_add_ref_copy的区别是对不同_Lock_policy有不同的实现,包含直接加、原子操作加、加锁。因此我们可以得出结论:在多线程环境下,管理同一个数据的shared_ptr在进行计数的增加或减少的时候是线程安全的,这是一波原子操作。修改指向修改指向分为操作同一个shared_ptr对象和操作不同的shared_ptr对象两种。多线程代码操作的是同一个shared_ptr的对象比如std::thread的回调函数,是一个lambda表达式,其中引用捕获了一个shared_ptr对象shared_ptr<< "Start Calculate..." << std::endl; // (4)    int sum = 0;    for (int num : numbers) {        sum += num;    }    return sum;}int main() {    std::vector<< "Other operations are in progress..." << std::endl; // (1)    int counter = 1;    while (counter <= 1000000000) {        counter++;    }    std::cout << "Other operations are completed." << std::endl; // (2)    // 等待异步任务完成并获取结果    int sum = future_sum.get();    std::cout << "The calculation result is:" << sum << std::endl; // (3)    return 0;}直接运行上面的代码,输出结果如下:Other operations are in progress...Start Calculate...Other operations are completed.The calculation result is:655执行完(1) 就去执行(4), 然后再(2)(3),说明这里是异步执行的。那可以认为async一定是异步的吗?如果改成std::async(std::launch::deferred, calculate_sum, numbers); 运行结果如下:Other operations are in progress...Other operations are completed.Start Calculate...The calculation result is:655执行完(1) (2), 然后再(4)(3), 说明是真正调用std::future<>
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2024-12-27 02:10 , Processed in 0.944972 second(s), 25 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表