C++17新特性optional和string_view

1. optional的作用

類模板 std::optional 管理一個可選的容納值,即可以存在也可以不存在的值

一種常見的 optional 使用情況是一個可能失敗的函數的返回值。與其他手段,如 std::pair<T,bool> 相比, optional 良好地處理構造開銷高昂的對象,並更加可讀,因爲它顯式表達意圖。

 std::optional對象只是包含對象的內部內存加上一個布爾標誌。因此,大小通常比包含的對象大一個字節。對象使用與所包含類型相同的對齊方式。然而,std::optional對象不僅僅是向值成員添加布爾標誌功能的結構。例如,如果沒有值,就不會爲所包含的類型調用構造函數(因此,可以爲對象提供沒有值的默認狀態)。支持Move語義。

2. optional使用

std::optional<>爲任意類型的可空實例建模。實例可以是成員、參數或返回值。可以認爲std::optional<>是一個包含0或1個元素的容器。

2.1 std::optional<>返回值

/** @file  optionalT.cpp
*  @note   All Right Reserved.
*  @brief
*  @author xor
*  @date   2019-11-2
*  @note
*  @history
*  @warning
*/
#include <iostream>
#include <optional>
#include <string>

// convert string to int if possible:
std::optional<int> asInt(const std::string& s)
{
    try
    {
        return std::stoi(s);
    }
    catch (...)
    {
        return std::nullopt;
    }
}

std::optional<int> asInt2(const std::string& s)
{
    std::optional<int> ret; // initially no value
    try
    {
        ret = std::stoi(s);
    }
    catch (...)
    {
    }
    return ret;
}

int main()
{
    for (auto s : { "42", " 077", "hello", "0x33" })
    {
        // convert s to int and use the result if possible:
        std::optional<int> oi = asInt(s);
        if (oi) {
            std::cout << "convert '" << s << "' to int: " << *oi << "\n";
        }
        else {
            std::cout << "can't convert '" << s << "' to int\n";
        }
    }
}

 

has_value()用來檢查是否有返回值,如果有通過value()來獲取。value()比操作符*更安全,因爲沒有值而調用該接口的話會拋出異常。操作符*只有你確認有值的情況下才能使用,否則程序會出現未定義行爲。

    注意,可以通過使用新的類型std::string_view來改進asInt()。

2.2 std::optional<>參數和數據成員

/** @file  optionalT.cpp
*  @note   All Right Reserved.
*  @brief
*  @author xor
*  @date   2019-11-2
*  @note
*  @history
*  @warning
*/

#include <iostream>
#include <string>
#include <optional>

class Name
{
private:
    std::string first;
    std::optional<std::string> middle;
    std::string last;
public:
    Name(std::string f,
        std::optional<std::string> m,
        std::string l)
        : first{ std::move(f) }, middle{ std::move(m) }, last{ std::move(l) }
    {
    }
    friend std::ostream& operator << (std::ostream& strm, const Name& n)
    {
        strm << n.first << ' ';
        if (n.middle) {
            strm << n.middle.value() << ' ';
        }
        return strm << n.last;
    }
};
int main()
{
    Name n{ "Jim", std::nullopt, "Knopf" };
    std::cout << n << '\n';
    Name m{ "Donald", "Ervin", "Knuth" };
    std::cout << m << '\n';

    return 0;
}

 

可選對象還使用<utility>中定義的對象std::in_place(類型爲std::in_place_t)來初始化帶有多個參數的可選對象的值(參見下面)。

3. 構造函數

可以創建一個沒有值的可選對象。在這種情況下,必須指定包含的類型:
std::optional<int> o1;
std::optional<int> o2(std::nullopt);

這裏不會爲所包含的類型調用任何構造函數。

可以傳遞一個值來初始化所包含的類型。根據推導指南,不必指定所包含的類型,如下:

std::optional o3{42}; // deduces optional<int>
std::optional<std::string> o4{"hello"};
std::optional o5{"hello"}; // deduces optional<const char*>

 

要初始化一個具有多個參數的可選對象,必須創建該對象或將std::in_place添加爲第一個參數(所包含的類型無法推斷):

std::optional o6{std::complex{3.0, 4.0}};
std::optional<std::complex<double>> o7{std::in_place, 3.0, 4.0};

注意,第二種形式避免創建臨時對象。通過使用這種形式,甚至可以傳遞初始化器列表和附加參數:

// initialize set with lambda as sorting criterion:
auto sc = [] (int x, int y)
{
return std::abs(x) < std::abs(y);
};
std::optional<std::set<int,decltype(sc)>> o8{std::in_place, {4, 8, -7, -2, 0, 5}, sc};

 

