Aynı vektörden bir öğeyi geri itmek güvenli midir?


126
vector<int> v;
v.push_back(1);
v.push_back(v[0]);

İkinci geri itme bir yeniden atamaya neden olursa, vektördeki ilk tam sayıya yapılan başvuru artık geçerli olmayacaktır. Yani bu güvenli değil mi?

vector<int> v;
v.push_back(1);
v.reserve(v.size() + 1);
v.push_back(v[0]);

Bu onu güvenli kılıyor mu?


4
Bir not: Şu anda standart teklifler forumunda bir tartışma var. Bunun bir parçası olarak, birisi örnek bir uygulamapush_back verdi . Başka bir poster , tarif ettiğiniz vakayı düzgün bir şekilde ele almadığı için bir hataya dikkat çekti . Anladığım kadarıyla hiç kimse bunun bir hata olmadığını iddia etmedi. Bunun kesin bir kanıt olduğunu söylemiyorum, sadece bir gözlem.
Benjamin Lindley

9
Üzgünüm ama doğru cevap konusunda hala tartışmalar olduğu için hangi cevabı kabul edeceğimi bilmiyorum.
Neil Kirk

4
Bu soruya 5. yorum ile yorum yapmam istendi: stackoverflow.com/a/18647445/576911 . Bunu şu anda yazan her yanıta olumlu oy vererek yapıyorum: evet, aynı vektörden bir öğeyi geri itmek güvenlidir.
Howard Hinnant

2
@BenVoigt: <shrug> Standardın ne dediğine katılmıyorsanız veya standarda katılıyorsanız, ancak yeterince açık olduğunu düşünmüyorsanız, bu her zaman sizin için bir seçenektir: cplusplus.github.io/LWG/ lwg-active.html # submit_issue Bu seçeneği kendim de hatırlayabildiğimden çok kez kullandım. Bazen başarıyla, bazen değil. Standardın ne dediğini veya ne söylemesi gerektiğini tartışmak istiyorsanız, SO etkili bir forum değildir. Konuşmamızın normatif bir anlamı yok. Ancak yukarıdaki bağlantıyı takip ederek normatif bir etki şansına sahip olabilirsiniz.
Howard Hinnant

2
@ Polaris878 Push_back vektörün kapasitesine ulaşmasına neden olursa, vektör yeni ve daha büyük bir arabellek ayırır, eski veriyi kopyalar ve ardından eski arabelleği siler. Ardından yeni öğeyi ekleyecektir. Sorun şu ki, yeni öğe silinmiş olan eski arabellekteki verilere bir referanstır. Push_back, silmeden önce değerin bir kopyasını oluşturmadıkça, kötü bir referans olacaktır.
Neil Kirk

Yanıtlar:


31

Standartta olası bir kusur olarak http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-closed.html#526 bu sorunu (veya ona çok benzer bir şeyi) ele almış gibi görünüyor :

1) const referansı ile alınan parametreler, işlevin yürütülmesi sırasında değiştirilebilir.

Örnekler:

Verilen std :: vektör v:

v.insert (v.begin (), v [2]);

v [2], vektörün elemanları hareket ettirilerek değiştirilebilir

Önerilen çözüm, bunun bir kusur olmadığıydı:

vector :: insert (iter, value) çalışması için gereklidir, çünkü standart çalışmaması için izin vermez.


17.6.4.9'da izin buldum: "Bir işlevin bağımsız değişkeninin geçersiz bir değeri varsa (işlevin etki alanı dışındaki bir değer veya amaçlanan kullanımı için geçersiz bir işaretçi gibi), davranış tanımsızdır." Yeniden tahsis oluşursa, tüm yineleyiciler ve öğelere yapılan başvurular geçersiz kılınır, yani işleve iletilen parametre referansı da geçersizdir.
Ben Voigt

4
Bence asıl mesele, yeniden tahsisi yapmaktan uygulamanın sorumlu olmasıdır. Girdi başlangıçta tanımlanmışsa davranışın tanımlanmasını sağlamak onun görevidir. Spesifikasyonlar, push_back'in bir kopya oluşturduğunu açıkça belirttiğinden, uygulamaların, yürütme süresi pahasına, ayırmadan önce tüm değerleri önbelleğe alması veya kopyalaması gerekir. Bu özel soruda harici referanslar kalmadığından, yinelemelerin ve referansların geçersiz olması önemli değildir.
OlivierD

