C ++ 'ın IOStream'lerini kim tasarladı / tasarladı ve yine de günümüz standartlarına göre iyi tasarlanmış olarak kabul edilir mi? [kapalı]


128

Öncelikle, öznel görüşler istiyormuşum gibi görünebilir, ama peşinde olduğum şey bu değil. Bu konuyla ilgili bazı sağlam temelli argümanlar duymak isterim.


Modern bir akış / serileştirme çerçevesinin nasıl tasarlanması gerektiğine dair biraz fikir edinme umuduyla, kısa süre önce kendime Angelika Langer ve Klaus Kreft tarafından yazılan Standard C ++ IOStreams and Locales kitabının bir kopyasını aldım . IOStreams iyi tasarlanmış olmasaydı, ilk etapta C ++ standart kitaplığına girmeyeceğini düşündüm.

Bu kitabın çeşitli bölümlerini okuduktan sonra, IOStreams'in genel mimari bakış açısından örneğin STL ile karşılaştırılıp karşılaştırılamayacağı konusunda şüphelerim olmaya başladım. Örneğin , STL'ye giren bazı tasarım kararları hakkında bilgi edinmek için Alexander Stepanov (STL'nin "mucidi") ile yapılan bu röportajı okuyun .

Beni özellikle şaşırtan şey :

  • IOStreams'in genel tasarımından kimin sorumlu olduğu bilinmiyor gibi görünüyor (bununla ilgili bazı arka plan bilgilerini okumak isterim - iyi kaynakları bilen var mı?);

  • IOStreams'in hemen yüzeyinin altına indiğinizde, örneğin IOStreams'i kendi sınıflarınızla genişletmek istiyorsanız, oldukça şifreli ve kafa karıştırıcı üye işlev adlarının bulunduğu bir arayüze ulaşırsınız, örneğin getloc/ imbue, uflow/ underflow, snextc/ sbumpc/ sgetc/ sgetn, pbase/ pptr/ epptr(ve muhtemelen daha da kötü örnekler). Bu, genel tasarımı ve münferit parçaların nasıl birlikte çalıştığını anlamayı çok daha zor hale getirir. Yukarıda bahsettiğim kitap bile o kadar yardımcı olmuyor (IMHO).


İşte sorum şu:

Eğer (aslında orada eğer bugünün yazılım mühendisliği standartlarına göre yargıç olsaydı olduğunu Bunlarla ilgili herhangi genel bir mutabakat), C ++ 'ın hala iyi tasarlanmış olarak kabul edilebilir IOStreams ki? (Yazılım tasarım becerilerimi genellikle modası geçmiş bir şeyden geliştirmek istemem.)


7
İlginç Herb Sutter'ın görüşü stackoverflow.com/questions/2485963/… :) Adamın yalnızca birkaç günlük katılımdan sonra SO'dan ayrılması çok kötü
Johannes Schaub - litb

5
STL akışlarında endişelerin karıştığını gören başka biri var mı? Bir akış normalde baytları okumak veya yazmak için tasarlanmıştır, başka hiçbir şey yapmaz. Belirli veri türlerini okuyabilen veya yazabilen bir şey, bir biçimlendiricidir (biçimlendirilmiş baytları okumak / yazmak için bir akış kullanmak zorunda kalmayabilir). Her ikisini de tek bir sınıfa karıştırmak, kendi akışlarını uygulamayı daha da karmaşık hale getirir.
mmmmmmmm

4
@rsteven, bu endişeler arasında bir ayrım var. std::streambufbayt okuma ve yazma için temel sınıftır ve istream/ ostreambiçimlendirilmiş giriş ve çıkış içindir, std::streambufhedef / kaynak olarak bir gösterici alır .
Johannes Schaub -

1
@litb: Ama akım (formatlayıcı) tarafından kullanılan streambuf'u değiştirmek mümkün müdür? Öyleyse, belki STL biçimlendirmesini kullanmak ama verileri belirli bir akış arabasıyla yazmak istiyorum?
mmmmmmmm

2
@rstevens,ostream foo(&somebuffer); foo << "huh"; foo.rdbuf(cout.rdbuf()); foo << "see me!";
Johannes Schaub - litb

Yanıtlar:


31

: Birkaç aslı astarı fikirler standardı içine kendi yolunu buldu auto_ptr, vector<bool>, valarrayve exportsadece birkaç isim. Bu yüzden IOStreams'in varlığını kaliteli tasarımın bir işareti olarak kabul etmeyeceğim.

IOStreams'in damalı bir geçmişi var. Aslında daha önceki bir akış kitaplığının yeniden işlenmesidirler, ancak günümüzün C ++ deyimlerinin çoğunun bulunmadığı bir zamanda yazılmıştır, bu nedenle tasarımcılar geriye bakma avantajına sahip değildi. Zamanla ortaya çıkan sorunlardan biri, sanal işlevlerin bolca kullanılması ve en ince ayrıntı düzeyinde bile dahili tampon nesnelerine iletilmesi nedeniyle ve ayrıca bazı anlaşılmaz tuhaflıklar sayesinde, IOStreams'i C'nin standardı kadar verimli bir şekilde uygulamanın neredeyse imkansız olmasıdır. yerel ayarların tanımlanma ve uygulanma biçiminde. Bununla ilgili hafızam oldukça belirsiz, itiraf edeceğim; Comp.lang.c ++ 'da moderatörlüğünde, birkaç yıl önce yoğun bir tartışma konusu olduğunu hatırlıyorum.


3
Girişiniz için teşekkürler. comp.lang.c++.moderatedArşive göz atacağım ve değerli bir şey bulursam sorumun altındaki bağlantıları göndereceğim. - Ayrıca, sizinle aynı fikirde değilim auto_ptr: Herb Sutter'ın Olağanüstü C ++ ' sını okuduktan sonra RAII modelini uygularken çok faydalı bir sınıf gibi görünüyor.
stakx -

5
@stakx: Yine de kullanımdan kaldırılıyor ve yerini unique_ptrdaha net ve daha güçlü anlambilimle değiştiriyor.
UncleBens

3
@UncleBens unique_ptr, rvalue referansı gerektirir. Yani bu noktada auto_ptrçok güçlü bir işaretçi.
Artyom

7
Ama auto_ptrkopyalama / atama anlambilimini mahvetti, bu da onu hataların referansını kaldırmak için bir niş haline getirdi ...
Matthieu M.

5
@TokenMacGuy: Bu bir vektör değil ve boolleri depolamıyor. Bu da onu biraz yanıltıcı yapıyor. ;)
jalf

