C++ folly庫解讀(一) Fbstring —— 一個完美替代std::string的庫(下)

2021-02-14 紅宸笑
builtin_unreachable && assume(0)字符串拷貝

同初始化,也是根據不同的字符串類型,調用不同的函數:

  fbstring_core(const fbstring_core& rhs) {
    assert(&rhs != this);
    switch (rhs.category()) {
      case Category::isSmall:
        copySmall(rhs);
        break;
      case Category::isMedium:
        copyMedium(rhs);
        break;
      case Category::isLarge:
        copyLarge(rhs);
        break;
      default:
        folly::assume_unreachable();
    }
  }

copySmall
template <class Char>
inline void fbstring_core<Char>::copySmall(const fbstring_core& rhs) {
  // Just write the whole thing, don't look at details. In
  // particular we need to copy capacity anyway because we want
  // to set the size (don't forget that the last character,
  // which stores a short string's length, is shared with the
  // ml_.capacity field).

  ml_ = rhs.ml_;
}

正如注釋中所說,雖然 small strings 的情況下,字符串存儲在 small中,但是我們只需要把 ml直接賦值即可,因為在一個 union 中。

copyMedium
template <class Char>
FOLLY_NOINLINE inline void fbstring_core<Char>::copyMedium(
    const fbstring_core& rhs) {
  // Medium strings are copied eagerly. Don't forget to allocate
  // one extra Char for the null terminator.
  auto const allocSize = goodMallocSize((1 + rhs.ml_.size_) * sizeof(Char));
  ml_.data_ = static_cast<Char*>(checkedMalloc(allocSize));
  // Also copies terminator.
  fbstring_detail::podCopy(
      rhs.ml_.data_, rhs.ml_.data_ + rhs.ml_.size_ + 1, ml_.data_);
  ml_.size_ = rhs.ml_.size_;
  ml_.setCapacity(allocSize / sizeof(Char) - 1, Category::isMedium);
}

medium strings 是 eager copy,所以就是"深拷貝":

賦值 size、capacity、category.copyLarge
template <class Char>
FOLLY_NOINLINE inline void fbstring_core<Char>::copyLarge(
    const fbstring_core& rhs) {
  // Large strings are just refcounted
  ml_ = rhs.ml_;
  RefCounted::incrementRefs(ml_.data_);
}

large strings 的 copy 過程很直觀,因為是 COW 方式:

incrementRefs 和內部調用 fromData 這兩個個函數值得看一下:

static RefCounted* fromData(Char* p) {
      return static_cast<RefCounted*>(static_cast<void*>(
          static_cast<unsigned char*>(static_cast<void*>(p)) -
          getDataOffset()));
}

static void incrementRefs(Char* p) {
  fromData(p)->refCount_.fetch_add(1, std::memory_order_acq_rel);
}

因為 ml中指向的是 RefCounted 的 data[1],所以我們需要通過 fromData 來找到 data_所屬的 RefCounted 的地址。我把 fromData 函數內的運算拆開:

static RefCounted * fromData(Char * p) {
      // 轉換data_[1]的地址
      void* voidDataAddr = static_cast<void*>(p);
      unsigned char* unsignedDataAddr = static_cast<unsigned char*>(voidDataAddr);

     // 獲取data_[1]在結構體的偏移量再相減,得到的就是所屬RefCounted的地址
      unsigned char* unsignedRefAddr = unsignedDataAddr - getDataOffset();

      void* voidRefAddr = static_cast<void*>(unsignedRefAddr);
      RefCounted* refCountAddr = static_cast<RefCounted*>(voidRefAddr);

      return refCountAddr;
}

值得關注的是如何轉換不同類型結構體的指針並做運算,這裡的做法是 :Char* -> void* -> unsigned char* -> 與size_t做減法 -> void * -> RefCounted*

析構
~fbstring_core() noexcept {
    if (category() == Category::isSmall) {
      return;
    }
    destroyMediumLarge();
}

如果是 small 類型,直接返回,因為利用的是棧空間。否則,針對 medium 和 large,調用 destroyMediumLarge。
FOLLY_MALLOC_NOINLINE void destroyMediumLarge() noexcept {
  auto const c = category();
  FBSTRING_ASSERT(c != Category::isSmall);
  if (c == Category::isMedium) {
    free(ml_.data_);
  } else {
    RefCounted::decrementRefs(ml_.data_);
  }
}

