【現代C++】性能制御のツールボックスのstring_view
本稿では,
一、背景
日常のC/C++プログラミングでは、関数にデータを渡すなど、データの伝達操作をよく行います.データの消費メモリが大きい場合、データのコピーを減らすことで、プログラムのパフォーマンスを効果的に向上させることができます.Cではポインタがこの目的を達成する標準的なデータ構造であり、C++はより安全性の高い参照タイプを導入している.したがって、C++で伝達されたデータが読み取り専用であれば、文字列の文字面値、文字配列、文字列ポインタの伝達は依然としてデータコピーの3種類の低級データ型が
C++17には
二、std::string_view
名前から、データベース・ビューをクラス比することができます.
文字列ビューを使用するには、
2.1コンストラクタ
基本的には自己解釈であり、唯一説明しなければならないのは、なぜ私たちのコード
実際には、
したがって、 に変換する. .
2.2カスタム文字数
カスタム字面量もC++17に追加された特性で、定数の読みやすさが向上しました.次のコードはcppreferenceの値をとり,カスタムフォント値と文字列の意味の違いをよく説明できる.
出力:
以上の例は両者の意味の違いをよく見ることができ、
2.3メンバー関数
次に、そのメンバー関数を列挙します.関数の戻り値は無視され、関数にリロードがある場合は、カッコ内に
関数リストから見ると、 モディファイヤの3つの関数は、
それ以外に、関数名は基本的に自己解釈されます.
2.4例
Haskellには共通の関数
string-バージョン
上記の例では、
このバージョンでは、
三、落とし穴の使用
世の中には無料の昼食はありません. を含まない場合がある.
のように
2.
3.
四、まとめ
私の公衆番号に注目してくださいね.
string_view
に導入された背景から,その関連知識点と使用方法を順に紹介し,次によく見られる使用トラップについて説明し,最後にこのタイプについてまとめた.一、背景
日常のC/C++プログラミングでは、関数にデータを渡すなど、データの伝達操作をよく行います.データの消費メモリが大きい場合、データのコピーを減らすことで、プログラムのパフォーマンスを効果的に向上させることができます.Cではポインタがこの目的を達成する標準的なデータ構造であり、C++はより安全性の高い参照タイプを導入している.したがって、C++で伝達されたデータが読み取り専用であれば、
const string&
はC++の天然の方法となる.しかし、これは完璧ではありません.実践から見ると、少なくとも以下のいくつかの問題があります.string
タイプと異なり、入力時にコンパイラは暗黙的な変換を行う必要があります.すなわち、これらのデータをコピーしてstring
一時オブジェクトを生成する必要があります.const string&
が指すのは、実際にはこの一時的なオブジェクトです.通常、文字列の文字面値は小さく、性能損失は無視できます.ただし、文字列ポインタや文字配列が大きい場合(ファイルの内容を読み取るなど)、頻繁なメモリ割り当てやデータコピーが発生し、プログラムのパフォーマンスに深刻な影響を及ぼす場合があります.substr
O(n)複雑度これは特によく使われる関数であり、std::string
にこの関数が提供されているのが幸いであり、米国では不足しているのは、毎回新しく生成されたサブストリングを返し、性能のホットスポットを引き起こしやすいことである.実は私たちの本意は元の文字列を変えるわけではありませんが、どうして元の文字列に基づいて返さないのですか.C++17には
string_view
が導入されており,以上の2つの問題をうまく解決できる.二、std::string_view
名前から、データベース・ビューをクラス比することができます.
view
は、このタイプがデータに記憶領域を割り当てることがなく、読み取りにのみ使用できることを示します.このデータ型は、{ , }
の2つの要素によって表すことができ、実際には、このデータ型のインスタンスは、元のデータを具体的に記憶することはなく、指向するデータの開始ポインタと長さだけを記憶するので、このオーバーヘッドは非常に小さい.文字列ビューを使用するには、
を導入する必要があります.次に、このデータ型の主なAPIについて説明します.これらのAPIは基本的にconstexpr
で修飾されているので、コンパイル時に文字列の文字面値をうまく処理することができ、プログラム効率を向上させることができる.2.1コンストラクタ
constexpr string_view() noexcept;
constexpr string_view(const string_view& other) noexcept = default;
constexpr string_view(const CharT* s, size_type count);
constexpr string_view(const CharT* s);
基本的には自己解釈であり、唯一説明しなければならないのは、なぜ私たちのコード
string_view foo(string("abc"))
がコンパイルできるのか、なぜ対応する構造関数がないのかということです.実際には、
string
クラスがstring
からstring_view
の変換オペレータを再ロードしているためです.したがって、
operator std::basic_string_view() const noexcept;
は実際に2つのステップを実行しました.string_view foo(string("abc"))
がstring("abc")
のオブジェクトa string_view
使用対象本編string_view
から導入した背景,2.2カスタム文字数
カスタム字面量もC++17に追加された特性で、定数の読みやすさが向上しました.次のコードはcppreferenceの値をとり,カスタムフォント値と文字列の意味の違いをよく説明できる.
#include
#include
int main()
{
using namespace std::literals;
std::string_view s1 = "abc\0\0def";
std::string_view s2 = "abc\0\0def"sv;
std::cout << "s1: " << s1.size() << " \"" << s1 << "\"
";
std::cout << "s2: " << s2.size() << " \"" << s2 << "\"
";
}
出力:
s1: 3 "abc"
s2: 8 "abc^@^@def"
以上の例は両者の意味の違いをよく見ることができ、
string_view
は文字列にとって、文字列の終わりを表す特殊な意味があり、文字列ビューはcareではなく、実際の文字の個数に関心を持っている.2.3メンバー関数
次に、そのメンバー関数を列挙します.関数の戻り値は無視され、関数にリロードがある場合は、カッコ内に
\0
で埋め込まれます.これにより、全体的な輪郭を持つことができます.//
begin()
end()
cbegin()
cend()
rbegin()
rend()
crbegin()
crend()
//
size()
length()
max_size()
empty()
//
operator[](size_type pos)
at(size_type pos)
front()
back()
data()
//
remove_prefix(size_type n)
remove_suffix(size_type n)
swap(basic_string_view& s)
copy(charT* s, size_type n, size_type pos = 0)
string_view substr(size_type pos = 0, size_type n = npos)
compare(...)
starts_with(...)
ends_with(...)
find(...)
rfind(...)
find_first_of(...)
find_last_of(...)
find_first_not_of(...)
find_last_not_of(...)
関数リストから見ると、
...
の読み取り専用関数とほぼ一致し、string
を使用する方法でstring_view
とほぼ一致する.いくつかの点で特に説明する必要があります.string
のstring_view
関数の時間的複雑さはO(1)であり,背景部分の第2の問題を解決した.substr
のデータポインタのみを変更し、ポインタのデータは変更しません.それ以外に、関数名は基本的に自己解釈されます.
2.4例
Haskellには共通の関数
string_view
があり、文字列を行に切断してコンテナに格納します.C++で実現しますstring-バージョン
#include
#include
#include
#include
#include
void lines(std::vector<:string> &lines, const std::string &str) {
auto sep{"
"};
size_t start{str.find_first_not_of(sep)};
size_t end{};
while (start != std::string::npos) {
end = str.find_first_of(sep, start + 1);
if (end == std::string::npos)
end = str.length();
lines.push_back(str.substr(start, end - start));
start = str.find_first_not_of(sep, end + 1);
}
}
lines
タイプで分割する文字列を受信しましたが、大きなメモリを指す文字ポインタを入力すると、プログラムの効率に影響します.const std::string &
を使用すると、string_view-バージョン#include
#include
#include
#include
#include
#include
void lines(std::vector<:string> &lines, std::string_view str) {
auto sep{"
"};
size_t start{str.find_first_not_of(sep)};
size_t end{};
while (start != std::string_view::npos) {
end = str.find_first_of(sep, start + 1);
if (end == std::string_view::npos)
end = str.length();
lines.push_back(std::string{str.substr(start, end - start)});
start = str.find_first_not_of(sep, end + 1);
}
}
上記の例では、
std::string_view
タイプをstring
に変更するだけで性能の向上が得られた.一般に、プログラム内のstring_view
をstring
に置き換えるプロセスは、両者のメンバー関数の類似性のおかげで比較的直感的である.しかし、すべての「翻訳」プロセスがそうであるわけではありません.例えば、void lines(std::vector<:string> &lines, const std::string& str) {
std::stringstream ss(str);
std::string line;
while (std::getline(ss, line, '
')) {
lines.push_back(line);
}
}
このバージョンでは、
string_view
を使用してstringstream
関数を実装します.lines
は、対応する構造関数がstringstream
タイプのパラメータを受信していないため、直接置換することはできないので、翻訳プロセスは複雑である.三、落とし穴の使用
世の中には無料の昼食はありません.
string_view
の不適切な使用も一連の問題をもたらす.string_view
の範囲内の文字は、string_view
のように
#include
#include
int main() {
std::string_view str{"abc", 1};
std::cout << str.data() << std::endl;
return 0;
}
\0
を印刷するつもりでしたが、a
が出力されました.これは、文字列に関連する関数には、互換性Cの約束があるためです.abc
は文字列の末尾を表します.上記のプログラムは、開始から文字列終了までのすべての文字を印刷し、\0
に含まれる有効文字はstr
であるが、a
はcout
を認識する.このメモリ空間には合法的な文字列末尾文字があり、\0
がstr
のない文字配列を指している場合、プログラムにメモリの問題が発生する可能性が高いので、\0
タイプのデータを受信文字列の関数に転送するときは注意しなければなりません.2.
string_view
から[const] char*
オブジェクトを構築する時間複雑度string_view
これは、文字列の長さを取得するために最初から巡回する必要があるためである.O(n)
のタイプに対していくつかの[const] char*
の動作のみである場合、O(1)
を直接使用するよりも、[const] char*
に移行するのは性能上の利点がない.string_view
const string&
に比べてコピーの損失が少なくなったにすぎない.実際にはstring_view
ですべての文字列を受信することができますが、このタイプはあまりにも下位で、使用しにくいです.場合によっては、[const] char*
に移行するのは、string_view
のようないくつかの関数を使用したいだけかもしれません.3.
substr
が指すコンテンツのライフサイクルは、それ自体よりも短い可能性があります.string_view
は、コンテンツを指す所有権を持っていません.Rustの用語では、一時的なstring_view
(借用)にすぎません.所有者が事前に解放された場合、これらのコンテンツを使用している場合は、borrow
(dangling pointer)やサスペンションリファレンス(dangling references)に似たメモリの問題が発生します.Rustは、コンパイル時に変数のライフサイクルを分析するメカニズムを備えており、
のリソースが使用中に解放されないことを保証していますが、C++はこのような検査がなく、人工的な保証が必要です.以下に、典型的な問題点を示します.std::string_view sv = std::string{"hello world"};
string_view foo() {
std::string s{"hello world"};
return string_view{s};
}
auto id(std::string_view sv) { return sv; }
int main() {
std::string s = "hello";
auto sv = id(s + " world");
}
四、まとめ
borrow
はいくつかの痛みを解決したが、ポインタと引用のいくつかの古い問題も導入した.C++標準はこのタイプにあまり制約を与えていません.これは、通常の変数のように多くの方法で使用することができます.例えば、パラメータを伝達することができ、関数として値を返すことができ、一般的な変数を作ることができ、容器に入れることができます.シーンの使用が複雑になるにつれて、人工的に指向されるコンテンツのライフサイクルが十分に長くなることは保証されません.したがって、推奨される使用方法は、関数パラメータとしてのみ使用されるため、このパラメータが関数内でのみ使用され、伝達されない場合は安全である.私の公衆番号に注目してくださいね.