Neden tüm <algorithm> işlevleri konteynerleri değil, yalnızca aralıklarını kullanıyor?


49

Birçok yararlı fonksiyon vardır <algorithm>, fakat hepsi "sekans" larda çalışır - yinelemeler Örneğin, eğer bir konteynırım varsa ve std::accumulateüzerine koştuğumda yazmam gerekir

std::vector<int> myContainer = ...;
int sum = std::accumulate(myContainer.begin(), myContainer.end(), 0);

Yapmayı düşündüğüm tek şey:

int sum = std::accumulate(myContainer, 0);

Bu benim gözlerimde biraz daha okunaklı ve net.

Şimdi sadece bir kabın bazı bölümlerinde çalışmak isteyebileceğiniz durumların olabileceğini görebiliyorum, bu yüzden aralıkların geçilmesi seçeneğinin olması kesinlikle yararlı . Ama en azından deneyimime göre, bu nadir görülen özel bir durum. Genelde bütün konteynerlerde çalışmak isterim.

Bir konteyneri alan ve çağıran begin()ve end()üzerine bir sarmalayıcı işlevi yazmak kolaydır , ancak bu kolaylık işlevleri standart kitaplığa dahil edilmez.

Bu STL tasarım seçiminin nedenini bilmek istiyorum.


7
STL tipik olarak kolaylık sağlayan paketleyicileri sağlıyor mu veya eski C ++ 'ın şu anda kullan-kullan-kullan ilkesi politikasını izliyor mu?
Kilian Foth

2
Kayıt için: kendi paketleyicinizi yazmak yerine, algoritma paketleyicilerini Boost.Range; bu durumda,boost::accumulate
ecatmur

Yanıtlar:


40

... aralıkların geçme seçeneğine sahip olmak kesinlikle faydalıdır. Ama en azından deneyimime göre, bu nadir görülen özel bir durum. Genelde bütün konteynerlerde çalışmak isterim

Deneyiminizde nadir görülen özel bir durum olabilir , ancak gerçekte tüm konteyner özel durumdur ve keyfi aralık genel durumdur.

Geçerli kapsayıcıyı kullanarak tüm konteyner durumunu uygulayabileceğinizi zaten farkettiniz , ancak görüşmeyi yapamazsınız.

Bu yüzden, kütüphane yazarı öndeki iki arayüzü uygulamak veya hala tüm vakaları kapsayan birini uygulamak arasında bir seçeneğe sahipti.


Konteynır alan ve çağrıları başlatan () ve end () üzerine sarılmış bir sarmalayıcı işlevi yazmak kolaydır, ancak bu kolaylık işlevleri standart kitaplığa dahil edilmez

Doğru, özellikle de serbest fonksiyonlar std::beginve std::endşimdi dahil edilmiştir.

Öyleyse, kütüphanenin kolaylık aşırı yüklemesini sağladığını varsayalım:

template <typename Container>
void sort(Container &c) {
  sort(begin(c), end(c));
}

Şimdi aynı zamanda bir karşılaştırma işlevini alan eşdeğer aşırı yükü sağlaması gerekiyor ve diğer tüm algoritmalar için eşdeğerleri sağlamamız gerekiyor.

Ama en azından tam bir konteynerde çalışmak istediğimiz her vakayı ele aldık, değil mi? Pek iyi değil. Düşünmek

std::for_each(c.rbegin(), c.rend(), foo);

Kaplarda geriye doğru işlem yapmak istiyorsak , mevcut algoritma başına başka bir yönteme (veya bir çift yönteme) ihtiyacımız var.