40

Onları kimin tasarladığına gelince, orijinal kütüphane Bjarne Stroustrup tarafından oluşturuldu (şaşırtıcı değil) ve daha sonra Dave Presotto tarafından yeniden uygulandı. Bu daha sonra yeniden tasarlandı ve Andrew Koenig'in manipülatör fikrini kullanarak Jerry Schwarz tarafından Cfront 2.0 için yeniden uygulandı. Kütüphanenin standart versiyonu bu uygulamaya dayanmaktadır.

Kaynak "The Design & Evolution of C ++", bölüm 8.3.1.


3
@Neil - fındık tasarım hakkındaki fikriniz nedir? Diğer cevaplarınıza göre, birçok kişi fikrinizi duymak ister ...
DVK

1
@DVK Görüşümü ayrı bir cevap olarak yayınladım.

2
Bjarne Stroustrup ile IOStreams geçmişinin bazı parçalarını ve parçalarından bahsettiği bir röportajın dökümünü buldum: www2.research.att.com/~bs/01chinese.html (bu bağlantı şu anda geçici olarak kesilmiş gibi görünüyor, ancak deneyebilirsiniz Google'ın sayfa önbelleği)
stakx -

2
Güncellenen bağlantı: stroustrup.com/01chinese.html .
FrankHB

28

Bugünün yazılım mühendisliği standartlarına göre karar vermek zorunda olsaydınız (eğer bunlarla ilgili genel bir anlaşma varsa), C ++ 'ın IOStreams hala iyi tasarlanmış olarak kabul edilir miydi? (Yazılım tasarım becerilerimi genellikle modası geçmiş bir şeyden geliştirmek istemem.)

HAYIR derdim , birkaç nedenden dolayı:

Yetersiz hata işleme

Hata durumları ile değil, istisnalar ile rapor edilmelidir operator void*.

"Zombi nesnesi" anti-kalıbı, bunun gibi hatalara neden olan şeydir .

Biçimlendirme ve G / Ç arasında zayıf ayrım

Bu, ihtiyacınız olsun veya olmasın, biçimlendirme için fazladan durum bilgisi içermeleri gerektiğinden akış nesnelerini gereksiz karmaşık hale getirir.

Aynı zamanda aşağıdaki gibi böcek yazma olasılığını da artırır:

using namespace std; // I'm lazy.
cout << hex << setw(8) << setfill('0') << x << endl;
// Oops!  Forgot to set the stream back to decimal mode.

Bunun yerine şöyle bir şey yazdın:

cout << pad(to_hex(x), 8, '0') << endl;

Biçimlendirmeyle ilgili durum bitleri olmayacak ve sorun olmayacaktır.

Java, C # ve Python gibi "modern" dillerde, tüm nesnelerin G / Ç rutinleri tarafından çağrılan bir toString/ ToString/ __str__işlevi olduğunu unutmayın. AFAIK, yalnızca C ++ stringstreambir dizeye dönüştürmenin standart yolu olarak kullanarak bunu tersine yapar .

İ18n için zayıf destek

Iostream tabanlı çıktı, dize değişmezlerini parçalara böler.

cout << "My name is " << name << " and I am " << occupation << " from " << hometown << endl;

Biçim dizeleri tam cümleleri dizge değişmezlerine yerleştirir.

printf("My name is %s and I am %s from %s.\n", name, occupation, hometown);

İkinci yaklaşımın GNU gettext gibi uluslararasılaştırma kitaplıklarına uyarlanması daha kolaydır, çünkü tüm cümlelerin kullanımı çevirmenler için daha fazla bağlam sağlar. Dize biçimlendirme yordamınız yeniden sıralamayı destekliyorsa (POSIX $printf parametreleri gibi), o zaman diller arasındaki kelime sırasındaki farklılıkları daha iyi ele alır.


4
Aslında, i18n için, değiştirmeler konumlarla (% 1,% 2, ..) tanımlanmalıdır, çünkü bir çeviri parametre sırasını değiştirmeyi gerektirebilir. Aksi takdirde, tamamen katılıyorum - +1.
peterchen

4
@peterchen: POSIX $belirleyicileri bunun için printf.
jamesdlin

2
Sorun, biçim dizeleri değil, C ++ 'nın tür uyumlu olmayan değişkenlere sahip olmasıdır.
dan04

5
C ++ 11'den itibaren artık typafe değişkenlerine sahiptir.
Ördek mölemeye

2
IMHO 'ekstra durum bilgisi' en kötü konudur. cout küreseldir; ona biçimlendirme bayrakları eklemek, bu bayrakları küresel hale getirir ve bunların çoğu kullanımının birkaç satırlık amaçlanan bir kapsama sahip olduğunu düşündüğünüzde, bu oldukça korkunç. Bunu bir ostream'e bağlanan ancak kendi durumunu koruyan bir 'formatlayıcı' sınıfıyla düzeltmek mümkün olurdu. Ve, cout ile yapılan şeyler genellikle printf ile yapılan aynı şeyle karşılaştırıldığında (bu mümkün olduğunda)
berbat görünüyor

17

Bunu ayrı bir cevap olarak gönderiyorum çünkü bu saf bir fikir.

Girdi ve çıktının gerçekleştirilmesi (özellikle girdi) çok, çok zor bir sorundur, bu nedenle iostreams kitaplığının gövdeler ve mükemmel bir geriye bakışla daha iyi yapılabilecek şeylerle dolu olması şaşırtıcı değildir. Ama bana öyle geliyor ki, tüm I / O kütüphaneleri, hangi dilde olursa olsun böyle. I / O sisteminin tasarımcısına hayran kalmamı sağlayan güzel bir şey olduğu bir programlama dili hiç kullanmadım. İostreams kitaplığının özellikle CI / O kitaplığına göre avantajları vardır (genişletilebilirlik, tür güvenliği vb.), Ancak kimsenin onu harika OO veya genel tasarım örneği olarak tuttuğunu düşünmüyorum.


16

C ++ iostreams hakkındaki düşüncem, özellikle kendi akış sınıflarımı uygulayarak onları gerçekten genişletmeye başladıktan sonra, zaman içinde önemli ölçüde gelişti. Gülünç derecede zayıf üye işlev adlarına xsputnveya her neyse , genişletilebilirliği ve genel tasarımı takdir etmeye başladım . Her şeye rağmen, G / Ç akışlarının tip güvenliği olmayan ve büyük güvenlik kusurlarıyla dolu C stdio.h'ye göre büyük bir gelişme olduğunu düşünüyorum.

Bence IO akışlarıyla ilgili temel sorun, birbiriyle ilişkili ancak biraz ortogonal iki kavramı birleştirmeleridir: metin biçimlendirme ve serileştirme. Bir yandan, IO akışları, bir nesnenin insan tarafından okunabilir, biçimlendirilmiş metinsel temsilini üretmek ve diğer yandan bir nesneyi taşınabilir bir biçime serileştirmek için tasarlanmıştır. Bazen bu iki hedef bir ve aynıdır, ancak diğer zamanlarda bu ciddi şekilde can sıkıcı uyumsuzluklara neden olur. Örneğin:

std::stringstream ss;
std::string output_string = "Hello world";
ss << output_string;

...

std::string input_string;
ss >> input_string;
std::cout << input_string;

Burada girdi olarak aldığımız şey, başlangıçta akışa çıkardığımız şey değildir . Bunun nedeni, <<operatörün tüm dizeyi çıktı olarak vermesidir, oysa >>operatör, akışta depolanan uzunluk bilgisi olmadığından bir boşluk karakteriyle karşılaşana kadar yalnızca akıştan okuyacaktır . Yani "merhaba dünya" içeren bir dizge nesnesi çıktısak bile, sadece "merhaba" içeren bir dizge nesnesi gireceğiz. Bu nedenle, akış bir biçimlendirme aracı olarak amacına hizmet ederken, nesneyi düzgün bir şekilde serileştirmeyi ve ardından nesneyi serileştirmeyi başaramadı.

IO akışlarının serileştirme tesisleri olarak tasarlanmadığını söyleyebilirsiniz, ancak durum buysa, girdi akışları gerçekte ne için? Ayrıca, pratikte I / O akışları genellikle nesneleri serileştirmek için kullanılır, çünkü başka standart serileştirme olanakları yoktur. Düşünün boost::date_timeya boost::numeric::ublas::matrixile çıkış bir matris nesnesi ise nerede, <<operatör giriş o kullanırken, aynı kesin matris elde edersiniz >>operatörü. Ancak bunu başarmak için Boost tasarımcıları, sütun sayısı ve satır sayısı bilgilerini çıktıda metin verileri olarak saklamak zorunda kaldı ve bu da gerçek insan tarafından okunabilir ekranı tehlikeye attı. Yine, metinsel biçimlendirme olanakları ve serileştirmenin garip bir kombinasyonu.

Diğer dillerin çoğunun bu iki tesisi nasıl ayırdığına dikkat edin. Örneğin Java'da, biçimlendirme toString()yöntemle gerçekleştirilirken , serileştirme Serializablearabirim aracılığıyla gerçekleştirilir .

Bence en iyi çözüm , standart karakter tabanlı akışların yanı sıra bayt tabanlı akışları da tanıtmak olurdu . Bu akışlar, insan tarafından okunabilir biçimlendirme / görüntüleme endişesi olmadan ikili veriler üzerinde çalışacaktır. C ++ nesnelerini taşınabilir bayt dizilerine çevirmek için yalnızca serileştirme / seriyi kaldırma tesisleri olarak kullanılabilirler.


cevapladığınız için teşekkürler. Bu konuda yanılmış olabilirim, ancak son noktanızla ilgili olarak (bayt tabanlı ve karakter tabanlı akışlar), IOStream'in (kısmi?) Buna yanıtı akış arabellekleri arasındaki ayrım (karakter dönüştürme, taşıma ve arabelleğe alma) değil mi? ve akışlar (biçimlendirme / ayrıştırma)? Ve sadece (makine tarafından okunabilir) serileştirme ve seriyi kaldırma amaçlı olanlar ve (insan tarafından okunabilir) biçimlendirme ve ayrıştırmaya yönelik benzersiz bir şekilde tasarlanmış yeni akış sınıfları oluşturamaz mıydınız?
stakx -

@stakx, evet ve aslında bunu yaptım. Göründüğünden biraz daha can sıkıcı çünkü std::char_traitsbir unsigned char. Bununla birlikte, geçici çözümler var, bu yüzden sanırım genişletilebilirlik bir kez daha kurtarmaya geliyor. Ancak bayt tabanlı akışların standart olmadığı gerçeğinin kütüphanenin bir zayıflığı olduğunu düşünüyorum.
Charles Salvia

4
Ayrıca, ikili akışları uygulamak , biçimlendirme endişelerinden tamamen ayrı olmadığından, yeni akış sınıfları ve yeni tampon sınıfları uygulamanızı gerektirir std::streambuf. Yani, temelde genişlettiğiniz tek şey std::basic_iossınıftır. Dolayısıyla, "genişletmenin" "tamamen yeniden uygulama" bölgesine geçtiği bir çizgi var ve C ++ I / O akış tesislerinden bir ikili akış oluşturmak bu noktaya yaklaşıyor gibi görünüyor.
Charles Salvia

iyi dedim ve tam olarak ne şüphelendim. Ve hem C hem de C ++ 'nın belirli bit genişlikleri ve gösterimleri hakkında garanti vermemek için büyük çaba sarf etmesi , G / Ç yapmak söz konusu olduğunda gerçekten sorunlu hale gelebilir.
stakx -

" Taşınabilir biçime nesne seri hale getirilmeye. " Hayır, onlar destek amacıyla asla olduğunu
curiousguy

11

C ++ IOStreams'i her zaman kötü tasarlanmış bulmuşumdur: bunların uygulanması, yeni bir akım türünü doğru şekilde tanımlamayı çok zorlaştırır. ayrıca io özelliklerini ve biçimlendirme özelliklerini karıştırırlar (manipülatörler hakkında düşünün).

şahsen, bulduğum en iyi akış tasarımı ve uygulaması Ada programlama dilinde yatıyor. bu, ayrıştırmada bir model, yeni tür akışlar yaratmak için bir zevktir ve çıktı işlevleri, kullanılan akıştan bağımsız olarak her zaman çalışır. bu, en az yaygın olan bir paydaya teşekkür eder: bir akışa bayt çıkışı yaparsınız ve bu kadar. akış işlevleri baytları akışa koymaya özen gösterir, mesela bir tamsayıyı onaltılık tabana biçimlendirmek onların işi değildir (tabii ki, biçimlendirmeyi işlemek için tanımlanmış, bir sınıf üyesine eşdeğer bir tür öznitelikleri kümesi vardır)

C ++ 'ın akışlarla ilgili olarak basit olmasını diliyorum ...


Bahsettiğim kitap, temel IOStreams mimarisini şu şekilde açıklıyor: Bir taşıma katmanı (akış tampon sınıfları) ve bir ayrıştırma / biçimlendirme katmanı (akış sınıfları) var. İlki, bir yan test akışından / akışına karakterleri okumaktan / yazmaktan sorumluyken, ikincisi karakterleri ayrıştırmaktan veya değerleri karakterlere serileştirmekten sorumludur. Bu yeterince açık görünüyor, ancak bu endişelerin gerçekte gerçekten net bir şekilde ayrılıp ayrılmadığından emin değilim. yerel ayarlar devreye girdiğinde. - Yeni akış sınıfları uygulamanın zorluğu konusunda da sizinle aynı fikirdeyim.
stakx -

"mix io ​​özelliklerini ve biçimlendirme özelliklerini" <- Bunda yanlış olan nedir? Kütüphanenin amacı bu. Yeni dere yapmakla ilgili olarak, dere yerine dere yatağı yapmalı ve dere yatağı etrafında düz bir dere inşa etmelisiniz.
Billy ONeal

Görünüşe göre bu sorunun cevapları bana hiç açıklanmayan bir şeyi anlamamı sağladı: Bir akarsu yerine bir akarsu türetmeliyim ...
Adrien Plisson

@stakx: Eğer streambuf katmanı söylediğini yaptıysa, sorun olmaz. Ancak karakter dizisi ve bayt arasındaki dönüşümün tümü gerçek G / Ç (dosya, konsol vb.) İle karıştırılır. Karakter dönüşümü yapmadan dosya G / Ç işlemini gerçekleştirmenin bir yolu yok, bu çok talihsiz bir durum.
Ben Voigt

10

IOStreams tasarımının genişletilebilirlik ve kullanışlılık açısından mükemmel olduğunu düşünüyorum.

  1. Akış tamponları: boost.iostream uzantılarına bir göz atın: gzip, tee, akışları birkaç satırda kopyalayın, özel filtreler oluşturun vb. Onsuz mümkün olmazdı.
  2. Yerelleştirme entegrasyonu ve biçimlendirme entegrasyonu. Neler yapılabileceğini görün:

    std::cout << as::spellout << 100 << std::endl;

    Yazdırabilir: "yüz" veya hatta:

    std::cout << translate("Good morning")  << std::endl;

    Yerleştirilen yere göre "Bonjour" veya "בוקר טוב" yazdırabilir std::cout!

    Bu tür şeyler sadece iostreams çok esnek olduğu için yapılabilir.

Daha iyi yapılabilir mi?

Tabii ki olabilir! Aslında geliştirilebilecek pek çok şey var ...

Günümüzde, doğru bir şekilde türetilmesi stream_bufferoldukça zahmetli, akışa ek biçimlendirme bilgileri eklemek oldukça önemsiz değil, ancak mümkün.

Ama yıllar öncesine baktığımda, hala kütüphane tasarımı pek çok güzellik getirecek kadar iyiydi.

Çünkü her zaman büyük resmi göremezsiniz, ancak uzantılara puan bırakırsanız, düşünmediğiniz noktalarda bile size çok daha iyi yetenekler verir.


5
2. nokta için örneklerinizin neden basitçe print (spellout(100));ve print (translate("Good morning"));gibi bir şeyi kullanmaktan daha iyi olacağına dair bir yorum yapabilir misiniz? Bu, biçimlendirmeyi ve i18n'yi I / O'dan ayırdığı için bu iyi bir fikir gibi görünebilir.
Schedler

3
Çünkü akarsu içine yerleştirilmiş dile göre tercüme edilebilir. yani french_output << translate("Good morning"):; english_output << translate("Good morning") size şunu verir: "Bonjour Günaydın"
Artyom

3
Bir dilde '<< "metin" << değer "ancak başka bir dilde" << değer << "metin"' yapmanız gerektiğinde yerelleştirme çok daha zordur - printf ile karşılaştırıldığında
Martin Beckett

@Martin Beckett Biliyorum, Boost.Locale kütüphanesine bir göz atın, böyle bir durumda ne olur out << format("text {1}") % valueve tercüme edilebilir "{1} translated". Yani iyi çalışıyor ;-).
Artyom