可以複製可選對象(包括類型轉換):
std::optional o5{"hello"}; // deduces optional<const char*>
std::optional<std::string> o9{o5}; // OK

 

還有一個方便的函數make_optional<>(),它允許使用單個或多個參數初始化(不需要in_place參數)。像往常一樣make……函數推導:

auto o10 = std::make_optional(3.0); // optional<double>
auto o11 = std::make_optional("hello"); // optional<const char*>
auto o12 = std::make_optional<std::complex<double>>(3.0, 4.0);
然而,注意,沒有構造函數接受一個值並根據它的值來決定是使用值初始化一個可選值還是使用nullopt。可以使用操作符?:,例如:

std::multimap<std::string, std::string> englishToGerman;
...
auto pos = englishToGerman.find("wisdom");
auto o13 = pos != englishToGerman.end()? std::optional{pos->second}: std::nullopt;

o13初始化爲std::optional<std::string>,這是由於類模板參數的推導std::optionalf(pos->second)。對於std::nullopt類模板參數推導不起作用,但是運算符?:在推導表達式的結果類型時也將其轉換爲這種類型。

 4. string_view

string_view 是C++17所提供的用於處理只讀字符串的輕量對象。這裏後綴 view 的意思是隻讀的視圖。

    • 通過調用 string_view 構造器可將字符串轉換爲 string_view 對象。
      string 可隱式轉換爲 string_view。
    • string_view 是隻讀的輕量對象,它對所指向的字符串沒有所有權。
    • string_view通常用於函數參數類型,可用來取代 const char* 和 const string&。
      string_view 代替 const string&,可以避免不必要的內存分配。
    • string_view的成員函數即對外接口與 string 相類似,但只包含讀取字符串內容的部分。
      string_view::substr()的返回值類型是string_view,不產生新的字符串,不會進行內存分配。
      string::substr()的返回值類型是string,產生新的字符串,會進行內存分配。
    • string_view字面量的後綴是 sv。(string字面量的後綴是 s)

 

5. 示例

 

/** @file  optionalT.cpp
*  @note   All Right Reserved.
*  @brief
*  @author xor
*  @date   2019-11-2
*  @note   
*  @history
*  @warning
*/
#include <string>
#include <functional>
#include <iostream>
//#include <optional>
#include <experimental/optional>//試驗階段
using namespace std;
// optional 可用作可能失敗的工廠的返回類型
std::optional<std::string> create(bool b) {
    if(b)
        return "Godzilla";
    else
        return {};
}

// 能用 std::nullopt 創建任何(空的) std::optional
auto create2(bool b) {
    return b ? std::optional<std::string>{"Godzilla"} : std::nullopt;
}

// std::reference_wrapper 可用於返回引用
auto create_ref(bool b) {
    static std::string value = "Godzilla";
    return b ? std::optional<std::reference_wrapper<std::string>>{value}
             : std::nullopt;
}

int main()
{
    std::cout << "create(false) returned "
              << create(false).value_or("empty") << '\n';

    // 返回 optional 的工廠函數可用作 while 和 if 的條件
    if (auto str = create2(true)) {
        std::cout << "create2(true) returned " << *str << '\n';
    }

    if (auto str = create_ref(true)) {
        // 用 get() 訪問 reference_wrapper 的值
        std::cout << "create_ref(true) returned " << str->get() << '\n';
        str->get() = "Mothra";
        std::cout << "modifying it changed it to " << str->get() << '\n';
    }
}

 

 

#include <iostream>
#include <optional>
#include <string_view>
 
using namespace std;
 
optional<size_t> find_last(string_view string, char to_find, optional<size_t> start_index = nullopt)
{
    if (string.empty())
        return nullopt;
 
    size_t index = start_index.value_or(string.size() - 1);
 
    while (true)
    {
        if (string[index] == to_find) return index;
        if (index == 0) return nullopt;
        --index;
    }
}
 
int main()
{
    const auto string = "Growing old is mandatory; growing up is optional.";
    
    const optional<size_t> found_a{ find_last(string, 'a') };
    if (found_a)
        cout << "Found the last a at index " << *found_a << endl;
 
    const auto found_b{ find_last(string, 'b') };
    if (found_b)
        cout << "Found the last b at index " << found_b.value() << endl;
 
    const auto found_early_i(find_last(string, 'i', 10));
    if (found_early_i != nullopt)
        cout << "Found an early i at index " << *found_early_i << endl;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章