medium :free 動態分配的字符串內存即可。large : 調用 decrementRefs,針對共享字符串進行操作。
static void decrementRefs(Char * p) {
  auto const dis = fromData(p);
  size_t oldcnt = dis->refCount_.fetch_sub(1, std::memory_order_acq_rel);
  FBSTRING_ASSERT(oldcnt > 0);
  if (oldcnt == 1) {
    free(dis);
  }
}

邏輯也很清晰:先對引用計數減 1,如果本身就只有 1 個引用,那麼直接 free 掉整個 RefCounted。

COW

最重要的一點,也是 large strings 獨有的,就是 COW. 任何針對字符串寫的操作,都會觸發 COW,包括前面舉過的[]操作,例如:

non-const operator[](size_type pos "")

我們舉個例子,比如non-const operator[](size_type pos "")

non-const operator[](size_type pos "")
reference operator[](size_type pos "") {
    return *(begin() + pos);
}

iterator begin() {
    return store_.mutableData();
}

來重點看下 mutableData() :

Char* mutableData() {
  switch (category()) {
  case Category::isSmall:
    return small_;
  case Category::isMedium:
    return ml_.data_;
  case Category::isLarge:
    return mutableDataLarge();
  }
  fbstring_detail::assume_unreachable();
}

template <class Char>
inline Char* fbstring_core<Char>::mutableDataLarge() {
  FBSTRING_ASSERT(category() == Category::isLarge);
  if (RefCounted::refs(ml_.data_) > 1) { // Ensure unique.
    unshare();
  }
  return ml_.data_;
}

同樣是分三種情況。small 和 medium 直接返回字符串的地址,large 會調用 mutableDataLarge(),可以看出,如果引用數大於 1,會進行 unshare 操作 :

void unshare(size_t minCapacity = 0);

template <class Char>
FOLLY_MALLOC_NOINLINE inline void fbstring_core<Char>::unshare(
    size_t minCapacity) {
  FBSTRING_ASSERT(category() == Category::isLarge);
  size_t effectiveCapacity = std::max(minCapacity, ml_.capacity());
  auto const newRC = RefCounted::create(&effectiveCapacity);
  // If this fails, someone placed the wrong capacity in an
  // fbstring.
  FBSTRING_ASSERT(effectiveCapacity >= ml_.capacity());
  // Also copies terminator.
  fbstring_detail::podCopy(ml_.data_, ml_.data_ + ml_.size_ + 1, newRC->data_);
  RefCounted::decrementRefs(ml_.data_);
  ml_.data_ = newRC->data_;
  ml_.setCapacity(effectiveCapacity, Category::isLarge);
  // size_ remains unchanged.
}

基本思路:

對原有的共享字符串減少一個引用 decrementRefs,這個函數在上面的析構小節裡分析過。設置 ml_的 data、capacity、category.注意此時還不會設置 size,因為還不知道應用程式對字符串進行什麼修改。non-const 與 const

大家可能注意到了,上面的 at 和[]強調了 non-const,這是因為 const-qualifer 針對這兩個調用不會觸發 COW ,還以[]為例:

// C++11 21.4.5 element access:
const_reference operator[](size_type pos "") const {
    return *(begin() + pos);
}

const_iterator begin() const {
    return store_.data();
}

// In C++11 data() and c_str() are 100% equivalent.
const Char* data() const { return c_str(); }

const Char* c_str() const {
    const Char* ptr = ml_.data_;
    // With this syntax, GCC and Clang generate a CMOV instead of a branch.
    ptr = (category() == Category::isSmall) ? small_ : ptr;
    return ptr;
}

可以看出區別,non-const 版本的 begin()中調用的是 mutableData(),而 const-qualifer 版本調用的是 data() -> c_str(),而 c_str()直接返回的字符串地址。

所以,當字符串用到[]、at 且不需要寫操作時,最好用 const-qualifer.

我們拿 folly 自帶的benchmark 工具[1]測試一下:

#include "folly/Benchmark.h"
#include "folly/FBString.h"
#include "folly/container/Foreach.h"

using namespace std;
using namespace folly;