15
Ne "yapılabilir" pek alakalı değil. Sen bir programcısın, her şey yeterli çabayla yapılabilir . Ancak IOStreams, yapılabileceklerin çoğunu elde etmeyi çok acı verici hale getiriyor . Ve sıkıntınız için genellikle kötü performans alırsınız.
jalf

2

(Bu cevap sadece benim düşünceme dayanmaktadır)

IOStreams'in işlev eşdeğerlerinden çok daha karmaşık olduğunu düşünüyorum. C ++ ile yazdığımda, çok daha öngörülebilir bulduğum "eski tarz" G / Ç için hala cstdio başlıklarını kullanıyorum. Bir yan not olarak, (gerçekten önemli olmasa da; mutlak zaman farkı ihmal edilebilir) IOStreams'ın CI / O'dan daha yavaş olduğu birçok durumda kanıtlanmıştır.


Sanırım "işlevsel" yerine "işlev" demek istiyorsun. işlevsel programlama, genel programlamadan daha kötü görünen kod üretir.
Chris Becke

Bu hatayı işaret ettiğiniz için teşekkürler; Düzeltmeyi yansıtmak için cevabı düzenledim.
Delan Azabani

5
IOStreams neredeyse kesinlikle klasik stdio'dan daha yavaş olmalıydı; Genişletilebilir ve kullanımı kolay bir G / Ç akış çerçevesi tasarlama görevi bana verilseydi, gerçek darboğazların büyük olasılıkla dosya G / Ç hızı veya ağ trafiği bant genişliği olacağı düşünüldüğünde, muhtemelen hızı ikincil olarak değerlendirirdim.
stakx -

