47325


C++でメンバ関数 (getter) から文字列値を返したい場合、その返り値の型はおおよそ次の3つに分類できるかと思います。

  1. `std::string`
[code]     std::string getString() const {

        return m_member;
    }
    
[/code]

新しく `std::string` の実体を生成するパターンです。  
例えば`std::filesystem::path::*string()`はこのパターンにあたります。

  2. 参照 (`const char*`, `std::string_view`, `const std::string&` など)
[code]     std::string_view getStringRef() const {

        return m_member;
    }
    
[/code]

メンバ変数の参照を何らかの形で表現した型を返すパターンです。  
LLVMのAPIにはこのパターンが見られます。

  3. スマートポインタ (`std::shared_ptr<const std::string>` など)
[code]     std::shared_ptr<const std::string> getStringPtr() const {

        return { shared_from_this(), &m_member };
    }
    
[/code]

メンバ変数のスマートポインタを返すパターンです。  
参照カウントはthisと共有されます。  
これが用いられているのを目にしたことはありません。

コード全文: <https://wandbox.org/permlink/xWWJWpAbCAN9UWH2>

それぞれに考えられるメリット、デメリットを挙げます。

  1. `std::string`

メリット: 参照のライフタイムなどの余計な考慮が必要なくなる。  
デメリット: 十分長い文字列に対して呼び出しごとにコピーのコストがかかる。(RVOが有効な場合でも1回のコピーが必要。)

  2. 参照

メリット: 呼び出しごとのコピーが必要ない。  
デメリット:
返り値の参照の有効範囲を実装から見極める必要がある。参照先の変更に影響される。内部に`std::string`を直接的/間接的に実体として持つことを要求する。

  3. スマートポインタ

メリット: 参照の有効範囲の考慮が必要ない。  
デメリット: 参照先の変更に影響される。(例の場合)クラスが`shared_ptr`によって管理されることを要求する。

普通であれば1.のように`std::string`を返してしまえばよいと思うのですが、  
次のような条件を考える場合には (あるいはそれ以外の場合についても) どのパターンがふさわしいか意見をお聞かせください。

条件:

  1. C++11以降である。
  2. SSOを考慮しない。(十分長い文字列も入りうる。)
  3. RVOを期待できる。
  4. 対象のメンバは他のメンバ関数などによって値を変更されることはない。
  5. 頻繁に呼ばれる可能性がある。


CC BY-SA 4.0
2018-08-07T02:12:13.723
47300
2018-08-07T12:50:23.967
2018-08-07T06:37:15.943
29081
29081
question
c++
C++で文字列を返すメンバ関数のベストプラクティスについて


個人的な方針について述べてみます。  
他にもあると思いますが、ありがちな2つの目的について考えてみました。

目的(a).対象クラスに固有の文字列を取得する場合。  
目的(b).対象クラスに文字列演算を依頼する場合。

質問にはいわゆる(getter)を想定すると明言されていますので目的(a)を想定しているのかもしれません。

個人的には目的(a)の場合は方法2.を使います。  
方法1.はプロトタイプだけから判別すると、原理的には合理的な時間内で完了する保障がないと見積もられるためです。  
自分の小さなグループ内ではレギュレーション違反と判定されるかもしれません。  
(実装の内部を見ることができないという条件です)

目的(b)の場合は、加工結果を入れる文字列クラス(の参照又はポインタ)を引数で渡すことにしています。  
戻り値で戻したいケースも(おおいに)あるのですが、この実装方法によって、ネストしたコードを書きにくくなることを目的としています。  
(ネストがきつくなると実行速度はたして改善しないわりに、可読性がひどく落ちるため。)  
従って、戻り値に文字列を戻すことはありえません。

方法3.は単体のクラスでなくてよいという条件になると解釈できます。  
この条件であれば、その目的に最も適した実装を選択できます。  
ゆえに、なんとも言えません。

以上から、個人的には方法2.だけが選択可能という結論になります。


CC BY-SA 4.0
2018-08-07T06:12:14.007
47313
2018-08-07T06:12:14.007
3793
47300
answer


基本的には`const std::string &getString() const`を使っています。

[code]

    auto s1{obj.getString()}; // auto では、参照修飾はなくなることに注意
    
    std::string s2;
    s2 = obj.getString();
    
    if(obj.getString() == s3) {
        // do something interesting
    }
    
[/code]

のような使い方の場合は、その場で値を見るだけなので、参照を気にする必要がありません。

`const auto &rs{obj.getString()};`  
のような場合は、参照を気にする必要がありますが、明示的に参照を使っているのですから、責任は呼び出し側にあると考えます。

参照ではない`std::string
getString()`は、メンバ関数で動的に文字列を生成するときにしか使っていません。`std::filesystem::path::string`も、おそらく、そのような実装を想定していると思います。


CC BY-SA 4.0
2018-08-07T12:50:23.967
47325
2018-08-07T12:50:23.967
3605
47300
answer