BENCHMARK(nonConstFbstringAt, n) {
  ::folly::fbstring str(
      "fbstring is a drop-in replacement for std::string. The main benefit of fbstring is significantly increased "
      "performance on virtually all important primitives. This is achieved by using a three-tiered storage strategy "
      "and by cooperating with the memory allocator. In particular, fbstring is designed to detect use of jemalloc and "
      "cooperate with it to achieve significant improvements in speed and memory usage.");
  FOR_EACH_RANGE(i, 0, n) {
    char &s = str[2];
    doNotOptimizeAway(s);
  }
}

BENCHMARK_DRAW_LINE();

BENCHMARK_RELATIVE(constFbstringAt, n) {
  const ::folly::fbstring str(
      "fbstring is a drop-in replacement for std::string. The main benefit of fbstring is significantly increased "
      "performance on virtually all important primitives. This is achieved by using a three-tiered storage strategy "
      "and by cooperating with the memory allocator. In particular, fbstring is designed to detect use of jemalloc and "
      "cooperate with it to achieve significant improvements in speed and memory usage.");
  FOR_EACH_RANGE(i, 0, n) {
    const char &s = str[2];
    doNotOptimizeAway(s);
  }
}
int main() { runBenchmarks(); }

結果是 constFbstringAt 比 nonConstFbstringAt 快了 175%

============================================================================
delve_folly/main.cc                             relative  time/iter  iters/s
============================================================================
nonConstFbstringAt                                          39.85ns   25.10M
-
constFbstringAt                                  175.57%    22.70ns   44.06M
============================================================================

Realloc

reserve、operator+等操作,可能會涉及到內存重新分配,最終調用的都是 memory/Malloc.h 中的 smartRealloc:

inline void* checkedRealloc(void* ptr, size_t size) {
  void* p = realloc(ptr, size);
  if (!p) {
    throw_exception<std::bad_alloc>();
  }
  return p;
}

/**
 * This function tries to reallocate a buffer of which only the first
 * currentSize bytes are used. The problem with using realloc is that
 * if currentSize is relatively small _and_ if realloc decides it
 * needs to move the memory chunk to a new buffer, then realloc ends
 * up copying data that is not used. It's generally not a win to try
 * to hook in to realloc() behavior to avoid copies - at least in
 * jemalloc, realloc() almost always ends up doing a copy, because
 * there is little fragmentation / slack space to take advantage of.
 */
FOLLY_MALLOC_CHECKED_MALLOC FOLLY_NOINLINE inline void* smartRealloc(
    void* p,
    const size_t currentSize,
    const size_t currentCapacity,
    const size_t newCapacity) {
  assert(p);
  assert(currentSize <= currentCapacity &&
         currentCapacity < newCapacity);

  auto const slack = currentCapacity - currentSize;
  if (slack * 2 > currentSize) {
    // Too much slack, malloc-copy-free cycle:
    auto const result = checkedMalloc(newCapacity);
    std::memcpy(result, p, currentSize);
    free(p);
    return result;
  }
  // If there's not too much slack, we realloc in hope of coalescing
  return checkedRealloc(p, newCapacity);
}

從注釋和代碼看為什麼函數起名叫smartRealloc :

如果(the currentCapacity - currentSize) _ 2 > currentSize,即 currentSize < 2/3 _ capacity,說明當前分配的內存利用率較低,此時認為如果使用 realloc 並且 realloc 決定拷貝當前內存到新內存,成本會高於直接 malloc(newCapacity) + memcpy + free(old_memory)。其他__builtin_expect

給編譯器提供分支預測信息。原型為:

long __builtin_expect (long exp, long c)

表達式的返回值為 exp 的值,跟 c 無關。 我們預期 exp 的值是 c。例如下面的例子,我們預期 x 的值是 0,所以這裡提示編譯器,只有很小的機率會調用到 foo()

if (__builtin_expect (x, 0))
  foo ();

再比如判斷指針是否為空:

if (__builtin_expect (ptr != NULL, 1))
  foo (*ptr);

在 fbstring 中也用到了builtin_expect,例如創建 RefCounted 的函數 (FOLLY_LIKELY 包裝了一下builtin_expect):

#if __GNUC__
#define FOLLY_DETAIL_BUILTIN_EXPECT(b, t) (__builtin_expect(b, t))
#else
#define FOLLY_DETAIL_BUILTIN_EXPECT(b, t) b
#endif