1
I / O veya ağ için hesaplama hızının o kadar önemli olmadığını kabul ediyorum. Ancak, sayısal / dize dönüşümü için C ++ 'ın kullanıldığını unutmayın sstringstream. Bence hız ikincil olsa da önemli.
Matthieu M.

1
@stakx dosya G / Ç ve ağ darboğazları, oldukça küçük olan ve teknolojik gelişmelerle önemli ölçüde azaltan 'bayt başına' maliyetlerin bir fonksiyonudur. Ayrıca, DMA verildiğinde, bu ek yükler aynı makinedeki diğer iş parçacıklarından CPU zamanını alamaz. Dolayısıyla, biçimlendirilmiş çıktı yapıyorsanız, bunu verimli bir şekilde yapmanın maliyeti veya yapmamanın maliyeti kolayca önemli olabilir (en azından, disk veya ağ tarafından gölgede bırakılamaz; uygulamadaki diğer işlemler daha büyük olasılıkla gölgede kalır).
greggo

2

IOStream'i kullanırken her zaman sürprizlerle karşılaşıyorum.

Kütüphane, metin odaklı ve ikili odaklı değil gibi görünüyor. Bu ilk sürpriz olabilir: dosya akışlarında ikili bayrağı kullanmak, ikili davranış elde etmek için yeterli değildir. Yukarıdaki Charles Salvia kullanıcısı bunu doğru bir şekilde gözlemlemiştir: IOStreams, biçimlendirme yönlerini (güzel çıktı istediğiniz yerde, örneğin kayan sayılar için sınırlı rakamlar) serileştirme yönleriyle (bilgi kaybı istemediğiniz yerlerde) karıştırır. Muhtemelen bu yönleri ayırmak iyi olacaktır. Boost.Serialization bu yarıyı yapar. İsterseniz ekleyicileri ve çıkarıcıları yönlendiren bir serileştirme işlevine sahipsiniz. Zaten her iki yön arasında gerilim var.