Dolayısıyla, menzil temelli yaklaşım basit anlamda daha geneldir:

  • bütün konteyner versiyonunun yapabileceği her şeyi yapabilir
  • Tüm konteyner yaklaşımı, hala daha az güçlü olmakla birlikte, gereken aşırı yük sayısını iki katına çıkarır veya üçe katlar.
  • Menzil tabanlı algoritmalar da kompoze edilebilir (iterator adaptörlerini istifleyebilir veya zincirleyebilirsiniz, ancak bu daha yaygın olarak fonksiyonel dillerde ve Python'da yapılır).

Elbette, STL'nin standart hale getirilmesinin zaten çok fazla uğraştığı ve bunun yaygın bir şekilde kullanılmadan önce uygun ambalajlarla şişirilmesi, sınırlı bir komite zamanının büyük bir kullanımı olmazdı. Eğer ilgileniyorsanız, Stepanov & Lee'nin teknik raporunu burada bulabilirsiniz.

Yorumlarda belirtildiği gibi, Boost.Range , standartta değişiklik gerektirmeden daha yeni bir yaklaşım sunar.


9
OP'nin dahil olduğu kimsenin, her özel durum için aşırı yükleme yapılmasını önerdiğini sanmıyorum. “Tüm konteyner” “rastgele bir aralıktan” daha az yaygın olsa bile, kesinlikle “tüm konteyner ters çevrilmiş” den çok daha yaygındır. f(c.begin(), c.end(), ...)Aşırı yüklenme sayısının iki katına çıkmasını önlemek için sınırlandırın ve belki de en sık kullanılan aşırı yüke sınırlandırın (ancak bunu belirlersiniz). Ayrıca, yineleyici adaptörleri tamamen diktir (not ettiğiniz gibi, yineleyicileri çok farklı çalışan ve bahsettiğiniz gücün çoğuna sahip olmayan Python'da gayet iyi çalışırlar).

3
Tüm konteynırın, ileriye dönük davaların çok yaygın olduğu konusunda hemfikirim , ancak bunun önerilenden çok daha küçük bir olası kullanım kümesi olduğunu belirtmek isterdim. Spesifik olarak, seçim, tüm konteyner ile kısmi konteyner arasında değil, tüm konteyner ile kısmi konteyner arasında olduğu için muhtemelen ters çevrilmiş veya başka şekilde uyarlanmış olduğundan. Ayrıca , eğer algoritma aşırı yükünü de değiştirmek zorunda kalırsanız, adaptörleri kullanmanın algılanan karmaşıklığının daha yüksek olduğunu söylemenin adil olacağını düşünüyorum .
Yararsız

23
Kapsayıcı sürümü Not olur STL bir dizi nesnesi önersen tüm durumları kapsıyor; örn std::sort(std::range(start, stop)).

3
Aksine: birleşik işlevsel algoritmalar (harita ve filtre gibi) bir koleksiyonu temsil eden tek bir nesneyi alır ve tek bir nesneyi döndürür, kesinlikle bir çift yineleyiciye benzer bir şey kullanmazlar.
svick

3
bir makro bunu yapabilir: #define MAKE_RANGE(container) (container).begin(), (container).end()</jk>
cırcır ucube 16:

21

Bu konuda Herb Sutter'ın bir makalesi olduğu ortaya çıktı. Temel olarak, sorun aşırı yük belirsizliğidir. Aşağıdakiler göz önüne alındığında:

template<typename Iter>
void sort( Iter, Iter ); // 1

template<typename Iter, typename Pred>
void sort( Iter, Iter, Pred ); // 2

Ve aşağıdakileri ekleyerek:

template<typename Container>
void sort( Container& ); // 3

template<typename Container, typename Pred>
void sort( Container&, Pred ); // 4

Ayırt etmek 4ve 1doğru şekilde zorlaştırır .

Önerilen fakat sonuçta C ++ 0x'e dahil olmayan kavramlar bunu çözmüş olacaktı ve onu kullanarak atlatmak da mümkün enable_if. Algoritmaların bazıları için hiç sorun değil. Ama buna karşı karar verdiler.

Şimdi burada tüm yorumları ve cevapları okuduktan sonra, rangenesnelerin en iyi çözüm olacağını düşünüyorum . Sanırım bir göz atacağım Boost.Range.


