更改集的預設排序

setmultiset 有預設的比較方法,但在某些情況下你可能需要過載它們。

讓我們假設我們將字串值儲存在一個集合中,但我們知道這些字串只包含數值。預設情況下,排序將是字典字串比較,因此順序與數字排序不匹配。如果要應用與 int 值相同的排序,則需要一個仿函式來過載 compare 方法:

#include <iostream>
#include <set>
#include <stdlib.h> 

struct custom_compare final
{
    bool operator() (const std::string& left, const std::string& right) const
    {
        int nLeft = atoi(left.c_str());
        int nRight = atoi(right.c_str());
        return nLeft < nRight;
    }
};

int main ()
{
    std::set<std::string> sut({"1", "2", "5", "23", "6", "290"});
  
    std::cout << "### Default sort on std::set<std::string> :" << std::endl;
    for (auto &&data: sut)
        std::cout << data << std::endl;
  
    std::set<std::string, custom_compare> sut_custom({"1", "2", "5", "23", "6", "290"},
                                                     custom_compare{}); //< Compare object optional as its default constructible.
  
    std::cout << std::endl << "### Custom sort on set :" << std::endl;
    for (auto &&data : sut_custom)
        std::cout << data << std::endl;
  
    auto compare_via_lambda = [](auto &&lhs, auto &&rhs){ return lhs > rhs; };
    using set_via_lambda = std::set<std::string, decltype(compare_via_lambda)>;
    set_via_lambda sut_reverse_via_lambda({"1", "2", "5", "23", "6", "290"},
                                          compare_via_lambda);
  
    std::cout << std::endl << "### Lambda sort on set :" << std::endl;
    for (auto &&data : sut_reverse_via_lambda)
        std::cout << data << std::endl;

    return 0;
}

輸出將是:

### Default sort on std::set<std::string> :
1
2
23
290
5
6
### Custom sort on set :
1
2
5
6
23
290  

### Lambda sort on set :
6
5
290
23
2
1

在上面的示例中,可以找到 3 種不同的方法來向 std::set 新增比較操作,每種方法在其自己的上下文中都很有用。

預設排序

這將使用鍵的比較運算子(第一個模板引數)。通常,金鑰已經為 std::less<T> 函式提供了一個很好的預設值。除非此功能是專用的,否則它使用物件的 operator<。當其他程式碼也嘗試使用某種排序時,這尤其有用,因為這樣可以在整個程式碼庫中保持一致性。

以這種方式編寫程式碼將減少在金鑰更改為 API 時更新程式碼的工作量,例如:包含 2 個成員的類,這些成員將更改為包含 3 個成員的類。通過更新類中的 operator<,所有例項都將更新。

正如你所料,使用預設排序是合理的預設值。

自定義排序

當預設比較不符合時,通常使用帶有比較運算子的物件新增自定義排序。在上面的例子中,這是因為字串是指整數。在其他情況下,當你想根據它們所引用的物件比較(智慧)指標或者因為你需要不同的約束來進行比較時(例如:將 std::pairfirst 的值進行比較),它經常被使用。

建立比較運算子時,這應該是一個穩定的排序。如果在插入後比較運算子的結果發生更改,則你將具有未定義的行為。作為一種好的做法,比較運算子應該只使用常量資料(const 成員,const 函式……)。

如上例所示,你經常會遇到沒有成員的類作為比較運算子。這導致預設建構函式和複製建構函式。預設建構函式允許你在構造時省略例項,並且需要複製建構函式,因為該集合採用了 compare 運算子的副本。

Lambda 排序

Lambda 是編寫函式物件的較短方法。這允許在較少的行上編寫比較運算子,使整個程式碼更具可讀性。

使用 lambdas 的缺點是每個 lambda 在編譯時都會獲得一個特定的型別,因此 decltype(lambda) 對於同一個編譯單元(cpp 檔案)的每次編譯都會與多個編譯單元(當通過標頭檔案包含時)不同。因此,建議在標頭檔案中使用函式物件作為比較運算子。

當在函式的區域性範圍內使用 std::set 時經常遇到這種結構,而當用作函式引數或類成員時,首選函式物件。

其他排序選項

由於 std::set 的比較運算子是模板引數,因此所有可呼叫物件都可以用作比較運算子,上面的示例只是特定情況。這些可呼叫物件的唯一限制是:

  • 它們必須是可複製的
  • 它們必須可以使用鍵型別的 2 個引數進行呼叫。 (允許隱式轉換,但不推薦,因為它會影響效能)