Birçok işlevin kafa karıştırıcı anlambilimleri de vardır (ör. Get, getline, yok say ve oku. Bazıları sınırlayıcıyı çıkarır, bazıları çıkarmaz; ayrıca bazıları eof). Ayrıca bazıları, bir akışı uygularken garip işlev adlarından bahsederler (örn. Xsputn, uflow, underflow). Wchar_t değişkenleri kullanıldığında işler daha da kötüleşir. Wstringstream bunu yapmazken wifstream multibyte'a çeviri yapar. İkili G / Ç, wchar_t ile kutudan çıkmaz: codecvt'in üzerine yazabilirsiniz.

C tamponlu G / Ç (yani FILE), C ++ muadili kadar güçlü değildir, ancak daha şeffaftır ve çok daha az karşı sezgisel davranışa sahiptir.

Yine de IOStream'e her rastladığımda, ateşlenecek bir güve gibi ona çekiliyorum. Muhtemelen, gerçekten zeki bir adamın genel mimariye iyi bakması iyi bir şey olurdu.


1

Sorunun ilk bölümünü cevaplamaktan kendimi alamıyorum (Bunu kim yaptı?). Ancak diğer yazılarda cevaplandı.

Sorunun ikinci kısmına gelince (İyi tasarlanmış mı?), Cevabım yankılanan bir "Hayır!" İşte yıllardır hayretle kafamı sallayan küçük bir örnek:

