Neden std :: queue :: pop değeri dönmüyor?


123

Bu sayfadan geçtim ama bunun sebebini anlayamıyorum. Orada bahsediliyor

"hiç bir değer döndürmemesi ve istemcilerin sıranın önündeki değeri incelemek için front () kullanmasını gerektirmesi daha mantıklıdır"

Ancak front () 'den bir öğeyi incelemek, o öğenin lvalue içinde kopyalanmasını da gerektiriyordu. Örneğin bu kod segmentinde

std::queue<int> myqueue;
int myint;
int result;
std::cin >> myint;
myqueue.push (myint);

/ * burada sonuca atanacak olan RHS'de geçici oluşturulacak ve referansla döndürülmesi durumunda pop işleminden sonra sonuç geçersiz kılınacak * /

result = myqueue.front();  //result.
std::cout << ' ' << result;
myqueue.pop();

beşinci satırda cout nesnesi önce myqueue.front () 'un bir kopyasını oluşturur ve ardından bunu sonuca atar. Öyleyse, fark ne, pop işlevi de aynı şeyi yapmış olabilir.


Çünkü bu şekilde uygulanmaktadır (yani void std::queue::pop();).
101010

Soru cevaplandı, ancak bir not olarak: Eğer gerçekten geri dönen bir pop istiyorsanız, ücretsiz bir işlevle kolayca uygulanabilir: ideone.com/lnUzf6
eerorika

1
Bağlantınız STL dokümantasyonuna. Ama C ++ standart kitaplığı hakkında soru soruyorsunuz. Farklı şeyler.
juanchopanza

5
"Ama bir öğeyi incelemek front()aynı zamanda o öğenin ldeğer olarak kopyalanmasını gerektirdi" - hayır öyle değil. frontdeğer değil, referans döndürür. Referans verdiği değeri kopyalamadan inceleyebilirsiniz.
Mike Seymour

1
@KeminZhou tarif ettiğiniz modelin bir kopyası gerekiyor. Olabilir. Kuyruğun tüketimini çoğullamak istiyorsanız, evet, kuyruktaki kilidi açmadan önce bir kopya almanız gerekir. Bununla birlikte, yalnızca giriş ve çıkışı ayırmayı önemsiyorsanız , ön tarafı incelemek için bir kilide ihtiyacınız yoktur . Tüketim bitene ve aramanız gerekene kadar kilitlemek için bekleyebilirsiniz pop(). Eğer kullanırsanız std::queue<T, std::list<T>>o andan itibaren sağlanan referansla ilgili herhangi bir sorun bulunmadığı front()bir tarafından geçersiz ediliyor push(). Ama gerekir biliyorum , kullanım deseni ve kısıtlamaları belgelemek gerekir.
jwm

Yanıtlar:


105

Öyleyse, fark nedir, pop işlevi de aynı şeyi yapmış olabilir.

Gerçekten de aynı şeyi yapabilirdi. Bunun yapılmamasının nedeni, patlatılan öğeyi döndüren bir pop'un istisnaların varlığında güvenli olmamasıdır (değere göre dönmek zorunda kalmak ve böylece bir kopya oluşturmak).

Bu senaryoyu düşünün (benim fikrimi aydınlatmak için saf / uydurma bir pop uygulamasıyla):

