Sarkan const ref nasıl çözülür


18

Aşağıdaki kısa program

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

hiçbir derleyici hakkında uyarıyor gibi görünmüyor sarkan bir referans sorunu var. Sorun şudur: Geçiciler, daha sonra saklayabileceğiniz const-ref'lere bağlanabilir. O zaman soru şu; Bu soruna girmekten kaçınmanın bir yolu var mı? Tercihen sabit doğruluktan fedakarlık etmeyi veya her zaman büyük nesnelerin kopyalarını yapmayı içermeyen bir tane.


4
Bu zor. Ben bir üye değişken const başvuru yapmadan önce iki kez düşünüyorum emin olabilirsiniz. Şüpheniz varsa, bu verileri bir şekilde akıllı işaretçinin dahil edilebileceğini ( std::unique_ptrmünhasır sahiplik std::shared_ptrveya paylaşılan sahiplik için veya std::weak_ptren azından kayıp verileri tanımak için) modellemeyi düşünürüm .
Scheff

C ++ 'da ihtiyacınız olmayan / kullanmadığınız şey için ödeme yapmazsınız. Referans hala kullanımda / mevcutken, belirtilen nesnenin ömrünün sona ermemesine dikkat etmek programcıya bağlıdır. Ham işaretçiler için de aynı şey, ... İstediğiniz özellikleri getirmek için akıllı işaretçiler var :)
Fareanor

2
Referans üyeleri her zaman bir hatadır: otlarutter.com/2020/02/23/references-simply
Maxim Egorushkin

Derleyici uyarmasa da, bu hata Valgrind ve tarafından yakalanabilir -fsanitize=address. Performanstan ödün vermeden bundan kaçınmanın en iyi yolu olduğunu sanmıyorum.
ks1322

Yanıtlar:


8

Bazı yöntemlerin döndükten sonra bir referans tuttuğu durumlarda std::reference_wrapper, normal referans yerine kullanmak iyi bir fikirdir :

#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. zaten değerlerin bağlanmasını ve geçici olarak istenmeyen geçişleri önleyen bir dizi aşırı yük ile birlikte gelir, bu nedenle Woop (std::vector<int> const &&) = delete;yönteminiz için bir rvalue alarak ekstra yasaklanmış aşırı yük ile uğraşmanıza gerek yoktur :
Woop woop{someNums()}; // error
woop.report();
  1. mevcut geçerli çağrıları bozmamak için lvalues ​​örtük olarak bağlanmasına izin verir:
auto nums{someNums()};
Woop woop{nums}; // ok
woop.report();
  1. arayanın geri döndükten sonra referansı koruyacağını belirtmek için iyi bir uygulama olan lvalues'ın açıkça bağlanmasına izin verir:
auto nums{someNums()};
Woop woop{::std::ref(nums)}; // even better because explicit
woop.report();

10

Sınıfınızı daha az savunmasız hale getirmenin bir yolu, sağ ref gerektiren bir silinmiş kurucu eklemek olabilir. Bu, sınıf örneğinizin geçici ortamlara bağlanmasını engeller.

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

Bu silinen kurucu aslında O / P kodunu derlemez, aradığınız davranış bu olabilir mi?


3

Sınıf içinde bir referans depolamanız gerekiyorsa, dikkatle düşünmeniz gereken diğer cevapları ve yorumları kabul ediyorum. Ve bunu yaparsanız, bunun yerine bir const vektörüne sabit olmayan bir işaretçi isteyeceksiniz (yani std::vector<int> const * numbers_).

Ancak, bu durumda, şu anda gönderilen diğer cevapların yanında olduğunu görüyorum. Hepsi size Woopbu değerleri nasıl oluşturacağınızı gösteriyor .

İçine geçirdiğiniz vektörün Woopörneğinizden daha fazla olacağından emin olabilirseniz Woop, bir değerden bir inşa etmeyi açıkça devre dışı bırakabilirsiniz . Bu, bu C ++ 11 sözdizimini kullanarak mümkündür:

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

Şimdi örnek kodunuz artık derlenmeyecek. İle derleyici aşağıdakine benzer bir hata verir:

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;
      |     ^~~~

Not: Muhtemelen açık bir kurucu istiyorsunuz, bakınız örneğin açık anahtar kelime ne anlama geliyor? .


Cevabını orada çalmışım gibi görünüyor. Afedersiniz!
Gem Taylor

1

Bu özel durumu önlemek için, bir işaretçi almayı seçebilirsiniz ( Weep(&std::vector<int>{1,2,3})izin verilmediği için) veya geçici olarak hata verecek sabit olmayan bir başvuru alabilirsiniz.

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

Bunlar hala değerin geçerli kalmasını garanti etmez, ancak en kolay hatayı durdurur, kopya oluşturmaz ve numsözel bir şekilde (örneğin std::shared_ptrveya olduğu std::weak_ptrgibi) oluşturulmasına gerek yoktur.

std::scoped_lockmuteksi referans almak bir örnek ve benzersiz / paylaşılan / zayıf ptr'nin gerçekten istenmediği bir örnek olacaktır. Genellikle std::mutexsadece temel bir üye veya yerel değişken olacaktır. Yine de çok dikkatli olmalısınız, ancak bu durumlarda yaşam süresini belirlemek genellikle kolaydır.

std::weak_ptrsahip shared_ptrolmama için başka bir seçenektir, ancak daha sonra arayanı kullanmaya zorlarsınız (ve böylece yığın ayırmayı) ve bazen bu istenmez.

Bir kopya uygunsa, bu sadece sorunu önler.

Eğer Woopsahipliğini almak ya r değeri ve taşımak (ve önlemek pointer / referans sorunları tamamen) veya kullanımı gibi geçmelidir unique_ptrEğer geçerli kalmasını işaretçi değerini kendisi taşımak veya istemiyor eğer.

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

Veya sahiplik paylaşılıyorsa, shared_ptrher şey için kullanabilirsiniz ve son referansla birlikte silinir, ancak bu, aşırı kullanım durumunda nesne yaşam döngülerini takip etmeyi çok kafa karıştırıcı hale getirebilir.


1

Bir kapsayıcı içeren bir nesneye sahip olmak template programmingve kullanmak arraysistiyorsanız const. constexprYapıcı nedeniyle ve constexpr arrayselde const correctnessve compile time execution.

İlginç olabilecek bir yazı: std :: const vector'u taşıyın

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

kodu çalıştır

Çıktı:

3 5 7 11                                                                                                                        
1 2 3 5 12 3 51

1
Bunun gibi sayılarla, std::arraybaşka bir hamle yapılsa bile kopyalanması garanti edilir. Bunun üzerine wooping1ve wooping2ideal olanın altındaysa aynı tip, değildir.
sp2danny

@ sp2danny Geri bildiriminiz için teşekkürler ve her iki noktada da sizinle aynı fikirdeyim. user7860670 daha iyi bir çözüm
sundu
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.