#include <stdint.h>
#include <iostream>
#include <vector>

// A small attempt in generic programming ;)
template <class _T>
void ShowVector( const char *title, const std::vector<_T> &v)
{
    std::vector<_T>::const_iterator iter;
    std::cout << title << " (" << v.size() << " elements): ";
    for( iter = v.begin(); iter != v.end(); ++iter )
    {
        std::cout << (*iter) << " ";
    }
    std::cout << std::endl;
}
int main( int argc, const char * argv[] )
{
    std::vector<uint8_t> byteVector;
    std::vector<uint16_t> wordVector;
    byteVector.push_back( 42 );
    wordVector.push_back( 42 );
    ShowVector( "Garbled bytes as characters output o.O", byteVector );
    ShowVector( "With words, the numbers show as numbers.", wordVector );
    return 0;
}

Yukarıdaki kod, iostream tasarımı nedeniyle saçma sapan şeyler üretir. Benim kavrayışımın ötesindeki bazı nedenlerden dolayı, onlar uint8_t baytları karakter olarak ele alırken, daha büyük integral türleri sayılar gibi ele alınır. Qed Kötü tasarım.

Bunu düzeltmeyi düşünmemin bir yolu da yok. Tür, bunun yerine bir kayan nokta veya bir çift olabilir ... bu nedenle, 'int' türüne dönüştürülerek aptalca bir iostream, konunun karakterlerin değil sayıların olduğunu anlamasını sağlar.