1
Eh, sadece bir kullanma kullanarak typename Iterkatı bir dil için ördek gibi yazılmış gibi görünüyor. Örneğin (vb. Belirsizlik problemini çözebilecek olan) template<typename Container> void sort(typename Container::iterator, typename Container::iterator); // 1ve template<template<class> Container, typename T> void sort( Container<T>&, std::function<bool(const T&)> ); // 4benzeri şeyleri tercih ederim
Vlad

@Vlad: Ne yazık ki, mevcut olmadığı için düz eski diziler için işe yaramaz T[]::iterator. Ayrıca, uygun yineleyici, herhangi bir koleksiyonun iç içe geçme türü olmak zorunda değildir, tanımlaması yeterlidir std::iterator_traits.
firegurafiku 25:15

@firegurafiku: Bazı temel TMP püf noktaları ile dizilerin özel durumları kolaydır.
Vlad

11

Temel olarak eski bir karar. Yineleyici kavramı işaretçiler üzerinde modellenmiştir, ancak kaplar diziler üzerinde modellenmemiştir. Ayrıca, dizilerin geçmesi zor olduğu için (genelde uzunluk için bir tip olmayan şablon parametresine ihtiyaç duyar), genellikle bir işlev yalnızca işaretçilere sahiptir.

Ama evet, görüş açıklarında karar yanlış. Ya begin/endda begin/length; ' dan ya da ; Şimdi _nbunun yerine çoklu ek algoritmalarımız var .


5

Bunları eklemek size hiçbir güç kazandırmayacak (zaten tüm konteyneri arayarak .begin()ve .end()kendiniz arayarak yapabilirsiniz), ve tedarikçiler tarafından kütüphanelere eklenmiş, test edilmiş, bakımı yapılmış, kütüphaneye uygun şekilde belirtilmesi gereken bir şey daha ekleyecektir. vs vs.

Kısacası, muhtemelen orada değil çünkü tüm konteyner kullanıcılarını bir ekstra fonksiyon çağrısı parametresi yazmaktan korumak için fazladan bir dizi şablon tutmaya değmez.


9
Bana güç kazandıramadı, bu doğru - ama sonunda, ikisi de yok std::getlineve yine de kütüphanede. Biri genişletilmiş kontrol yapılarının bana güç kazanamayacağını söyleyecek kadar ileri gidebilirdi, çünkü her şeyi sadece ifve kullanarak yapabilirim goto. Evet, haksız karşılaştırma, biliyorum;) Bir şekilde şartname / uygulama / bakım yükünü anlayabildiğimi düşünüyorum, ama burada bahsettiğimiz küçük bir sarıcı, yani ..
ölümcül-gitar

Küçük bir sarıcı kodlamanın hiçbir maliyeti yoktur ve belki de kütüphanede olması bir anlam ifade etmez.
ebasconp

-1

Şimdilik, http://en.wikipedia.org/wiki/C++11#Range-based_for_loop için güzel bir alternatif std::for_each. Dikkat edin, açık bir yineleyici yok:

int a[5] = {1, 2, 3, 4, 5};
for (auto &i: a) { i *= 2; }

( Https://stackoverflow.com/a/694534/2097284 tarafından ilham alındı .)


1
Sadece tek bir parçasını çözer <algorithm>, ihtiyaç duyan tüm gerçek cebirleri beginve yinelemeleri çözmez end- ancak fayda göz ardı edilemez! 2009ish'de ilk kez C ++ 03'ü denediğimde, döngüdeki kazan plakası nedeniyle yineleyicilerden uzak durdum, ve neyse ki o zamanki projelerime izin verildi. 2014'te C ++ 11'i yeniden başlatmak, inanılmaz bir yükseltme oldu, C ++ dili her zaman olmalıydı ve şimdi onsuz yaşayamam auto &it: them:)
underscore_d
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.