Std :: reference_wrapper ile basit işaretçi arasındaki fark nedir?


104

Neden sahip olmaya ihtiyaç var std::reference_wrapper? Nerelerde kullanılmalı? Basit bir işaretçiden farkı nedir? Performansı basit bir işaretçiye kıyasla nasıl?


4
Temelde .yerine kullandığınız bir işaretçi->
MM

5
@MM Hayır, kullanmak .önerdiğiniz şekilde çalışmaz (bir noktada operatör nokta önerisi benimsenmediği ve entegre edilmediği sürece :))
Columbo

3
Yeni C ++ ile çalışmak zorunda olduğumda beni mutsuz eden bu gibi sorular.
Nils

Columbo'yu izlemek için, std :: reference_wrapper get()üye işlevi ile veya temeldeki türe örtük dönüşümü ile kullanılır .
Max Barraclough

Yanıtlar:


89

std::reference_wrapperşablonlarla birlikte kullanışlıdır. Bir nesneyi üzerine bir işaretçi depolayarak sararak, olağan semantiğini taklit ederken yeniden atama ve kopyalamaya izin verir. Ayrıca, belirli kütüphane şablonlarına nesneler yerine referansları saklamalarını söyler.

STL'deki functor'ları kopyalayan algoritmaları düşünün: Functor'un kendisi yerine functor'a atıfta bulunan bir referans sarmalayıcıyı ileterek bu kopyayı önleyebilirsiniz:

unsigned arr[10];
std::mt19937 myEngine;
std::generate_n( arr, 10, std::ref(myEngine) ); // Modifies myEngine's state

Bu işe yarıyor çünkü…

  • ... reference_wrappers aşırı yükoperator() onlar sadece işlev gibi çağrılabilir yüzden bakın nesneleri:

    std::ref(myEngine)() // Valid expression, modifies myEngines state
    
  • … (Un) sıradan referanslar gibi, kopyalama (ve atama) reference_wrapperssadece pointee atar.

    int i, j;
    auto r = std::ref(i); // r refers to i
    r = std::ref(j); // Okay; r refers to j
    r = std::cref(j); // Error: Cannot bind reference_wrapper<int> to <const int>
    

Bir referans sarmalayıcının kopyalanması pratik olarak bir işaretçiyi kopyalamaya eşdeğerdir, bu da olabildiğince ucuzdur. Kullanmanın doğasında olan tüm işlev çağrıları (örneğin, yapılacak olanlar operator()) tek satırlık olduklarından sadece satır içi olmalıdır.

reference_wrappere-postalar std::refvestd::cref aracılığıyla oluşturulur :

int i;
auto r = std::ref(i); // r is of type std::reference_wrapper<int>
auto r2 = std::cref(i); // r is of type std::reference_wrapper<const int>

Şablon argümanı, başvurulan nesnenin türünü ve cv niteliğini belirtir; r2a anlamına gelir const intve yalnızca bir referans verir const int. constİçlerinde functor olan sarmalayıcılara başvurmak için yapılan çağrılar yalnızca constüye işlev operator()lerini çağırır .

Rvalue başlatıcılarına izin verilmesi yarardan çok zarar vereceğinden izin verilmez. R değerleri yine de taşınacağı için (ve kısmen kaçınılsa bile garantili kopya eleme ile ), anlambilimini geliştirmiyoruz; bir referans sarmalayıcı pointee'in ömrünü uzatmadığından, sarkan işaretçiler ekleyebiliriz.

Kütüphane etkileşimi

Daha önce bahsedildiği gibi , karşılık gelen argümanı a'dan geçirerek make_tuplesonuçta bir referansın saklanması talimatı verilebilir :tuplereference_wrapper

int i;
auto t1 = std::make_tuple(i); // Copies i. Type of t1 is tuple<int>
auto t2 = std::make_tuple(std::ref(i)); // Saves a reference to i.
                                        // Type of t2 is tuple<int&>

Bunun forward_as_tupleşunlardan biraz farklı olduğuna dikkat edin : Burada, bağımsız değişken olarak r değerlerine izin verilmez.

std::bindaynı davranışı gösterir: Bağımsız değişkeni kopyalamaz, ancak bir reference_wrapper. Bu bağımsız değişkenin (veya bindfunctorun !) Kopyalanması gerekmiyorsa ancak -functor kullanılırken kapsam dahilinde kalırsa kullanışlıdır.

Sıradan işaretçilerden farkı

  • Ek bir sözdizimsel yönlendirme seviyesi yoktur. İşaretçilerin başvurdukları nesnenin bir değerini elde etmek için referanslarının kaldırılması gerekir; reference_wrappers örtük bir dönüştürme operatörüne sahiptir ve sardıkları nesne gibi çağrılabilir.

    int i;
    int& ref = std::ref(i); // Okay
    
  • reference_wrappers, işaretçilerden farklı olarak, boş bir duruma sahip değildir. Bir referansla veya başka biriylereference_wrapper başlatılmaları gerekir .

    std::reference_wrapper<int> r; // Invalid
    
  • Bir benzerlik, yüzeysel kopya semantiğidir: İşaretçiler ve reference_wrapperler yeniden atanabilir.


Bir şekilde std::make_tuple(std::ref(i));daha mı üstün std::make_tuple(&i);?
Laurynas Lazauskas

6
@LaurynasLazauskas Bu farklı. Gösterdiğiniz ikincisi i, ona bir referans değil, bir gösterici kaydeder .
Columbo

Hm ... Sanırım hala bu ikisini istediğim kadar ayırt edemiyorum ... Pekala, teşekkürler.
Laurynas Lazauskas