Cevabıma bir olumsuz oy verdikten sonra, belki birkaç kelime daha açıklama ... IOStream tasarımı, programcıya bir öğenin NASIL işlendiğini belirtmek için bir araç sağlamadığından kusurludur. IOStream uygulaması rastgele kararlar verir (uint8_t'yi bir bayt numarası olarak değil, bir karakter olarak ele almak gibi). Bu, ulaşılmaz olanı başarmaya çalıştıkları için IOStream tasarımının bir kusuru.

C ++, bir türün sınıflandırılmasına izin vermez - dil, tesise sahip değildir. İs_number_type () veya is_character_type () IOStream'in makul bir otomatik seçim yapmak için kullanabileceği bir şey yoktur. Bunu görmezden gelmek ve tahmin etmekten sıyrılmak bir kütüphanenin tasarım kusurudur.

Kabul etmek gerekir ki, printf () genel bir "ShowVector ()" uygulamasında eşit derecede çalışmayacaktır. Ancak bu, iostream davranışı için bir mazeret değildir. Fakat printf () durumunda ShowVector () şu şekilde tanımlanacaktır:

template <class _T>
void ShowVector( const char *formatString, const char *title, const std::vector<_T> &v );

3
Suç (tamamen) iostream ile yalan söylemez. Senin yazının ne için uint8_tolduğunu kontrol et . Aslında bir karakter mi? Öyleyse, ona bir char gibi davrandığı için iostreams'i suçlamayın.
Martin Ba

Ve genel kodda bir sayı aldığınızdan emin olmak istiyorsanız , akış ekleme operatörü yerine num_putfaçeta kullanabilirsiniz .
Martin Ba

@Martin Ba Haklısınız - c / c ++ standartları bir "kısa işaretsiz int" in kaç bayta sahip olduğunu açık tutar. "imzasız karakter", dilin kendine özgü bir özelliğidir. Gerçekten bir bayt istiyorsanız, işaretsiz bir karakter kullanmanız gerekir. C ++, şablon argümanlarına kısıtlamalar getirmeye de izin vermiyor - "yalnızca sayılar" gibi ve bu nedenle ShowVector uygulamasını önerilen num_put çözümünüzle değiştirirsem, ShowVector artık bir dizge vektörü gösteremez, değil mi? ;)
BitTickler