//  Likeliness annotations
//
//  Useful when the author has better knowledge than the compiler of whether
//  the branch condition is overwhelmingly likely to take a specific value.
//
//  Useful when the author has better knowledge than the compiler of which code
//  paths are designed as the fast path and which are designed as the slow path,
//  and to force the compiler to optimize for the fast path, even when it is not
//  overwhelmingly likely.

#define FOLLY_LIKELY(x) FOLLY_DETAIL_BUILTIN_EXPECT((x), 1)
#define FOLLY_UNLIKELY(x) FOLLY_DETAIL_BUILTIN_EXPECT((x), 0)

static RefCounted* create(const Char* data, size_t* size) {
  const size_t effectiveSize = *size;
  auto result = create(size);
  if (FOLLY_LIKELY(effectiveSize > 0)) {       // __builtin_expect
    fbstring_detail::podCopy(data, data + effectiveSize, result->data_);
  }
  return result;
}

從彙編角度來說,會將可能性更大的彙編緊跟著前面的彙編,防止無效指令的加載。可以參考:

likely() and unlikely()[2]What is the advantage of GCC's \_\_builtin_expect in if else statements?[3]CMOV 指令

conditional move,條件傳送。類似於 MOV 指令,但是依賴於 RFLAGS 寄存器內的狀態。如果條件沒有滿足,該指令不會有任何效果。

CMOV 的優點是可以避免分支預測,避免分支預測錯誤對 CPU 流水線的影響。詳細可以看這篇文檔:amd-cmovcc.pdf[4]

fbstring 在一些場景會提示編譯器生成 CMOV 指令,例如:

const Char* c_str() const {
  const Char* ptr = ml_.data_;
  // With this syntax, GCC and Clang generate a CMOV instead of a branch.
  ptr = (category() == Category::isSmall) ? small_ : ptr;
  return ptr;
}

builtin_unreachable && assume(0)

如果程序執行到了builtin_unreachable 和assume(0) ,那麼會出現未定義的行為。例如**builtin_unreachable 出現在一個不會返回的函數後面,而且這個函數沒有聲明為**attribute\_\_((noreturn))。例如6.59 Other Built-in Functions Provided by GCC給出的例子 :

void function_that_never_returns (void);

int g (int c)
{
  if (c)
    {
      return 1;
    }
  else
    {
      function_that_never_returns ();
      __builtin_unreachable ();
    }
}

如果不加__builtin_unreachable ();,會報error: control reaches end of non-void function [-Werror=return-type]

folly 將 builtin_unreachable 和assume(0) 封裝成了assume_unreachable:

[[noreturn]] FOLLY_ALWAYS_INLINE void assume_unreachable() {
  assume(false);
  // Do a bit more to get the compiler to understand
  // that this function really will never return.
#if defined(__GNUC__)
  __builtin_unreachable();
#elif defined(_MSC_VER)
  __assume(0);
#else
  // Well, it's better than nothing.
  std::abort();
#endif
}

在 fbstring 的一些特性場景,比如 switch 判斷 category 中用到。這是上面提到過的 mutableData() :

Char* mutableData() {
  switch (category()) {
    case Category::isSmall:
      return small_;
    case Category::isMedium:
      return ml_.data_;
    case Category::isLarge:
      return mutableDataLarge();
  }
  folly::assume_unreachable();
}

jemalloc大致的算法和原理可以參考 facebook 的這篇博客 :Scalable memory allocation using jemalloc[7]find 算法

使用的簡化的 Boyer-Moore 算法,文檔說明是在查找成功的情況下比 std::string 的 find 快 30 倍。benchmark 代碼在FBStringBenchmark.cpp[8]

我自己測試的情況貌似是搜索長字符串的情況會更好些。

判斷大小端
// It's MSVC, so we just have to guess ... and allow an override
#ifdef _MSC_VER
# ifdef FOLLY_ENDIAN_BE
  static constexpr auto kIsLittleEndian = false;
# else
  static constexpr auto kIsLittleEndian = true;
# endif
#else
  static constexpr auto kIsLittleEndian =
  __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__;
#endif

__BYTE_ORDER__ 為預定義宏:[9],值是ORDER_LITTLE_ENDIANORDER_BIG_ENDIANORDER_PDP_ENDIAN中的一個。

一般會這麼使用:

/* Test for a little-endian machine */
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__

c++20 引入了std::endian[10],判斷會更加方便。

(完)

參考資料[1]