@Columbo Boş durumları yoksa bir dizi referans sarmalayıcı nasıl mümkün olabilir? Diziler genellikle tüm öğeler null durumuna ayarlanmış olarak başlamaz mı?
anatolyg

2
@anatolyg Bu diziyi başlatmanıza ne engel oluyor?
Columbo

27

En az iki motive edici amaç vardır std::reference_wrapper<T>:

  1. Fonksiyon şablonlarına değer parametresi olarak aktarılan nesnelere referans semantiği vermektir. Örneğin, std::for_each()işlev nesnesi parametresini değerine göre alan, geçmek istediğiniz büyük bir işlev nesnesine sahip olabilirsiniz . Nesneyi kopyalamaktan kaçınmak için kullanabilirsiniz

    std::for_each(begin, end, std::ref(fun));
    

    Bağımsız değişkenleri std::reference_wrapper<T>bir std::bind()ifadeye göre geçirmek, argümanları değere göre değil, başvuruya göre bağlamak için oldukça yaygındır.

  2. Bir kullanırken std::reference_wrapper<T>ile std::make_tuple()ilgili kayıt düzeni elemanının bir hale T&yerine daha T:

    T object;
    f(std::make_tuple(1, std::ref(object)));
    

İlk durum için lütfen bir kod örneği verebilir misiniz?
user1708860

1
@ user1708860: Verilenden farklı mı demek istiyorsun ...?
Dietmar Kühl

Std :: ref (fun) ile birlikte gelen gerçek kodu kastediyorum çünkü nasıl kullanıldığını anlamıyorum (eğlence bir nesne ve bir işlev değilse ...)
user1708860

2
@ user1708860: evet, büyük olasılıkla funbir işlev nesnesidir (yani bir işlev çağrısı operatörü olan bir sınıfın nesnesi) ve bir işlev değil: fungerçek bir işlev olursa, std::ref(fun)hiçbir amacı yoktur ve kodu potansiyel olarak yavaşlatır.
Dietmar Kühl

23

Kendi kendini belgeleyen kod açısından diğer bir fark, bir reference_wrappernesnenin sahipliğini esasen reddetmesidir. Bunun aksine, bir unique_ptrsahiplik iddia ederken, çıplak bir işaretçi sahip olabilir veya olmayabilir (çok sayıda ilgili koda bakmadan bilmek mümkün değildir):

vector<int*> a;                    // the int values might or might not be owned
vector<unique_ptr<int>> b;         // the int values are definitely owned
vector<reference_wrapper<int>> c;  // the int values are definitely not owned

3
c ++ 11 öncesi kod olmadığı sürece, ilk örnek isteğe bağlı, sahipsiz değerleri, örneğin dizine dayalı bir önbellek araması için belirtmelidir. Std bize boş olmayan, sahip olunan bir değeri (benzersiz ve paylaşılan varyantlar) temsil eden standart bir şey
sağlasaydı iyi olurdu

Çıplak işaretçilerin neredeyse her zaman ödünç alınan değerler olduğu C ++ 11'de belki bu kadar önemli değil.
Elling

reference_wrapperham işaretçilerden üstündür çünkü sadece sahip olunmayacağı açık olduğu için değil, aynı zamanda olamayacağı nullptr(saçmalıklar olmadan) ve bu nedenle kullanıcılar geçemeyeceklerini bilirler nullptr(saçmalıklar olmadan) ve siz de mecbur olmadığınızı bilirsiniz. kontrol edin.
undercore_d

20

Bunu, referansların etrafını saran kullanışlı bir paket olarak düşünebilirsiniz, böylece bunları kaplarda kullanabilirsiniz.

std::vector<std::reference_wrapper<T>> vec; // OK - does what you want
std::vector<T&> vec2; // Nope! Will not compile

Temelde bir CopyAssignablesürümü T&. Bir referans istediğinizde, ancak atanabilir, kullanılabilir std::reference_wrapper<T>veya yardımcı işlevi olması gerekir std::ref(). Veya bir işaretçi kullanın.


Diğer tuhaflıklar sizeof:

sizeof(std::reference_wrapper<T>) == sizeof(T*) // so 8 on a 64-bit box
sizeof(T&) == sizeof(T) // so, e.g., sizeof(vector<int>&) == 24

Ve karşılaştırma:

int i = 42;
assert(std::ref(i) == std::ref(i)); // ok

std::string s = "hello";
assert(std::ref(s) == std::ref(s)); // compile error

1
@LaurynasLazauskas Bir sarmalayıcıda bulunan işlev nesnelerini doğrudan çağırabilir. Bu da cevabımda açıklanıyor.
Columbo

2
Referans uygulama sadece bir işaretçi olduğundan, sarmalayıcıların neden herhangi bir yönlendirme veya performans cezası eklediğini anlayamıyorum
Riga

4
Bir sürüm kodu söz konusu olduğunda, basit bir referanstan daha fazla dolaylı olmamalı
Riga

3
Derleyicinin önemsiz reference_wrapperkodu satır içi yapmasını beklerdim , bu onu bir işaretçi veya referans kullanan kodla aynı yapar.
David Stone

4
@LaurynasLazauskas: std::reference_wrappernesnenin asla boş olmadığının garantisine sahiptir. Bir sınıf üyesi düşünün std::vector<T *>. Bu nesnenin nullptrvektörde a'yı saklayıp saklayamayacağını görmek için tüm sınıf kodunu incelemeniz gerekir , oysa ile std::reference_wrapper<T>geçerli nesnelere sahip olmanız garanti edilir.
David Stone
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.