template<class T>
class queue {
    T* elements;
    std::size_t top_position;
    // stuff here
    T pop()
    {
        auto x = elements[top_position];
        // TODO: call destructor for elements[top_position] here
        --top_position;  // alter queue state here
        return x;        // calls T(const T&) which may throw
    }

T'nin kopya yapıcısı dönüşte atarsa, kuyruğun durumunu zaten değiştirmişsinizdir ( top_positionbenim saf uygulamamda) ve öğe kuyruktan kaldırılır (ve geri döndürülmez). Tüm amaçlar ve amaçlar için (istemci kodundaki istisnayı nasıl yakaladığınız önemli değil), kuyruğun üst kısmındaki öğe kaybolur.

Bu uygulama, aynı zamanda, atılan değere ihtiyaç duymadığınız durumlarda da verimsizdir (yani, kimsenin kullanmayacağı öğenin bir kopyasını oluşturur).

Bu, iki ayrı işlemle ( void popve const T& front()) güvenli ve verimli bir şekilde uygulanabilir .


hmmm ... bu mantıklı
cbinder

37
C ++ 11 notu: T'nin ucuz bir hareket-yapıcısı hariç (ki bu genellikle yığınlara konan nesnelerin türü için geçerlidir), o zaman değere göre geri dönüş verimli ve istisnai güvenliklidir.
Roman L

9
@ DavidRod Rodríguez-dribeas: Herhangi bir değişiklik önermiyorum, benim açımdan, belirtilen bazı dezavantajların C ++ 11 ile daha az sorun haline gelmesiydi.
Roman L

11
Ama neden deniyor pop? bu oldukça sezgiseldir. İsimlendirilebilir dropve sonra elementi patlatmadığı, onun yerine
bıraktığı

12
@utnapistim: fiili "pop" işlemi her zaman bir yığından en üstteki öğeyi alıp geri döndürmek olmuştur. STL yığınlarıyla ilk karşılaştığımda, en azından, pophiçbir şeyi iade etmemek konusunda şaşırmıştım . Örneğin wikipedia'da bakın .
Cris Luengo

34

Bağlandığınız sayfa sorunuzu yanıtlar.

İlgili tüm bölümden alıntı yapmak için:

Pop () 'un neden value_type yerine void döndürdüğünü merak edebilirsiniz. Yani, neden ikisini tek bir üye işlevinde birleştirmek yerine sıranın önündeki öğeyi incelemek ve kaldırmak için front () ve pop () kullanmak gerekir? Aslında bu tasarımın iyi bir nedeni var. Pop () ön öğeyi döndürdüyse, başvuru yerine değere göre döndürmesi gerekirdi: başvuruya göre döndürme sarkan bir işaretçi oluşturacaktır. Ancak değere göre dönüş verimsizdir: en az bir yedek kopya yapıcı çağrısı içerir. Pop () 'un bir değeri hem verimli hem de doğru olacak şekilde döndürmesi imkansız olduğundan, hiç değer döndürmemesi ve istemcilerden, değeri incelemek için front () kullanmalarını istemek daha mantıklıdır. sıranın önü.

C ++, programcının yazması gereken kod satırı sayısından daha fazla verimlilik göz önünde bulundurularak tasarlanmıştır.


16
Belki, ancak gerçek neden, bir sürüm için popbir değer döndüren istisnai güvenli bir sürümü (güçlü garantiyle) uygulamanın imkansız olmasıdır .
James Kanze

5

pop, veri yapısından kaldırıldığı için kaldırılan değere bir başvuru döndüremez, öyleyse başvuru neyi ifade etmelidir? Değere göre dönebilir, ancak ya pop'un sonucu hiçbir yerde saklanmadıysa? Daha sonra değeri gereksiz yere kopyalayarak zaman boşa harcanır.


4
Gerçek sebep istisnai güvenliktir. İade popve geri dönüş eylemi bir istisnayla sonuçlanabiliyorsa , yığınla bir "işlem" yapmanın güvenli bir yolu yoktur (öğe yığında kalır veya size geri döner ) . Açıkça, geri dönmeden önce öğeyi kaldırması gerekecekti ve sonra bir şey fırlatırsa, öğe geri alınamaz bir şekilde kaybolabilir.
Jon

1
Bu endişe, haricinde bir hamle oluşturucu için hala geçerli mi? (Elbette 0 kopya, iki hareketten daha etkilidir, ancak bu, istisnai, güvenli, etkili birleşik ön + pop için kapıyı açabilir)
peppe

1
@peppe, value_typebir nothrow move kurucusu olduğunu biliyorsanız, değere göre dönebilirsiniz , ancak kuyruk arayüzü, içinde ne tür bir nesne depoladığınıza bağlı olarak farklı olacaktır, bu da yardımcı olmaz.
Jonathan Wakely

1
@peppe: Bu güvenli olurdu, ancak yığın genel bir kitaplık sınıfıdır ve bu nedenle öğe türü hakkında ne kadar çok şey varsayması gerekiyorsa, o kadar az kullanışlı olur.
Jon

STL'nin buna izin vermeyeceğini biliyorum, ancak bu, blackjack ve bu özellikle ^ W ^ W ^ W ile kendi kuyruk sınıfını oluşturabileceği anlamına geliyor :)
peppe

3

Mevcut uygulamayla bu geçerlidir:

int &result = myqueue.front();
std::cout << result;
myqueue.pop();

Pop, şöyle bir referans döndürürse:

value_type& pop();

Bu durumda, referans artık geçerli olmadığından aşağıdaki kod çökebilir:

int &result = myqueue.pop();
std::cout << result;

Öte yandan, doğrudan bir değer döndürürse:

value_type pop();

O zaman bu kodun çalışması için daha az verimli olan bir kopya yapmanız gerekir:

int result = myqueue.pop();
std::cout << result;

1

C ++ 11'den başlayarak, hareket semantiğini kullanarak istenen davranışı arşivlemek mümkün olacaktır. Beğen pop_and_move. Bu nedenle, kopya oluşturucu çağrılmayacak ve performans yalnızca taşıma yapıcısına bağlı olacaktır.


2
Hayır, hareket anlambilim sihirli bir şekilde popistisnayı güvenli hale getirmez .
LF

0

Bunu tamamen yapabilirsiniz:

std::cout << ' ' << myqueue.front();

Veya bir değişkendeki değeri istiyorsanız bir referans kullanın:

const auto &result = myqueue.front();
if (result > whatever) do_whatever();
std::cout << ' ' << result;

Bunun yanında, 'daha mantıklı' ifadesi, 'kullanım kalıplarını inceledik ve bölünmeye daha fazla ihtiyaç duyduk' ifadesinin öznel bir biçimidir. (İçiniz rahat olsun: C ++ dili hafifçe gelişmiyor ...)


0

En iyi çözümün şöyle bir şey eklemek olduğunu düşünüyorum:

std::queue::pop_and_store(value_type& value);

değer, atılan değeri alacaktır.

Avantajı, bir taşıma atama operatörü kullanılarak uygulanabilmesidir, ön + pop kullanımı ise bir kopya oluşturur.

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.