benchmark 工具: https://github.com/facebook/folly/blob/master/folly/docs/Benchmark.md

[2]

likely() and unlikely(): https://kernelnewbies.org/FAQ/LikelyUnlikely

[3]

What is the advantage of GCC's __builtin_expect in if else statements?: https://stackoverflow.com/questions/7346929/what-is-the-advantage-of-gccs-builtin-expect-in-if-else-statements

[4]

amd-cmovcc.pdf: https://www.cs.tufts.edu/comp/40-2011f/readings/amd-cmovcc.pdf

[5]

官網 : http://jemalloc.net/

[6]

API 文檔: http://jemalloc.net/jemalloc.3.html

[7]

Scalable memory allocation using jemalloc: https://engineering.fb.com/2011/01/03/core-data/scalable-memory-allocation-using-jemalloc/

[8]

FBStringBenchmark.cpp: https://github.com/facebook/folly/blob/master/folly/test/FBStringTestBenchmarks.cpp.h#L120

[9]

BYTE_ORDER為預定義宏:: https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html

[10]

std::endian: https://en.cppreference.com/w/cpp/types/endian

相關焦點

  • C++:32---IO庫
    一、IO庫I0庫類型和頭文件頭文件類型iostreamistream,wistream
  • C++機器學習庫介紹
    目錄為什麼我們要使用機器學習庫?C++中的機器學習庫SHARK 圖書館MLPACK庫為什麼我們要使用機器學習庫?這是很多新來者都會遇到的問題。庫在機器學習中的重要性是什麼?讓我試著在這一節解釋一下。-std=c++11 -lboost_serialization -lshark -lcblas用Shark實現線性回歸初始化階段我們將從包含線性回歸的庫和頭函數開始:#include <bits/stdc++.h> //所有c++標準庫的頭文件
  • Facebook folly代碼庫學習心得
    folly庫的學習心得獨立有用的小技巧Eventfd.h ---- 針對eventfd系統調用的包裝器。Foreach.h ---- 偽語句(作為宏語句來實現),用於迭代。FBvector的核心優化之一:利用memcpy/memmove來處理"可重定位的類型"!C++功能增強和擴展FBString.h ---- std::string性能優化版本。FBvector.h ---- std::vector性能優化版本。Bits.h ---- 各種位處理實用組件,針對速度而優化。
  • C++機器學習庫介紹 | 文末送書
    這是很多新來者都會遇到的問題,庫對於機器學習中的重要性是什麼?讓我試著在這一節解釋一下。比如說,經驗豐富的專業人士和行業老手已經付出了艱辛的努力,並想出了解決問題的辦法。你更願意使用它,還是願意花幾個小時從頭開始重新創建相同的東西?後一種方法通常沒有什麼意義,尤其是當你在DDL前的工作或學習。
  • C++標準庫和std命名空間
    後來C++引入了命名空間的概念,計劃重新編寫庫,將類、函數等都統一納入一個命名空間中(命名空間的名字是 std)。但是這時已經有很多用老式C++開發的程序了,它們的代碼中並沒有使用命名空間,直接修改原來的庫會帶來一個很嚴重的後果:程式設計師會因為不願花費大量時間修改老式代碼而極力反抗,拒絕使用新標準的C++代碼。
  • 跟我學C++中級篇——STL的學習
    一、c++標準庫C++的標準庫主要包含兩大類,首先是包含C的標準庫的,當然,為了適應c++對一些C庫進行了少許的修改和增加。最重要的當然是面向對象的c++庫;而c++庫又可以分成兩大類,即面向對象的c++庫和標準模板庫,也就是題目中的STL。
  • json for modern c++的使用
    然而C++對json沒有很好的內置支持,因此往往要引用一些第三方庫。而網上很多json的包,形形色色種類繁多,但是要麼功能不夠強大,要麼難以使用。json for modern c++是一款非常好用的json庫,具有語法直觀和使用簡單的特點,並且是用C++11標準編寫的,此外還支持STL和json容器之間的轉換,可謂集方便又強大。
  • 跟我學C++中級篇——STL中的字符串
    一、字符串在傳統的C/C++語言中,對字符串的處理比較麻煩,基本都是用char*來操作,而指針又往往是一個初學者的噩夢。STL為了解決這個問題,提供了std::string這個數據結構,其實它就是一個類,不過其提供了常見的對字符串的操作符的重載,實現在實際工程中經常遇到的字符串的長度計算,拼接和裁剪以及和C類型字符串的轉換。它不算是STL的容器,它只是一個類。
  • C++之字符串類學習總結
    答案是c++中並沒有提供原生的字符串類型。二、在C++標準庫中提供了string類型:string直接支持字符串連接string直接支持字符串的大小比較string直接支持字符串查找和提取string直接支持字符串的插入和替換代碼示例:#include <iostream>#include <string>using namespace std;
  • 在C++中將string轉換為char數組
    convert std::string to char[] or char* data type』。這可以在c_str()和strcpy()函數的幫助下實現。c_str()函數用於返回一個指向數組的指針,該數組包含一個以空結尾的字符序列,該序列表示字符串的當前值。const char* c_str() const ;如果拋出異常,則字符串中沒有任何更改。但是,當需要查找或訪問單個元素時,我們可以使用strcpy()函數將其複製到char數組中。
  • 實現32位簡單版Windows和Linux雙平臺的C++運行庫
    」運行庫實現「章節學到的知識,現編寫」運行庫實現「一文。由於動態庫的實現比靜態庫要複雜,所以MiniCRT僅僅以靜態庫的形式提供給最終用戶,在 Windows下它是minicrt.lib;在Linux下它是minicrt.a。
  • c++11新特性,所有知識點都在這了!
    完美轉發:可以寫一個接受任意實參的函數模板,並轉發到其它函數,目標函數會收到與轉發函數完全相同的實參。正則表達式c++11引入了regex庫更好的支持正則表達式,見代碼:#include <iostream>#include <iterator>#include <regex>#include <string>int main
  • 功能強大的 C++ redis 客戶端庫增加至 acl 項目中
    ,相對於 C/C++ 的庫,JAVA 版的 jedis 要好用的多,jedis 提供了 redis 庫的全命令實現,而 C/C++ 則只提供了協議級實現,使用者需要了解命令發送的格式,而且還得判斷分析所接收數據的格式,使用起來非常繁瑣,acl 庫(跨平臺網絡通信與伺服器框架)的作者在使用 C/C++ 版的 redis 庫時也屢受摧殘,於是其基於 acl 的基礎庫完整實現了 redis 的所有命令,該庫對應於
  • C++算法庫
    注意範圍定義為 [first, last) ,其中 last 指代要查詢或修改的最後元素的後一個元素。執行策略大多數算法擁有接受執行策略的重載。標準算法庫提供三種執行策略:有序、並行及並行加向量,且標準庫提供對應執行策略的類型和對象。
  • 史上最快的字符串格式化庫{fmt}及std::format
    boost::format是C++眾多字符串格式化的庫中非常非常慢的一個。所以不要被boost的名頭騙了。在我剛學C++的時候我就在一本書上見過,說C++編譯器知道每個需要格式化的參數的類型信息,所以完全有可能做出一個比printf更快的格式化庫。這個道理說起來像是沒問題,但是真正變成現實是直到2012年,一個名字叫cppformat的庫的出現。
  • C++庫文件說明及使用方法
    每種類型的文件都有其存在的意義庫文件庫是一些函數和類的集合,其實現了某些特定的功能,是程序的開發免於從頭開始。庫有兩種:靜態連結庫和動態連結庫!在windows下靜態連結庫為.lib,動態連結庫為.dll;Linux下靜態連結庫為.a,動態連結庫為.so。這裡我們只介紹Windows下的庫。1.
  • C/C++中字符串與數字轉換
    c++中的方法,方法三和方法四是C語言庫函數的方法。方法一:c++11中string中添加了下面這些方法幫助完成字符串和數字的相互轉換stod stof stoi stol stold stoll stoul stoull函數原型:float stof (const string& str, size_t* idx = 0);to_string to_wstring
  • c++ fstream + string處理大數據
    一:起因(1)之前處理文本數據時
  • C++17之std::optional
    string> std::optional<int> asInt(const std::string& s){ try { return std::stoi(s); } catch (
  • C++17新特性介紹:std::optional
    過時的解決辦法在前面描述的問題中,如果不採用std::optional,我們還是有辦法解決的。對於上面傳感器讀數的特定問題,我們的函數可以返回一個float的最大值FLT_MAX,當然,這就要求調用這個函數的程序特別處理這個特殊的返回值了。