3
@NeilKirk Bunun authorative cevabı olması gerektiğini düşünüyorum, o da Stephan T. Lavavej tarafından belirtilen Reddit'e temelde aynı argümanları kullanarak.
TemplateRex

v.insert(v.begin(), v[2]);yeniden tahsisi tetikleyemez. Peki bu soruya nasıl cevap veriyor?
ThomasMcLeod

@ThomasMcLeod: evet, açıkça bir yeniden tahsisi tetikleyebilir. Yeni bir eleman ekleyerek vektörün boyutunu genişletiyorsunuz.
Violet Giraffe

21

Evet, güvenlidir ve standart kitaplık uygulamaları bunu yapmak için çemberlerden geçer.

Uygulayıcıların bu gereksinimi bir şekilde 23.2 / 11'e kadar izlediğine inanıyorum, ancak nasıl olduğunu anlayamıyorum ve daha somut bir şey de bulamıyorum. Bulabildiğim en iyi şey şu makale:

http://www.drdobbs.com/cpp/copying-container-elements-from-the-c-li/240155771

Libc ++ 'ın ve libstdc ++' nın uygulamalarının incelenmesi, bunların da güvenli olduğunu gösterir.


9
Biraz destek burada gerçekten yardımcı olur.
chris

3
Bu ilginç, itiraf etmeliyim ki vakayı hiç düşünmemiştim ama gerçekten başarması oldukça zor görünüyor. Bunun için de geçerli vec.insert(vec.end(), vec.begin(), vec.end());mi?
Matthieu M.

2
@MatthieuM. Hayır: Tablo 100 şunu söylüyor: "pre: i ve j, a'ya yineleyiciler değildir".
Sebastian Redl

2
Bu benim de hatırladığım için şimdi oy veriyorum, ancak referansa ihtiyaç var.
bames53

3
Kullandığınız sürümde 23.2 / 11 "Aksi belirtilmedikçe (açıkça veya diğer işlevler açısından bir işlevi tanımlayarak), bir konteyner üye işlevini çağırmak veya bir kapsayıcıyı bir kitaplık işlevine bağımsız değişken olarak geçirmek yineleyicileri geçersiz kılmaz o kap içindeki nesnelere veya bunların değerlerini değiştirebilir. " ? Ancak vector.push_backaksini BELİRTMEKTEDİR. "Yeni boyut, eski kapasiteden daha büyükse yeniden tahsise neden olur." ve (at reserve) "Yeniden tahsis, dizideki öğelere atıfta bulunan tüm referansları, işaretçileri ve yineleyicileri geçersiz kılar."
Ben Voigt

13

Standart, ilk örneğinizin bile güvenli olmasını garanti eder. C ++ 11'den alıntı yapma

[Sequence.reqmts]

3 Tablo 100 ve 101'de ... Xbir sıra kapsayıcı sınıfını abelirtir X, türdeki öğeleri içeren bir değeri belirtir T, ... tbir ldeğer veya sabit değerX::value_type

16 Tablo 101 ...

İfade a.push_back(t) dönüş türü void Operasyonel semantik bir kopyasını ekler t. gerektirir: T olacaktır CopyInsertableiçine X. Konteyner basic_string , deque, list,vector

Dolayısıyla, tam olarak önemsiz olmasa da, uygulama push_back,.


7
Bunun güvenli olmasını nasıl garanti ettiğini anlamıyorum.
jrok

4
@Angew: Kesinlikle geçersiz kılar t, tek soru kopya yapmadan önce mi sonra mı? Son cümlenin kesinlikle yanlış.
Ben Voigt

4
@BenVoigt tListelenen ön koşulları karşıladığından, açıklanan davranış garantilidir. Bir uygulamanın bir ön koşulu geçersiz kılmasına ve daha sonra bunu belirtildiği gibi davranmamak için mazeret olarak kullanmasına izin verilmez.
bames53

8
@BenVoigt Müşteri, görüşme süresince ön koşulu sürdürmek zorunda değildir; sadece görüşmenin başlangıcında karşılanmasını sağlamak için.
bames53

