構造体は、所有者と閲覧者の観点で設計すると分かりやすい。

特に、構造体があるデータを長く保持する設計なら、そのデータを安全に所有できる型をメンバに使うほうが扱いやすい。 一方、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'; // 未定義動作
}

Ownerstd::string を持つため、getName() の返り値の内容を自分の中へコピーまたはムーブして保持できる。 そのため、元の一時オブジェクトが破棄されても問題ない。

一方、Viewerstd::string_view は文字列を所有しない。 getName() が返した一時 std::string は初期化式の終わりで破棄されるため、その文字列を指す std::string_view はダングリングする。

設計上の目安

  • 構造体がデータを保持する責任を持つなら、所有者型をメンバにする
  • 一時的に参照するだけなら、閲覧者型や参照型も使える
  • 閲覧者型をメンバにする場合は、参照先の寿命が構造体より長いことを保証する必要がある

補足

std::string_view 自体が悪いわけではない。 関数引数や短時間の読み取り専用ビューとしては便利だが、「保持するメンバ」として使うと寿命問題を起こしやすい。

関連

参考