C++ では、spdlog、g3log、log4cxx、log4cplus、log4qt などのログ ライブラリを使用するためのオプションが多数あります。これらはすべて使いやすく強力ですが、ソースコードの量の点で少し重いものもあります。ここでは、Ali の Yalanting ライブラリから、パフォーマンスに優れた軽量ログ ライブラリ easylog を紹介します。いくつかの単純なファイルのみで、使用するときにヘッダー ファイルをインクルードするだけです。
easylogの紹介
Ali がオープンソース化した軽量で高性能な C++ ログ ライブラリである easylog には、最低限 C++17 のコンパイラ サポートが必要です。これは Alibaba の Yalan Pavilion ライブラリに統合された機能に属しており、コード量が少なく、使いやすく、強力なパフォーマンスを備えています。constexpr コンパイル時の最適化、文字列ビュー クラス std::string_view、サードパーティ ライブラリ ConcurrentQueue (スレッドセーフなロックフリー キュー)、jkj::dragonbox (効率的なフローティング-ポイント番号から文字列への変換ライブラリ)、efvalue::meta_string 文字列メタプログラミングのため、高いパフォーマンスを持っています。
GitHub - purecpp-org/easylog: C++20 ログ ライブラリ
easylogの実装
easylog の実装アイデアは比較的シンプルかつ明確です。ファイルを直接操作するのではなく、キューにログを書き込むだけです。スレッドを開始してキューからデータを取得し、ファイルに書き込みます。コードの量は少なく、実装のアイデアはシンプルですが、C++ のパフォーマンス最適化の新機能が多数使用されており、学習する価値があります。
同時実行キュー
ConcurrentQueue は、追加の同期メカニズムなしで複数のスレッドがキュー上で同時に動作できるようにする、スレッドセーフなキュー データ構造です。これは、同時プログラミングで一般的に使用されるデータ構造の 1 つです。
moodycamel::ConcurrentQueue C++11 で実装されたマルチプロデューサー、マルチコンシューマーのロックフリーキュー。
倉庫の住所:
https://github.com/cameron314/concurrentqueue
利点は次のとおりです。
1. スレッド セーフティ: ConcurrentQueue は、共有リソースを保護するために追加の同期メカニズム (ミューテックスやセマフォなど) を手動で追加することなく、複数のスレッドでエンキューおよびデキュー操作を同時に実行できる組み込みのスレッド セーフティ メカニズムを提供します。これにより、同時プログラミングの複雑さが簡素化されます。
2. 効率的なパフォーマンス: ConcurrentQueue は、同時環境でより優れたパフォーマンスを提供します。ロックフリーのキューやきめ細かいロックなど、いくつかの効率的なアルゴリズムとデータ構造を使用して、競合を減らし、同時実行パフォーマンスを向上させます。
3. 低遅延: ConcurrentQueue は、高い同時実行性と低遅延のシナリオをサポートするように設計されているため、通常は動作遅延が短くなります。これは、迅速に応答し、多数の同時リクエストを処理する必要があるアプリケーションに役立ちます。
4. スケーラビリティ: ConcurrentQueue は、パフォーマンスのボトルネックを発生させることなく、必要に応じてより多くのスレッドに簡単に拡張できます。これは、同時実行性とスループットの高いアプリケーションに適しており、需要に基づいて水平方向に拡張できます。
ConcurrentQueue は、便利で効率的、スレッドセーフなキュー データ構造であり、同時プログラミング シナリオに適しており、より優れたパフォーマンスとスケーラビリティを提供できます。
ドラゴンボックスライブラリ
jkj::dragonbox は、float から string への効率的な変換のための C++ ライブラリです。これは、浮動小数点数を 10 進数文字列として表現するための高速かつ正確な方法を提供し、数値の書式設定やログ記録などのさまざまなアプリケーション シナリオに適しています。
倉庫の住所:
https://github.com/jk-jeon/dragonbox
jkj::dragonbox ライブラリの主な機能と用途:
1. 効率的なパフォーマンス: jkj::dragonbox は、いくつかの効率的なアルゴリズムとテクニックを使用して、浮動小数点数から文字列への高速変換を実現します。ほとんどの場合、標準ライブラリの変換関数よりも高速で、予測可能なパフォーマンスを備えています。
2. 精度: jkj::dragonbox ライブラリは正確な変換結果を提供し、浮動小数点数のすべての有効桁を保持でき、丸めの際に丸め誤差を正しく処理できます。
3. 移植性: jkj::dragonbox ライブラリは、さまざまなオペレーティング システムおよびコンパイラで使用できるクロスプラットフォーム C++ ライブラリです。4. 使いやすさ: jkj::dragonbox ライブラリのインターフェースはシンプルで使いやすく、対応するヘッダー ファイルをインクルードし、対応する変換関数を呼び出すだけで、浮動小数点数から文字列への変換が完了します。
jkj::dragonbox ライブラリは、高いパフォーマンスと精度を必要とするアプリケーション シナリオに適した、効率的で正確、移植可能な浮動小数点から文字列への変換ソリューションを提供します。
constexpr 機能
constexpr は C++11 で導入されたキーワードで、コンパイル時定数 (コンパイル時定数) を宣言するために使用されます。コンパイル時に評価し、コンパイル時に最適化することができ、コンパイル時に計算と初期化を実行する機能を提供します。
1. 定数式: constexpr を使用して、定数式、つまりコンパイル時に値を決定できる式を宣言できます。これらの式はコンパイル時に評価でき、実行時に評価する必要はありません。
2. コンパイル時の最適化: constexpr を使用して宣言された定数式は、プログラムのパフォーマンスを向上させるためにコンパイル時に最適化できます。コンパイラは、実行時に計算を行うことなく、コンパイル時に constexpr 式の結果を計算し、結果の値に直接置き換えることができます。
3. 型チェック: constexpr は、関数、コンストラクター、メンバー関数、およびクラスのメンバー変数を宣言するためにも使用できます。これらの宣言はコンパイル時に型チェックして、constexpr の要件を満たしていることを確認できます。
4. 配列サイズ: constexpr を使用して配列のサイズを宣言できます。つまり、配列のサイズはコンパイル時に決定されます。このようにして、コンパイル時に静的チェックを実行して、配列の範囲外などの問題を回避できます。constexpr を使用すると、コードのパフォーマンスと読みやすさが向上すると同時に、コンパイル時の計算と最適化がさらに可能になります。コンパイル時に評価する機能により、一部の定数の計算を実行時ではなくコンパイル時に完了できるため、プログラムの効率が向上します。
メタ文字列機能
GET_STRING マクロ定義
コンパイル時にファイル名と行番号情報を含む文字列プレフィックスを生成するために使用されます。その機能は、ログ出力などのシナリオで各ログ メッセージにファイル名と行番号を含むプレフィックスを追加して、ログ メッセージのソースを特定することです。
#define TO_STR(s) #s
#define GET_STRING(filename, line) \
[] { \
constexpr auto path = refvalue::meta_string{filename}; \
constexpr size_t pos = \
path.rfind(std::filesystem::path::preferred_separator); \
constexpr auto name = path.substr<pos + 1>(); \
constexpr auto prefix = name + ":" + TO_STR(line); \
return "[" + prefix + "] "; \
}()
実際、コンパイル時に文字列リテラルを生成するメタプログラミング ツールとみなすことができます。これは、コンパイル時に文字列を作成し、コンパイル時に文字列関連の操作と計算を実行する機能を提供します。これは、コンパイル中に文字列を処理するためのテンプレート構造を定義し、いくつかの文字列操作関数を提供します。
#pragma once
#include <algorithm>
#include <array>
#include <cstddef>
#if __has_include(<span>)
#include <compare>
#include <concepts>
#include <span>
#endif
#include <string_view>
#include <utility>
namespace refvalue {
template <std::size_t N>
struct meta_string {
std::array<char, N + 1> elements_;
constexpr meta_string() noexcept : elements_{} {}
constexpr meta_string(const char (&data)[N + 1]) noexcept {
for (size_t i = 0; i < N + 1; i++) elements_[i] = data[i];
}
#if __has_include(<span>)
template <std::size_t... Ns>
constexpr meta_string(std::span<const char, Ns>... data) noexcept
: elements_{} {
auto iter = elements_.begin();
((iter = std::copy(data.begin(), data.end(), iter)), ...);
}
#endif
template <std::size_t... Ns>
constexpr meta_string(const meta_string<Ns>&... data) noexcept : elements_{} {
auto iter = elements_.begin();
((iter = std::copy(data.begin(), data.end(), iter)), ...);
}
#if __has_include(<span>)
template <std::same_as<char>... Ts>
constexpr meta_string(Ts... chars) noexcept requires(sizeof...(Ts) == N)
: elements_{chars...} {}
#endif
constexpr char& operator[](std::size_t index) noexcept {
return elements_[index];
}
constexpr const char& operator[](std::size_t index) const noexcept {
return elements_[index];
}
constexpr operator std::string_view() const noexcept {
return std::string_view{elements_.data(), size()};
}
constexpr bool empty() const noexcept { return size() == 0; }
constexpr std::size_t size() const noexcept { return N; }
constexpr char& front() noexcept { return elements_.front(); }
constexpr const char& front() const noexcept { return elements_.front(); }
constexpr char& back() noexcept { return elements_[size() - 1]; }
constexpr const char& back() const noexcept { return elements_[size() - 1]; }
constexpr auto begin() noexcept { return elements_.begin(); }
constexpr auto begin() const noexcept { return elements_.begin(); }
constexpr auto end() noexcept { return elements_.begin() + size(); }
constexpr auto end() const noexcept { return elements_.begin() + size(); }
constexpr char* data() noexcept { return elements_.data(); }
constexpr const char* data() const noexcept { return elements_.data(); };
constexpr const char* c_str() const noexcept { return elements_.data(); }
constexpr bool contains(char c) const noexcept {
return std::find(begin(), end(), c) != end();
}
constexpr bool contains(std::string_view str) const noexcept {
return str.size() <= size()
? std::search(begin(), end(), str.begin(), str.end()) != end()
: false;
}
static constexpr size_t substr_len(size_t pos, size_t count) {
if (pos >= N) {
return 0;
}
else if (count == std::string_view::npos || pos + count > N) {
return N - pos;
}
else {
return count;
}
}
template <size_t pos, size_t count = std::string_view::npos>
constexpr meta_string<substr_len(pos, count)> substr() const noexcept {
constexpr size_t n = substr_len(pos, count);
meta_string<n> result;
for (int i = 0; i < n; ++i) {
result[i] = elements_[pos + i];
}
return result;
}
constexpr size_t rfind(char c) const noexcept {
return std::string_view(*this).rfind(c);
}
constexpr size_t find(char c) const noexcept {
return std::string_view(*this).find(c);
}
};
template <std::size_t N>
meta_string(const char (&)[N]) -> meta_string<N - 1>;
#if __has_include(<span>)
template <std::size_t... Ns>
meta_string(std::span<const char, Ns>...) -> meta_string<(Ns + ...)>;
#endif
template <std::size_t... Ns>
meta_string(const meta_string<Ns>&...) -> meta_string<(Ns + ...)>;
#if __has_include(<span>)
template <std::same_as<char>... Ts>
meta_string(Ts...) -> meta_string<sizeof...(Ts)>;
#endif
#if __has_include(<span>)
template <std::size_t M, std::size_t N>
constexpr auto operator<=>(const meta_string<M>& left,
const meta_string<N>& right) noexcept {
return static_cast<std::string_view>(left).compare(
static_cast<std::string_view>(right)) <=> 0;
}
#endif
template <std::size_t M, std::size_t N>
constexpr bool operator==(const meta_string<M>& left,
const meta_string<N>& right) noexcept {
return static_cast<std::string_view>(left) ==
static_cast<std::string_view>(right);
}
template <std::size_t M, std::size_t N>
constexpr bool operator==(const meta_string<M>& left,
const char (&right)[N]) noexcept {
return static_cast<std::string_view>(left) ==
static_cast<std::string_view>(meta_string{right});
}
template <std::size_t M, std::size_t N>
constexpr auto operator+(const meta_string<M>& left,
const meta_string<N>& right) noexcept {
return meta_string{left, right};
}
template <std::size_t M, std::size_t N>
constexpr auto operator+(const meta_string<M>& left,
const char (&right)[N]) noexcept {
meta_string<M + N - 1> s;
for (size_t i = 0; i < M; ++i) s[i] = left[i];
for (size_t i = 0; i < N; ++i) s[M + i] = right[i];
return s;
}
template <std::size_t M, std::size_t N>
constexpr auto operator+(const char (&left)[M],
const meta_string<N>& right) noexcept {
meta_string<M + N - 1> s;
for (size_t i = 0; i < M - 1; ++i) s[i] = left[i];
for (size_t i = 0; i < N; ++i) s[M + i - 1] = right[i];
return s;
}
#if __has_include(<span>)
template <meta_string S, meta_string Delim>
struct split_of {
static constexpr auto value = [] {
constexpr std::string_view view{S};
constexpr auto group_count = std::count_if(S.begin(), S.end(),
[](char c) {
return Delim.contains(c);
}) +
1;
std::array<std::string_view, group_count> result{};
auto iter = result.begin();
for (std::size_t start_index = 0, end_index = view.find_first_of(Delim);;
start_index = end_index + 1,
end_index = view.find_first_of(Delim, start_index)) {
*(iter++) = view.substr(start_index, end_index - start_index);
if (end_index == std::string_view::npos) {
break;
}
}
return result;
}();
};
template <meta_string S, meta_string Delim>
inline constexpr auto&& split_of_v = split_of<S, Delim>::value;
template <meta_string S, meta_string Delim>
struct split {
static constexpr std::string_view view{S};
static constexpr auto value = [] {
constexpr auto group_count = [] {
std::size_t count{};
std::size_t index{};
while ((index = view.find(Delim, index)) != std::string_view::npos) {
count++;
index += Delim.size();
}
return count + 1;
}();
std::array<std::string_view, group_count> result{};
auto iter = result.begin();
for (std::size_t start_index = 0, end_index = view.find(Delim);;
start_index = end_index + Delim.size(),
end_index = view.find(Delim, start_index)) {
*(iter++) = view.substr(start_index, end_index - start_index);
if (end_index == std::string_view::npos) {
break;
}
}
return result;
}();
};
template <meta_string S, meta_string Delim>
inline constexpr auto&& split_v = split<S, Delim>::value;
template <meta_string S, char C>
struct remove_char {
static constexpr auto value = [] {
struct removal_metadata {
decltype(S) result;
std::size_t actual_size;
};
constexpr auto metadata = [] {
auto result = S;
auto removal_end = std::remove(result.begin(), result.end(), C);
return removal_metadata{
.result{std::move(result)},
.actual_size{static_cast<std::size_t>(removal_end - result.begin())}};
}();
meta_string<metadata.actual_size> result;
std::copy(metadata.result.begin(),
metadata.result.begin() + metadata.actual_size, result.begin());
return result;
}();
};
template <meta_string S, char C>
inline constexpr auto&& remove_char_v = remove_char<S, C>::value;
template <meta_string S, meta_string Removal>
struct remove {
static constexpr auto groups = split_v<S, Removal>;
static constexpr auto value = [] {
return []<std::size_t... Is>(std::index_sequence<Is...>) {
return meta_string{std::span<const char, groups[Is].size()>{
groups[Is].data(), groups[Is].size()}...};
}
(std::make_index_sequence<groups.size()>{});
}();
};
template <meta_string S, meta_string Removal>
inline constexpr auto&& remove_v = remove<S, Removal>::value;
#endif
} // namespace refvalue
refvalue::meta_stringの主な機能は次のとおりです。
1. コンパイル時の文字列操作: refvalue::meta_string により、コンパイル時に連結、インターセプト、比較などの文字列に対する操作が可能になります。これにより、文字列関連の計算と処理を実行時ではなくコンパイル時に実行できるようになります。
2. コード生成: refvalue::meta_string を使用してコード、特に文字列関連のコードを生成できます。コンパイル時に文字列リテラルを生成すると、コードの一部として文字列を含めて、コード生成中に文字列を操作および処理できます。
3. メタデータ処理: refvalue::meta_string を使用して、クラス名、関数名などの文字列型のメタデータを処理できます。コンパイル時に文字列リテラルを生成すると、これらの文字列をメタプログラミングで型や識別子の名前として使用できます。
meta_string
は、パフォーマンスを向上させるためにコンパイル時に文字列操作を実行するように設計されています。この操作はコンパイル時に行われるため、実行時の文字列操作のオーバーヘッドを回避でき、プログラムの実行効率が向上します。たとえば、 を使用するとmeta_string
、実行時ではなくコンパイル時に、文字列の連結、部分文字列の抽出、文字の削除などを行うことができます。これにより、コードがより柔軟で効率的になります。
つまり、refvalue::meta_string は、コンパイル時に文字列リテラルを生成し、文字列関連の操作と処理を実行するためのメタプログラミング ツールです。これを使用すると、コードの生成、メタデータの処理、コンパイル時の文字列の計算と操作の実行が可能になり、コンパイル時に文字列を処理する機能が提供されます。
このコード内の一部の関数には、std::span や std::same_as などの機能など、C++20 以降のサポートが必要な場合があることに注意してください。したがって、これらの機能を使用する場合は、コンパイラーがそれらの機能をサポートしていることを確認する必要があります。
メタ文字列の使用例
#include <iostream>
#include "meta_string.hpp"
int main() {
using namespace refvalue;
constexpr auto str = meta_string("hello world");
static_assert(str.size() == 6, "字符串长度不正确");
std::cout << "字符串: " << str << std::endl;
std::cout << "字符串长度: " << str.size() << std::endl;
constexpr auto subStr = str.substr<3, 2>();
std::cout << "子串: " << subStr << std::endl;
std::cout << "子串长度: " << subStr.size() << std::endl;
constexpr auto concatStr = str + meta_string(" 欢迎!");
std::cout << "拼接后的字符串: " << concatStr << std::endl;
constexpr auto removedStr = remove_v<concatStr, meta_string("迎")>;
std::cout << "移除后的字符串: " << removedStr << std::endl;
return 0;
}
上の例では、meta_string
オブジェクトが作成および初期化されてから、に対するstr
いくつかの操作を示します。meta_string
std::cout を使用して元の文字列とその長さを出力し、substr
関数を使用して部分文字列を抽出し、開始位置と長さを指定して、部分文字列とその長さを出力します。+
演算子を使用してstr
別の演算子を連結しmeta_string
、結果を に保存しますconcatStr
。最後に、部分文字列「welcome」がremove_v
関数concatStr
で削除され、結果がremovedStr
に保存されます。constexpr
キーワードは、これらの操作がコンパイル時に確実に評価されるようにするために使用されます。
meta_string
実行時ではなくコンパイル時に文字列操作を許可します。これは、文字列処理の結果がプログラムの実行前に決定されることを意味し、プログラムのパフォーマンスと効率を向上させることができます。文字列処理は実行時ではなくコンパイル時に行われるため、プログラム実行中の文字列操作のオーバーヘッドが回避され、実行時の計算とメモリ消費量が削減されます。これは、 easylog で使用される理由でもありmeta_string
、日付やコード行番号などのログ形式のプレフィックス文字が固定されている一部のシーンでは、これを使用するとパフォーマンスが大幅に向上します。
タイプ特性のメカニズム
型特性の概念、直訳すると型抽出です。名前から、型を取得するために使用されることも推測できます。c++ 11 より前の stl では、すでに関連技術が使用されています。たとえば、イテレータは関連型を使用して取得しています。「STL ソース コード解析」に詳しく紹介されています。興味があれば、見に行くことができます。C++ 11 では、型の特性に関連する処理を行うための特別なヘッダー ファイル <type_traits> も導入されました。
簡単に理解すると、C++ の型特性は、コンパイル時に型情報をクエリおよび操作するためのメカニズムです。これらを使用すると、コンパイル時に型に関するプロパティと特性を取得し、それらに対してプログラムすることができます。型の特性は、型がポインタ、参照、配列などであるか、const か volatile 修飾子であるか、コピー構築や移動構築などの操作を実行できるかどうかなど、型に特定の特性があるかどうかを判断するのに役立ちます。 。これらの特性により、コンパイル時に型の特性に応じてさまざまな処理やロジック分岐を実行できます。
簡単に言うと、型特性はコンパイル時に型プロパティをクエリおよび操作するためのツールであり、型の特性に従ってプログラムを作成し、コードの信頼性と柔軟性を向上させるのに役立ちます。
template <typename Return>
struct function_traits<Return (*)() noexcept> {
using parameters_type = void;
using return_type = Return;
};
上に示したコード スニペットでは、function_traits
テンプレートの特殊化が、パラメーターと noexc 指定子を持たない関数ポインターに対して定義されています。Return (*)() noexcept
この特殊化は、テンプレート パラメーターがパターンに一致するとトリガーされます。特殊化では、parameters_type
と の2 つの型エイリアスが定義されていますreturn_type
。この場合、parameters_type
は に設定されvoid
、関数にパラメータがないことを示し、 はreturn_type
テンプレート パラメータ に設定されReturn
、関数の戻り値の型を示します。このコードは、戻り値の型の抽出や関数に特定のシグネチャがあるかどうかの確認など、関数の型に関する情報を抽出または操作する場合に役立ちます。このような型特性function_traits
は、コンパイル時の型情報が必要なテンプレート メタプログラミングまたはジェネリック プログラミング シナリオで使用できます。
理解を深めるために簡単な使用例を示します。
#include <iostream>
#include <type_traits>
// 类型特性,用于检查一个类型是否为指针
template <typename T>
struct is_pointer {
static constexpr bool value = false;
};
template <typename T>
struct is_pointer<T*> {
static constexpr bool value = true;
};
// 使用类型特性
int main() {
std::cout << std::boolalpha;
std::cout << "int* 是指针吗?" << is_pointer<int*>::value << std::endl;
std::cout << "float* 是指针吗?" << is_pointer<float*>::value << std::endl;
std::cout << "double 是指针吗?" << is_pointer<double>::value << std::endl;
return 0;
}
この例では、is_pointer
指定された型がポインターであるかどうかをチェックする、という名前の型特性が定義されています。value
この属性は、に設定された静的メンバー変数を含む構造体テンプレートとして定義されますfalse
。次に、is_pointer<T*>
部分的な特殊化が提供されます。 ここで、T*
はポインター型を示します。この部分特殊化では、value
に設定しますtrue
。関数のtype 属性をmain
使用して、is_pointer
さまざまな型がポインターであるかどうかを確認します。std::cout を使用して結果を出力します。int*
と の場合はポインタ型であるため、float*
の出力はポインタ型ではないため、次のようになります。この例では、型特性を使用してコンパイル時に型チェックを実行し、コンパイル時に型に関する情報を提供する方法を示します。 true
double
false
std::string_view
std::string_view は easylog で広く使用されています。
std::string_view は、C++17 で導入された軽量の文字列ビュー クラスです。
アドバンテージ
1. ゼロオーバーヘッドの抽象化: std::string_view はデータを所有せず、既存の文字列データの単なるビューです。これは、データのコピーのオーバーヘッドなしで、非常に効率的に構築して渡すことができることを意味します。
2. std::string との相互運用: std::string_view は std::string から簡単に構築でき、その逆も同様です。これは、 std::string または std::string_view を受け入れることができる関数の引数として理想的です。
3. セキュリティ: C スタイルの文字列と比較して、std::string_view はタイプセーフであり、潜在的なバッファ オーバーフロー エラーを回避できます。
4. 柔軟性: std::string_view は、std::string に限定されず、任意の連続した文字シーケンスを指すことができます。これにより、非常に多用途かつ柔軟になります。
該当シーン
1. 文字列の抽象化が必要だが、基になる文字列データを所有する必要がない場合。たとえば、関数パラメータでは、文字列データを読み取るだけで済み、そのデータを所有したり変更したりする必要がない場合があります。
2. 文字列ビューが必要な場合、さまざまな文字列タイプ (std::string、char*、const char* など) を指すことができます。3. 不要なコピーのオーバーヘッドを避けるために、文字列データを効率的に構築して渡す必要がある場合。
4. タイプセーフな文字列抽象化が必要な場合、C スタイルの文字列を置き換えることができます。
5. std::string と相互運用できる文字列ビューが必要な場合、std::string または std::string_view を受け入れる関数をサポートします。
要約すると、 std::string_view は、文字列データを効率的に操作し、型の安全性と std::string との相互運用性を提供する軽量の文字列抽象化です。これは、関数の引数として、または基になるデータを所有せずに文字列のビューが必要な場合に最適です。
doctest 単体テスト フレームワーク
easylog では doctest 単体テスト フレームワークが使用されます。doctest は C++ 用の単体テスト フレームワークであり、Google Test (gtest) と比較して、主な利点は次のとおりです。
1. 使いやすさ: doctest の API は非常にシンプルで、テスト ケースを作成するために必要なのは、いくつかのマクロ定義とコメントだけです。対照的に、gtest の API は少し複雑で、テストを定義するためにより多くの関数とクラスが必要です。
2. ライブラリをリンクする必要はありません: doctest はヘッダー ファイル ライブラリであるため、ライブラリ ファイルをリンクする必要はありません。gtest は、libgtest.a と libgtest_main.a の 2 つのライブラリ ファイルをリンクする必要があります。
3. サブテストのサポート: doctest は、テスト ケースを階層的なサブテストに編成することをサポートしています。これにより、テストがより構造化され、読みやすくなります。gtest は現在サブテストをサポートしていません。
4. さまざまなアサーションのサポート: doctest は、等価アサーション、真実アサーション、例外アサーションなど、テスト条件を検証するためのさまざまなアサーション マクロを提供します。gtest はさまざまなアサーションも提供しますが、その数はわずかに少なくなります。
5. ラベル付けとテスト ケースの無視のサポート: doctest はテスト ケースにラベルを追加し、特定のラベルが付いたテスト ケースのみを実行することを選択できます。指定されたテスト ケースの無視もサポートします。gtest はこれら 2 つの機能をサポートしていません。
6. 複数のテスト レポートのサポート: doctest は、XML、JSON、JUnit などの複数の形式でテスト レポートを生成できます。gtest は、Google Test 形式のレポートのみをサポートします。
7. テスト ケースのランダムな順序のサポート: doctest はテスト ケースの実行順序をランダムに変更できます。これは、テスト ケースの順序に依存するバグを検出する場合に役立ちます。gtest は、ランダム化されたテスト順序をサポートしていません。
一般に、doctest は、シンプルで使いやすく、より強力な C++ 単体テスト フレームワークです。gtest と比較すると、より多くの機能と利点があります。使いやすく、一部の C++ プロジェクトの単体テストに適しています。
イージーログの使用
easylog は比較的簡単に使用でき、サンプルを直接参照できます。
std::string filename = "easylog.txt";
std::filesystem::remove(filename);
easylog::init_log(Severity::DEBUG, filename, true, 5000, 1, true);
ELOG_INFO << 42 << " " << 4.5 << 'a' << Severity::DEBUG;
ELOGV(INFO, "test");
ELOGV(INFO, "it is a long string test %d %s", 2, "ok");
int len = 42;
ELOGV(INFO, "rpc header data_len: %d, buf sz: %lu", len, 20);
ELOG(INFO) << "test log";
easylog::flush();
ELOG_INFO << "hello "
<< "easylog";
ELOGI << "same";
ELOG_DEBUG << "debug log";
ELOGD << "debug log";
C++17のサポート
元のウェアハウスはデフォルトでは c++17 をサポートしておらず、最小要件は c++20 であることに注意してください。ただし、いくつかのコードは C++17 をサポートするように変更できます。
type_traits.h では次の定義が有効になります。
namespace std {
template <class T>
struct remove_cvref {
typedef std::remove_cv_t<std::remove_reference_t<T>> type;
};
template <class T>
using remove_cvref_t = typename remove_cvref<T>::type;
} // namespace std
meta-string.h からスパン関連のヘッダー ファイルと定義を削除します。<span> は C++20 の新機能だからです。
その他のリソース
効率的な C++ ロックフリー (ロックフリー) キューmoodycamel::ConcurrentQueue - プログラマーが求めた
https://github.com/jk-jeon/dragonbox
ミラー / Cameron314 / concurrentqueue · GitCode
ミラー / alibaba / yalantinglibs · GitCode
https://github.com/KjellKod/g3log
GitHub - purecpp-org/easylog: C++20 ログ ライブラリ
【ロギングツール】g3log_1_紹介_Bubble spit bubble ahのブログ - CSDNブログ
https://github.com/purecpp-org/easylog/tree/da2ed3a8e74b29a73faa67896dac02e1b7584551
C++ のそれら - std::string_view と std::span bzdww