6
@BenVoigt Bu iyi bir nokta ama inanıyorum ki, geçilen functor for_eachyineleyicileri geçersiz kılmak için gerekli değildir. Bir referans for_eachbulamıyorum, ancak bazı algoritmalarda "op ve binary_op yineleyicileri veya alt aralıkları geçersiz kılmayacaktır" gibi metinler görüyorum.
bames53

7

İlk örneğin güvenli olduğu açık değildir, çünkü bunun en basit uygulaması push_back, gerekirse önce vektörü yeniden tahsis etmek ve ardından referansı kopyalamak olacaktır.

Ama en azından Visual Studio 2010 ile güvenli görünüyor. Bunun uygulanması push_back, vektördeki bir öğeyi geri ittiğinizde durumun özel olarak ele alınmasını sağlar. Kod aşağıdaki şekilde yapılandırılmıştır:

void push_back(const _Ty& _Val)
    {   // insert element at end
    if (_Inside(_STD addressof(_Val)))
        {   // push back an element
                    ...
        }
    else
        {   // push back a non-element
                    ...
        }
    }

8
Spesifikasyonun bunun güvenli olmasını gerektirip gerektirmediğini bilmek istiyorum.
Nawaz

1
Standarda göre emniyetli olması gerekli değildir. Bununla birlikte, güvenli bir şekilde uygulanması mümkündür.
Ben Voigt

2
@BenVoigt Güvende olması gerektiğini söyleyebilirim (cevabıma bakın).
Angew artık

2
@BenVoigt Referansı geçtiğiniz anda geçerlidir.
Angew artık

4
@Angew: Bu yeterli değil. Çağrı süresi boyunca geçerli kalan bir referans iletmeniz gerekir ve bu referans geçmez.
Ben Voigt

3

Bu standarttan bir garanti değildir, ancak başka bir veri noktası olarak LLVM libc ++v.push_back(v[0]) için güvenlidir .

libc ++ 'ınstd::vector::push_back__push_back_slow_path belleği yeniden tahsis etmesi gerektiğinde çağırır :

void __push_back_slow_path(_Up& __x) {
  allocator_type& __a = this->__alloc();
  __split_buffer<value_type, allocator_type&> __v(__recommend(size() + 1), 
                                                  size(), 
                                                  __a);
  // Note that we construct a copy of __x before deallocating
  // the existing storage or moving existing elements.
  __alloc_traits::construct(__a, 
                            _VSTD::__to_raw_pointer(__v.__end_), 
                            _VSTD::forward<_Up>(__x));
  __v.__end_++;
  // Moving existing elements happens here:
  __swap_out_circular_buffer(__v);
  // When __v goes out of scope, __x will be invalid.
}

Kopyalama, yalnızca mevcut deponun serbest bırakılmasından önce değil, aynı zamanda mevcut öğelerden taşınmadan önce de yapılmalıdır. Sanırım var olan elemanların taşınması burada yapılır __swap_out_circular_buffer, bu durumda bu uygulama gerçekten güvenlidir.
Ben Voigt

@BenVoigt: iyi nokta ve hareketin içeride gerçekleştiği konusunda gerçekten haklısınız __swap_out_circular_buffer. (Bunu not etmek için bazı yorumlar ekledim.)
Nate Kohl

1

İlk sürüm kesinlikle güvenli DEĞİLDİR:

Standart bir kütüphane kabı veya dizi üyesi işlevi çağrılarak elde edilen yineleyiciler üzerindeki işlemler, temeldeki kaba erişebilir ancak onu değiştiremez. [Not: Özellikle yineleyicileri geçersiz kılan kapsayıcı işlemleri, bu kapsayıcıyla ilişkili yineleyiciler üzerindeki işlemlerle çakışır. - son not]

bölüm 17.6.5.9'dan


Bunun, insanların normalde iş parçacığı ile birlikte düşündükleri veri yarışları ile ilgili bölüm olduğuna dikkat edin ... ancak gerçek tanım "önce olur" ilişkileri içerir ve içeriğin çoklu yan etkileri arasında herhangi bir sıralama ilişkisi görmüyorum push_back. burada oynayın, yani referans geçersiz kılma, yeni kuyruk elemanının kopyalanması ile ilgili olarak sıralı olarak tanımlanmıyor gibi görünüyor.


