ぶら下がりconst refを解決する方法


18

次の短いプログラム

#include <vector>
#include <iostream>

std::vector<int> someNums()
{
    return {3, 5, 7, 11};
}

class Woop
{
public:
    Woop(const std::vector<int>& nums) : numbers(nums) {}
    void report()
    {
        for (int i : numbers)
            std::cout << i << ' ';
        std::cout << '\n';
    }
private:
    const std::vector<int>& numbers;
};

int main()
{
    Woop woop(someNums());
    woop.report();
}

ぶら下がっている参照の問題があり、コンパイラが警告するようには見えません。問題は、一時変数をconst-refにバインドできることです。問題はそれです。この問題を回避する方法はありますか?できれば、constの正確さを犠牲にすることや、常に大きなオブジェクトのコピーを作成することを含まないものが好ましい。


4
それはトリッキーです。メンバー変数のconst参照を行う前に、私は2度考えることをお約束します。疑わしい場合は、スマートポインタが関与できるように、このデータをモデル化することを検討します(std::unique_ptr所有権のstd::shared_ptr共有、所有権の共有、またはstd::weak_ptr少なくとも、失われたデータの認識)。
Scheff

C ++では、不要なものや使用しないものについては支払いません。参照がまだ使用/存在している間、参照されるオブジェクトの存続期間が終了しないように注意するのはプログラマの責任です。生のポインタについても同じです...あなたが求めていた機能を実現するためのスマートポインタがあります:)
Fareanor

2
参照メンバーは常に間違いです:herbsutter.com/2020/02/23/references-simply
Maxim Egorushkin

コンパイラは警告しませんが、このバグはValgrindとによって検出でき-fsanitize=addressます。パフォーマンスを犠牲にすることなくそれを回避するためのベストプラクティスはないと思います。
ks1322

回答:


8

一部のメソッドが戻った後も参照を保持している状況では、std::reference_wrapper通常の参照の代わりに使用することをお勧めします。

#include <functional>

class Woop
{
public:
    using NumsRef = ::std::reference_wrapper<const std::vector<int>>;
    Woop(NumsRef nums) : numbers_ref{nums} {}
    void report()
    {
        for (int i : numbers_ref.get())
            std::cout << i << ' ';
        std::cout << '\n';
    }
private:
    NumsRef numbers_ref;
};
  1. これには、右辺値のバインドや一時的な意図しない受け渡しを防止する一連のオーバーロードがすでに含まれているWoop (std::vector<int> const &&) = delete;ため、メソッドで右辺値を取得するための追加の禁止されたオーバーロードを気にする必要はありません。
Woop woop{someNums()}; // error
woop.report();
  1. 左辺値の暗黙的なバインディングを許可するので、既存の有効な呼び出しを壊しません:
auto nums{someNums()};
Woop woop{nums}; // ok
woop.report();
  1. これは、左辺値の明示的なバインディングを許可します。これは、呼び出し元が戻った後も参照を保持することを示すための良い方法です。
auto nums{someNums()};
Woop woop{::std::ref(nums)}; // even better because explicit
woop.report();

10

クラスの脆弱性を軽減する1つの方法は、right-refを取る削除されたコンストラクターを追加することです。これにより、クラスインスタンスが一時オブジェクトにバインドするのを停止します。

Woop(std::vector<int>&& nums)  =delete;

この削除されたコンストラクターは、実際にはO / Pコードをコンパイルしません。これは、探している動作である可能性がありますか?


3

クラス内に参照を保存する必要がある場合は慎重に検討する必要がある他の回答やコメントにも同意します。その場合、代わりにconstベクトルへの非constポインターが必要になるでしょう(つまりstd::vector<int> const * numbers_)。

しかし、そうだとすれば、現在投稿されている他の回答が要点を外れていることがわかります。それらはすべて、Woopそれらの値を独自に作成する方法を示しています。

渡すベクトルがWoopインスタンスよりも長く存続することが保証できる場合Woopは、右辺値からのの作成を明示的に無効にすることができます。これは、次のC ++ 11構文を使用して可能です。

Woop (std::vector<int> const &&) = delete;

これで、サンプルコードはコンパイルできなくなりました。コンパイラは次のようなエラーを出します:

prog.cc: In function 'int main()':
prog.cc:29:25: error: use of deleted function 'Woop::Woop(const std::vector<int>&&)'
   29 |     Woop woop(someNums());
      |                         ^
prog.cc:15:5: note: declared here
   15 |     Woop(std::vector<int> const &&) = delete;
      |     ^~~~

PS:おそらく明示的なコンストラクタが必要です。たとえば、明示的なキーワードはどういう意味ですか?を参照してください


そこであなたの答えを盗んだようです。ごめんなさい!
ジェムテイラー

1

その特定のケースを防ぐために、ポインターを使用することを選択できます(Weep(&std::vector<int>{1,2,3})許可されていないため)、または一時的にエラーになるconst以外の参照を使用することもできます。

Woop(const std::vector<int> *nums);
Woop(std::vector<int> *nums);
Woop(std::vector<int>& nums);

これらは依然として値が有効であることを保証しませんが、少なくとも最も簡単な間違いをなくし、コピーを作成せずnums、特別な方法で作成する必要がありません(例:as std::shared_ptrまたはstd::weak_ptrdoesなど)。

std::scoped_lockmutexへの参照を取得することは一例であり、unique / shared / weak ptrが本当に望まれない場合の例です。多くの場合、これstd::mutexは基本的なメンバーまたはローカル変数です。それでも十分に注意する必要がありますが、これらの場合、一般的に寿命を決定するのは簡単です。

std::weak_ptr非所有の別のオプションですが、呼び出し側に使用を強制しshared_ptr(したがってヒープ割り当ても)、それが望ましくない場合もあります。

コピーに問題がなければ、問題は回避されます。

Woop所有権を取得する必要がある場合は、r値として渡して移動(およびポインター/参照の問題を完全に回避)するunique_ptrか、値自体を移動できないか、ポインターを有効なままにしておく場合に使用します。

// the caller can't continue to use nums, they could however get `numbers` from Woop or such like
// or just let Woop only manipulate numbers directly.
Woop(std::vector<int> &&nums) 
   : numbers(std::move(nums)) {}
std::vector<int> numbers;

// while the caller looses the unique_ptr, they might still use a raw pointer, but be careful.
// Or again access numbers only via Woop as with the move construct above.
Woop(std::unique_ptr<std::vector<int>> &&nums) 
    : numbers(std::move(nums)) {}
std::unique_ptr<std::vector<int>> numbers;

または、所有権が共有されている場合はshared_ptr、すべてに使用でき、最終的な参照とともに削除されますが、これを使用すると、オブジェクトのライフサイクルの追跡が非常に混乱してしまいます。


1

あなたは使用することができますtemplate programmingし、arraysあなたが保持するオブジェクトにしたい場合はconst、コンテナを。以下のためにconstexprコンストラクタとconstexpr arraysあなたが達成const correctnessしてcompile time execution

ここに興味深いかもしれない投稿があります:std :: move const vector

#include <array>
#include <iostream>
#include <vector>


std::array<int,4>  someNums()
{
    return {3, 5, 7, 11};
}


template<typename U, std::size_t size>
class Woop
{
public:

template<typename ...T>
    constexpr Woop(T&&... nums) : numbers{nums...} {};

    template<typename T, std::size_t arr_size>
    constexpr Woop(std::array<T, arr_size>&& arr_nums) : numbers(arr_nums) {};

    void report()
    const {
        for (auto&& i : numbers)
            std::cout << i << ' ';
         std::cout << '\n';
    }



private: 
    const std::array<U, size> numbers;
    //constexpr vector with C++20
};

int main()
{
    Woop<int, 4> wooping1(someNums());
    Woop<int, 7> wooping2{1, 2, 3, 5, 12 ,3 ,51};

    wooping1.report();
    wooping2.report();
    return 0;
}

コードを実行する

出力:

3 5 7 11                                                                                                                        
1 2 3 5 12 3 51

1
数字としてはstd::array、移動が利用できる場合でも、コピーが保証されます。その上、同じタイプではないため、理想的wooping1wooping2は言えません。
sp2danny

@ sp2dannyフィードバックに感謝します。両方の点で同意する必要があります。user7860670はより良いソリューションを提供しました:)
M.Mac
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.