構造体は、所有者と閲覧者の観点で設計すると分かりやすい。
特に、構造体があるデータを長く保持する設計なら、そのデータを安全に所有できる型をメンバに使うほうが扱いやすい。
一方、std::string_view のような閲覧者型は、参照先の寿命を自分で延ばさないため、メンバとして保持するときは注意が必要である。
所有者型をメンバにする
構造体を所有者にする最も簡単な方法は、各データメンバに所有者型を使うことである。
#include <iostream>
#include <string>
#include <string_view>
struct Owner
{
std::string name {}; // std::string は文字列を所有する
};
struct Viewer
{
std::string_view name {}; // std::string_view は文字列を所有しない
};
std::string getName()
{
std::cout << "Enter a name: ";
std::string name {};
std::cin >> name;
return name;
}
int main()
{
Owner o { getName() };
std::cout << "The owner's name is " << o.name << '\n'; // ok
Viewer v { getName() };
std::cout << "The viewer's name is " << v.name << '\n'; // 未定義動作
}Owner は std::string を持つため、getName() の返り値の内容を自分の中へコピーまたはムーブして保持できる。
そのため、元の一時オブジェクトが破棄されても問題ない。
一方、Viewer の std::string_view は文字列を所有しない。
getName() が返した一時 std::string は初期化式の終わりで破棄されるため、その文字列を指す std::string_view はダングリングする。
設計上の目安
- 構造体がデータを保持する責任を持つなら、所有者型をメンバにする
- 一時的に参照するだけなら、閲覧者型や参照型も使える
- 閲覧者型をメンバにする場合は、参照先の寿命が構造体より長いことを保証する必要がある
補足
std::string_view 自体が悪いわけではない。
関数引数や短時間の読み取り専用ビューとしては便利だが、「保持するメンバ」として使うと寿命問題を起こしやすい。