1
Bunun bir kural değil bir not olduğu anlaşılmalıdır, bu yüzden yukarıdaki kuralın bir sonucunu açıklıyor ... ve sonuçlar referanslar için aynıdır.
Ben Voigt

5
Sonuç v[0]bir yineleyici değildir, aynı şekilde bir yineleyici push_back()almaz. Yani, bir dil avukatı bakış açısından, argümanınız geçersiz. Afedersiniz. Biliyorum, çoğu yineleyici işaretçilerdir ve bir yineleyiciyi geçersiz kılma noktası, referanslarla hemen hemen aynıdır, ancak standardın alıntı yaptığınız kısmı eldeki durumla ilgisizdir.
cmaster - reinstate monica

-1. Tamamen alakasız bir alıntı ve yine de cevap vermiyor. Komite x.push_back(x[0])GÜVENLİ olduğunu söylüyor .
Nawaz

0

Tamamen güvenlidir.

İkinci örneğinizde

v.reserve(v.size() + 1);

bu gerekli değildir çünkü vektör boyutunun dışına çıkarsa reserve,.

Bu şeylerden Vector sorumlu, sen değil.


-1

Push_back referansı değil değeri kopyalayacağından her ikisi de güvenlidir. İşaretçileri depoluyorsanız, vektör söz konusu olduğunda bu hala güvenlidir, ancak sadece vektörünüzün aynı verilere işaret eden iki elemanına sahip olacağınızı bilin.

Bölüm 23.2.1 Genel Konteyner Gereksinimleri

16
  • a.push_back (t) t'nin bir kopyasını ekler. Gerektirir: T, X'e CopyInsertable olacaktır.
  • a.push_back (rv) rv'nin bir kopyasını ekler. Gerektirir: T, X'e MoveInsertable olacaktır.

Bu nedenle push_back uygulamaları, bir kopyasının v[0] eklendiğinden emin olmalıdır . Karşı örnek olarak, kopyalamadan önce yeniden tahsis edecek bir uygulamayı varsayarsak, kesinlikle bir kopyasını eklemeyecektir v[0]ve bu nedenle özellikleri ihlal eder.


2
push_backancak vektörü de yeniden boyutlandıracak ve saf bir uygulamada bu, kopyalama gerçekleşmeden önce referansı geçersiz kılacaktır . Bu yüzden, bunu standarttan bir alıntıyla destekleyemezseniz, yanlış olduğunu düşüneceğim.
Konrad Rudolph

4
"Bu" derken, birinci mi yoksa ikinci örneği mi kastediyorsunuz? push_backdeğeri vektöre kopyalayacaktır; ancak (görebildiğim kadarıyla) bu yeniden ayırmadan sonra gerçekleşebilir , bu noktada kopyalamaya çalıştığı referans artık geçerli değildir.
Mike Seymour

1
push_backargümanını referans olarak alır .
bames53

1
@OlivierD: (1) yeni alan ayırmak (2) yeni öğeyi kopyalamak (3) mevcut öğeleri hareket ettirmek - inşa etmek (4) taşınan öğeleri yok etmek (5) eski depolamayı bu sırayla serbest bırakmak - ilk sürümün çalışması için.
Ben Voigt

1
@BenVoigt Bir konteyner, bu özelliği tamamen yok sayacaksa neden bir türün CopyInsertable olmasını gerektirsin ki?
OlivierD

-2

23.3.6.5 / 1'den: Causes reallocation if the new size is greater than the old capacity. If no reallocation happens, all the iterators and references before the insertion point remain valid.

Sonunda eklediğimiz için , vektör yeniden boyutlandırılmazsa hiçbir referans geçersiz olmayacaktır . Yani vektörün capacity() > size()çalışması garanti edilir, aksi takdirde tanımsız davranış olacağı garanti edilir.


Spesifikasyonun aslında bunun her iki durumda da çalışacağını garanti ettiğine inanıyorum. Yine de bir referans bekliyorum.
bames53

Soruda yineleyicilerden veya yineleyici güvenliğinden bahsedilmez.
OlivierD

3
@OlivierD iteratör kısmı burada gereksizdir: referencesAlıntı kısmıyla ilgileniyorum .
Mark B

2
Aslında güvenli olması garantilidir (cevabıma bakın, semantiği push_back).
Angew artık
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.