Cara mengatasi const ref yang menggantung

18

Berikut program singkatnya

#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();
}

memiliki masalah referensi yang menggantung, yang sepertinya tidak diperingatkan oleh kompiler. Masalahnya adalah bahwa temporaries dapat diikat ke const-ref, yang kemudian dapat Anda pertahankan. Pertanyaannya kemudian adalah; Apakah ada metode untuk menghindari masalah ini? Lebih disukai yang tidak melibatkan pengorbanan kebenaran konst, atau selalu membuat salinan benda-benda besar.

sp2danny
sumber
4
Itu rumit. Saya dapat meyakinkan Anda bahwa saya berpikir dua kali sebelum saya membuat referensi const variabel anggota. Jika ragu, saya akan mempertimbangkan untuk memodelkan data ini entah bagaimana penunjuk pintar dapat terlibat (baik std::unique_ptruntuk kepemilikan eksklusif, atau std::shared_ptratau kepemilikan bersama, atau std::weak_ptr, setidaknya, mengenali data yang hilang).
Scheff
Dalam C ++, Anda tidak membayar untuk apa yang tidak Anda butuhkan / gunakan. Terserah programmer untuk memastikan bahwa masa hidup objek yang dirujuk tidak berakhir saat referensi masih digunakan / ada. Hal yang sama untuk pointer mentah, ... Ada pointer cerdas untuk membawa Anda fitur yang Anda minta :)
Fareanor
2
Anggota referensi selalu menjadi kesalahan: herbalutter.com/2020/02/23/references-simply
Maxim Egorushkin
Meskipun kompiler tidak memperingatkan, bug ini dapat ditangkap oleh Valgrind dan -fsanitize=address. Saya tidak berpikir ada praktik terbaik untuk menghindarinya tanpa mengorbankan kinerja.
ks1322

Jawaban:

8

Dalam situasi ketika beberapa metode menyimpan referensi setelah kembali itu adalah ide yang baik untuk digunakan std::reference_wrapperdaripada referensi normal:

#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. itu sudah dilengkapi dengan satu set kelebihan beban yang mencegah pengikatan nilai-nilai dan pengabaian temporer yang tidak disengaja, jadi tidak perlu repot dengan kelebihan yang dilarang mengambil nilai Woop (std::vector<int> const &&) = delete;untuk metode Anda:
Woop woop{someNums()}; // error
woop.report();
  1. itu memungkinkan pengikatan nilai-nilai secara implisit sehingga tidak akan merusak permintaan valid yang ada:
auto nums{someNums()};
Woop woop{nums}; // ok
woop.report();
  1. itu memungkinkan pengikatan nilai-nilai secara eksplisit yang merupakan praktik yang baik untuk menunjukkan bahwa penelepon akan menyimpan referensi setelah kembali:
auto nums{someNums()};
Woop woop{::std::ref(nums)}; // even better because explicit
woop.report();
pengguna7860670
sumber
10

Salah satu cara untuk membuat kelas Anda tidak terlalu rentan adalah dengan menambahkan konstruktor yang dihapus yang membutuhkan ref-kanan. Ini akan menghentikan instance kelas Anda dari membuat binding ke temporaries.

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

Konstruktor yang dihapus ini sebenarnya akan membuat kode O / P tidak dikompilasi, yang mungkin merupakan perilaku yang Anda cari?

Gem Taylor
sumber
3

Saya setuju dengan jawaban dan komentar lain yang harus Anda pikirkan dengan cermat jika Anda benar-benar perlu menyimpan referensi di dalam kelas. Dan jika Anda melakukannya, Anda mungkin ingin pointer non-const ke vektor const sebagai gantinya (yaitu std::vector<int> const * numbers_).

Namun, jika itu masalahnya, saya menemukan bahwa jawaban lain yang saat ini diposting tidak penting. Semuanya menunjukkan kepada Anda cara membuat Woopmemiliki nilai-nilai itu.

Jika Anda dapat memastikan bahwa vektor yang Anda masukkan akan bertahan lebih lama dari Woopinstance Anda , maka Anda dapat secara eksplisit menonaktifkan membangun Woopdari nilai. Itu mungkin menggunakan sintaks C ++ 11 ini:

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

Sekarang kode contoh Anda tidak akan dikompilasi lagi. Kompiler dengan memberikan kesalahan mirip dengan:

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: Anda mungkin menginginkan konstruktor eksplisit, lihat mis. Apa arti kata kunci eksplisit? .

Darhuuk
sumber
Saya tampaknya telah mencuri jawaban Anda di sana. Maaf!
Permata Taylor
1

Untuk mencegah kasus tertentu, Anda dapat memilih untuk mengambil pointer (karena Weep(&std::vector<int>{1,2,3})tidak diizinkan) atau Anda dapat mengambil referensi non-const yang juga akan kesalahan sementara.

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

Ini masih tidak menjamin nilainya tetap valid, tetapi paling tidak menghentikan kesalahan yang paling mudah, tidak membuat salinan, dan tidak perlu numsdibuat dengan cara khusus (misalnya seperti std::shared_ptratau std::weak_ptrtidak).

std::scoped_lockmengambil referensi ke mutex akan menjadi contoh, dan satu di mana ptr unik / bersama / lemah tidak benar-benar diinginkan. Seringkali std::mutexhanya akan menjadi anggota dasar atau variabel lokal. Anda masih harus sangat berhati-hati, tetapi dalam kasus ini umumnya mudah untuk menentukan rentang hidup.

std::weak_ptradalah pilihan lain untuk tidak memiliki, tetapi kemudian Anda memaksa pemanggil untuk menggunakan shared_ptr(dan dengan demikian juga menumpuk mengalokasikan), dan kadang-kadang itu tidak diinginkan.

Jika salinannya OK, itu hanya menghindari masalah.

Jika Woopharus mengambil kepemilikan, lewati sebagai nilai-r dan pindahkan (dan hindari sama sekali masalah penunjuk / referensi), atau gunakan unique_ptrjika Anda tidak dapat memindahkan nilainya sendiri atau ingin penunjuk tetap valid.

// 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;

Atau jika kepemilikan dibagi, Anda dapat menggunakan shared_ptruntuk semuanya, dan itu akan dihapus bersama dengan referensi terakhir, tetapi ini dapat membuat melacak siklus hidup objek menjadi sangat membingungkan jika digunakan berlebihan.

Lancer Api
sumber
1

Anda dapat menggunakan template programmingdan arraysjika Anda ingin memiliki objek yang menampung constwadah. Karena constexprkonstruktor dan constexpr arraysAnda mencapai const correctnessdan compile time execution.

Berikut adalah posting yang mungkin menarik: std :: move a 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;
}

jalankan kode

Keluaran:

3 5 7 11                                                                                                                        
1 2 3 5 12 3 51
M.Mac
sumber
1
Dengan angka-angka sebagai std::arrayini dijamin untuk disalin, bahkan jika langkah lain akan tersedia. Di atas semua itu wooping1dan wooping2bukan tipe yang sama, yang kurang dari ideal.
sp2danny
@ sp2danny terima kasih atas umpan balik Anda dan saya harus setuju dengan Anda pada kedua poin. user7860670 memberikan solusi yang lebih baik :)
M.Mac