1
@Martin Bla: cppreference, int8_t'nin tam olarak 8 bit genişliğinde imzalı bir tamsayı türü olduğundan bahsediyor. . Typedef yerine __int8 gerçek bir türe sahip olarak çözülebilirdi.
gast128

Oh, aslında düzeltmesi oldukça kolay: // işaretsiz / işaretli / karakter türleri için desteği bozan ve 8 bitlik tam sayıları karaktermiş gibi yazdıran std :: ostream için düzeltmeler. ad alanı ostream_fixes {inline std :: ostream & operator << (std :: ostream & os, unsigned char i) {return os << static_cast <unsigned int> (i); } satır içi std :: ostream & operator << (std :: ostream & os, işaretli karakter i) {os'a dönüş << static_cast <işaretli int> (i); }} // ad alanı ostream_fixes
mcv

1

Diğer yanıtlarda belirtildiği gibi, C ++ iostreams'in birçok kusuru var, ancak savunmasında bir şeyi not etmek istiyorum.

C ++, yeni başlayanlar için değişken girdi ve çıktıları kolaylaştıran ciddi kullanımda diller arasında neredeyse benzersizdir. Diğer dillerde, kullanıcı girdisi tür zorlaması veya dize biçimlendiricileri içerme eğilimindeyken, C ++ derleyicinin tüm işi yapmasını sağlar. Aynısı çıktı için büyük ölçüde doğrudur, ancak C ++ bu açıdan benzersiz değildir. Yine de, C ++ 'da, pedagojik açıdan yararlı olan ve biçim sözdizimini anlamak zorunda kalmadan, sınıfları ve nesne yönelimli kavramları anlamak zorunda kalmadan, biçimlendirilmiş G / Ç'yi oldukça iyi yapabilirsiniz. Yine, yeni başlayanlara öğretiyorsanız, bu büyük bir artı.

Yeni başlayanlar için bu basitliğin bir bedeli vardır ve bu, daha karmaşık durumlarda I / O ile başa çıkmak için baş ağrısına neden olabilir, ancak umarım bu noktada programcı onlarla başa çıkabilecek kadar öğrenmiş veya en azından yeterince yaşlanmıştır. içmek için.

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.