ใครเป็นผู้สร้าง / ออกแบบ IOStream ของ C ++ และจะยังถือว่าได้รับการออกแบบมาอย่างดีตามมาตรฐานปัจจุบันหรือไม่ [ปิด]


128

ก่อนอื่นอาจดูเหมือนว่าฉันกำลังขอความคิดเห็นส่วนตัว แต่นั่นไม่ใช่สิ่งที่ฉันตามมา ฉันชอบที่จะได้ยินข้อโต้แย้งที่มีเหตุผลในหัวข้อนี้


ในความหวังในการได้รับข้อมูลเชิงลึกบางเป็นวิธีการกรอบลำธาร / อนุกรมที่ทันสมัยควรจะได้รับการออกแบบที่ฉันเพิ่งได้ตัวเองสำเนาของหนังสือc ++ มาตรฐาน IOStreams และตำแหน่งที่ตั้งโดย Angelika แลงเกอร์และเคลาส์ Kreft ฉันคิดว่าถ้า IOStreams ไม่ได้รับการออกแบบมาอย่างดีมันจะไม่ทำให้มันอยู่ในไลบรารีมาตรฐาน C ++ ตั้งแต่แรก

หลังจากได้อ่านส่วนต่างๆของหนังสือเล่มนี้ฉันเริ่มมีข้อสงสัยว่า IOStreams สามารถเปรียบเทียบกับเช่น STL จากมุมมองทางสถาปัตยกรรมโดยรวมได้หรือไม่ อ่านเช่นบทสัมภาษณ์ของ Alexander Stepanov ("นักประดิษฐ์" ของ STL)เพื่อเรียนรู้เกี่ยวกับการตัดสินใจในการออกแบบบางอย่างที่เข้าสู่ STL

สิ่งที่ทำให้ฉันประหลาดใจเป็นพิเศษ :

  • ดูเหมือนว่าจะไม่ทราบว่าใครเป็นผู้รับผิดชอบการออกแบบโดยรวมของ IOStreams (ฉันชอบอ่านข้อมูลพื้นฐานเกี่ยวกับเรื่องนี้ - มีใครรู้แหล่งข้อมูลดีๆบ้างไหม);

  • เมื่อคุณเจาะลึกลงไปใต้พื้นผิวของ IOStreams ในทันทีเช่นหากคุณต้องการขยาย IOStreams ด้วยคลาสของคุณเองคุณจะเข้าสู่อินเทอร์เฟซที่มีชื่อฟังก์ชันสมาชิกที่ค่อนข้างคลุมเครือและสับสนเช่นgetloc/ imbue, uflow/ underflow, snextc/ sbumpc/ sgetc/ sgetn, pbase/ pptr/ epptr(และมี อาจเป็นตัวอย่างที่แย่กว่านั้น) สิ่งนี้ทำให้เข้าใจการออกแบบโดยรวมและการทำงานร่วมกันของชิ้นส่วนเดี่ยวได้ยากขึ้นมาก แม้หนังสือที่ผมกล่าวข้างต้นไม่ได้ช่วยที่มาก (IMHO)


ดังนั้นคำถามของฉัน:

หากคุณมีที่จะตัดสินตามมาตรฐานวิศวกรรมซอฟต์แวร์ของวันนี้ (ถ้ามีจริงเป็นข้อตกลงใด ๆ ทั่วไปเกี่ยวกับเหล่านี้) จะ c ++ 's IOStreams ยังถือว่ามีการออกแบบที่ดี? (ฉันไม่ต้องการพัฒนาทักษะการออกแบบซอฟต์แวร์จากสิ่งที่ถือว่าล้าสมัยโดยทั่วไป)


7
ความเห็นที่น่าสนใจของ Herb Sutter stackoverflow.com/questions/2485963/… :) เสียดายผู้ชายคนนั้นออกจาก SO หลังจากเข้าร่วมเพียงไม่กี่วัน
Johannes Schaub - litb

5
มีใครอีกบ้างที่เห็นความกังวลในสตรีม STL โดยปกติสตรีมได้รับการออกแบบมาเพื่ออ่านหรือเขียนไบต์และไม่มีสิ่งอื่นใด สิ่งที่สามารถอ่านหรือเขียนชนิดข้อมูลเฉพาะคือฟอร์แมตเตอร์ (ซึ่งอาจ แต่ไม่จำเป็นต้องใช้สตรีมเพื่ออ่าน / เขียนไบต์ที่จัดรูปแบบ) การผสมทั้งสองอย่างเป็นคลาสเดียวทำให้การใช้สตรีมของตัวเองซับซ้อนมากยิ่งขึ้น
mmmmmmmm

4
@rsteven มีการแยกความกังวลเหล่านั้นออกจากกัน std::streambufเป็นคลาสฐานสำหรับการอ่านและเขียนไบต์และistream/ ใช้ostreamสำหรับการจัดรูปแบบในและเอาต์พุตโดยใช้ตัวชี้std::streambufเป็นปลายทาง / ต้นทาง
Johannes Schaub - litb

1
@litb: แต่เป็นไปได้ไหมที่จะเปลี่ยน streambuf ที่สตรีมใช้ (ฟอร์แมตเตอร์)? ดังนั้นฉันอาจต้องการใช้การจัดรูปแบบ STL แต่ต้องการเขียนข้อมูลผ่าน streambuf เฉพาะหรือไม่?
mmmmmmmm

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

คำตอบ:


31

หลายความคิดที่ไม่ดีรู้สึกพบวิธีที่พวกเขาเป็นมาตรฐาน: auto_ptr, vector<bool>, valarrayและexportเพียงเพื่อชื่อไม่กี่ ดังนั้นฉันจะไม่นำ IOStreams มาเป็นสัญลักษณ์ของการออกแบบที่มีคุณภาพ

IOStreams มีประวัติตาหมากรุก จริงๆแล้วพวกเขาเป็นการนำไลบรารีสตรีมก่อนหน้านี้มาใช้ใหม่ แต่ถูกเขียนขึ้นในช่วงเวลาที่ไม่มีสำนวน C ++ ในปัจจุบันจำนวนมากดังนั้นนักออกแบบจึงไม่ได้รับประโยชน์จากการมองย้อนกลับไป ปัญหาหนึ่งที่เห็นได้ชัดเมื่อเวลาผ่านไปคือแทบจะเป็นไปไม่ได้เลยที่จะใช้ IOStreams ได้อย่างมีประสิทธิภาพเท่ากับ stdio ของ C เนื่องจากการใช้ฟังก์ชันเสมือนจำนวนมากและการส่งต่อไปยังวัตถุบัฟเฟอร์ภายในที่ละเอียดที่สุดและยังต้องขอบคุณความแปลกประหลาดที่ไม่อาจหยั่งรู้ได้ ในวิธีการกำหนดและใช้งานโลแคล ความทรงจำของฉันเกี่ยวกับเรื่องนี้ค่อนข้างคลุมเครือฉันยอมรับ ฉันจำได้ว่ามันเป็นประเด็นที่ถกเถียงกันอย่างหนักเมื่อหลายปีก่อนบน comp.lang.c ++.


3
ขอบคุณสำหรับข้อมูลของคุณ ฉันจะเรียกดูที่comp.lang.c++.moderatedเก็บถาวรและโพสต์ลิงก์ที่ด้านล่างของคำถามของฉันหากฉันพบสิ่งที่มีค่า - นอกจากนี้ฉันไม่กล้าเห็นด้วยกับคุณauto_ptr: หลังจากอ่านC ++ พิเศษของ Herb Sutter ดูเหมือนว่าจะเป็นคลาสที่มีประโยชน์มากเมื่อใช้รูปแบบ RAII
stakx - ไม่ร่วมให้ข้อมูลใน

5
@stakx: อย่างไรก็ตามมันกำลังถูกเลิกใช้และถูกแทนที่unique_ptrด้วยความหมายที่ชัดเจนและมีพลังมากขึ้น
UncleBens

3
@UncleBens unique_ptrต้องการการอ้างอิง rvalue ดังนั้น ณ จุดนี้auto_ptrจึงเป็นตัวชี้ที่ทรงพลังมาก
Artyom

7
แต่auto_ptrได้ทำให้ความหมายของการคัดลอก / การกำหนดความผิดพลาดซึ่งทำให้เป็นช่องสำหรับ dereferencing bugs ...
Matthieu M.

5
@TokenMacGuy: มันไม่ใช่เวกเตอร์และไม่ได้เก็บบูล ซึ่งทำให้เข้าใจผิดอยู่บ้าง. ;)
jalf

40

เกี่ยวกับผู้ออกแบบห้องสมุดดั้งเดิมนั้น (ไม่น่าแปลกใจ) ที่สร้างขึ้นโดย Bjarne Stroustrup จากนั้นนำไปใช้ใหม่โดย Dave Presotto จากนั้นสิ่งนี้ได้รับการออกแบบใหม่และนำมาใช้ใหม่อีกครั้งโดย Jerry Schwarz สำหรับ Cfront 2.0 โดยใช้แนวคิดของผู้ควบคุมจาก Andrew Koenig ไลบรารีเวอร์ชันมาตรฐานขึ้นอยู่กับการใช้งานนี้

ที่มา "The Design & Evolution of C ++", section 8.3.1.


3
@ นีล - นัทคุณมีความคิดเห็นอย่างไรกับการออกแบบ? จากคำตอบอื่น ๆ ของคุณหลายคนชอบที่จะได้ยินความคิดเห็นของคุณ ...
DVK

1
@DVK โพสต์ความคิดเห็นของฉันเป็นคำตอบแยกต่างหาก

2
เพิ่งพบบทสัมภาษณ์ของ Bjarne Stroustrup ที่เขากล่าวถึงประวัติ IOStreams บางส่วน: www2.research.att.com/~bs/01chinese.html (ลิงก์นี้ดูเหมือนจะใช้งานไม่ได้ชั่วคราวในตอนนี้ แต่คุณสามารถลอง แคชของหน้า Google)
stakx - ไม่ร่วมให้ข้อมูลใน

2
การเชื่อมโยง Updated: stroustrup.com/01chinese.html
FrankHB

28

หากคุณต้องตัดสินตามมาตรฐานวิศวกรรมซอฟต์แวร์ในปัจจุบัน (หากมีข้อตกลงทั่วไปเกี่ยวกับสิ่งเหล่านี้จริง) IOStream ของ C ++ จะยังคงได้รับการพิจารณาว่าได้รับการออกแบบมาอย่างดีหรือไม่? (ฉันไม่ต้องการพัฒนาทักษะการออกแบบซอฟต์แวร์จากสิ่งที่ถือว่าล้าสมัยโดยทั่วไป)

ฉันจะตอบว่าไม่ด้วยเหตุผลหลายประการ:

การจัดการข้อผิดพลาดไม่ดี

operator void*เงื่อนไขข้อผิดพลาดควรมีการรายงานมีข้อยกเว้นไม่ได้กับ

"การวัตถุผีดิบ" ต่อต้านรูปแบบเป็นสิ่งที่ทำให้เกิดข้อผิดพลาดเช่นนี้

การแยกไม่ดีระหว่างการจัดรูปแบบและ I / O

สิ่งนี้ทำให้วัตถุสตรีมไม่จำเป็นต้องซับซ้อนเนื่องจากต้องมีข้อมูลสถานะเพิ่มเติมสำหรับการจัดรูปแบบไม่ว่าคุณจะต้องการหรือไม่ก็ตาม

นอกจากนี้ยังเพิ่มโอกาสในการเขียนข้อบกพร่องเช่น:

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

หากเป็นเช่นนั้นคุณเขียนว่า:

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

จะไม่มีบิตสถานะที่เกี่ยวข้องกับการจัดรูปแบบและไม่มีปัญหา

โปรดทราบว่าในภาษา "สมัยใหม่" เช่น Java, C # และ Python อ็อบเจ็กต์ทั้งหมดมีtoString/ ToString/ __str__ฟังก์ชันที่เรียกโดยรูทีน I / O AFAIK มีเพียง C ++ เท่านั้นที่ทำในทางอื่นโดยใช้stringstreamเป็นวิธีมาตรฐานในการแปลงเป็นสตริง

รองรับ i18n ไม่ดี

เอาต์พุตที่ใช้ Iostream จะแยกตัวอักษรสตริงออกเป็นชิ้น ๆ

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

จัดรูปแบบสตริงใส่ประโยคทั้งหมดลงในตัวอักษรสตริง

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

แนวทางหลังนี้ง่ายกว่าในการปรับให้เข้ากับไลบรารีสากลเช่น GNU gettext เนื่องจากการใช้ประโยคทั้งหมดให้บริบทมากขึ้นสำหรับผู้แปล หากรูทีนการจัดรูปแบบสตริงของคุณรองรับการเรียงลำดับใหม่ (เช่น$พารามิเตอร์POSIX printf) ก็จะจัดการความแตกต่างของลำดับคำระหว่างภาษาได้ดีขึ้น


4
จริงๆแล้วสำหรับ i18n การแทนที่ควรระบุด้วยตำแหน่ง (% 1,% 2, .. ) เนื่องจากการแปลอาจจำเป็นต้องเปลี่ยนลำดับพารามิเตอร์ มิฉะนั้นฉันเห็นด้วยอย่างเต็มที่ - +1
peterchen

4
@peterchen: นั่นคือสิ่ง POSIX $specifiers สำหรับprintfเป็น
jamesdlin

2
ปัญหาไม่ใช่การจัดรูปแบบสตริงเนื่องจาก C ++ มี varargs ที่ไม่ใช่ typesafe
dan04

5
ตั้งแต่ C ++ 11 ตอนนี้มี typesafe varargs
Mooing Duck

2
IMHO 'ข้อมูลพิเศษของรัฐ' เป็นปัญหาที่เลวร้ายที่สุด cout เป็นสากล การแนบแฟล็กการจัดรูปแบบเข้ากับแฟล็กเหล่านั้นทำให้แฟล็กเหล่านั้นเป็นแบบโกลบอลและเมื่อคุณพิจารณาว่าการใช้แฟล็กส่วนใหญ่มีขอบเขตที่ตั้งใจไว้เพียงไม่กี่บรรทัดมันก็แย่มาก มันจะเป็นไปได้ที่จะแก้ไขด้วยคลาส 'ฟอร์แมตเตอร์' ที่ผูกกับออสสตรีม แต่คงสถานะของตัวเองไว้ และสิ่งที่ทำด้วย cout โดยทั่วไปดูแย่มากเมื่อเทียบกับสิ่งเดียวกันกับ printf (เมื่อเป็นไปได้) ..
greggo

17

ฉันโพสต์ข้อความนี้เป็นคำตอบแยกต่างหากเนื่องจากเป็นความคิดเห็นที่บริสุทธิ์

การดำเนินการอินพุตและเอาต์พุต (โดยเฉพาะอินพุต) เป็นปัญหาที่ยากมากดังนั้นจึงไม่น่าแปลกใจที่ไลบรารี iostreams เต็มไปด้วย bodges และสิ่งต่างๆที่มีการมองย้อนกลับที่สมบูรณ์แบบน่าจะทำได้ดีกว่า แต่สำหรับฉันแล้วดูเหมือนว่าไลบรารี I / O ทั้งหมดไม่ว่าจะเป็นภาษาอะไรก็ตาม ฉันไม่เคยใช้ภาษาโปรแกรมที่ระบบ I / O เป็นสิ่งสวยงามที่ทำให้ฉันรู้สึกทึ่งในตัวนักออกแบบ ไลบรารี iostreams มีข้อดีโดยเฉพาะเหนือไลบรารี CI / O (ความสามารถในการขยายความสามารถในการพิมพ์ ฯลฯ ) แต่ฉันไม่คิดว่าจะมีใครถือเป็นตัวอย่างของ OO ที่ยอดเยี่ยมหรือการออกแบบทั่วไป


16

ความคิดเห็นของฉันเกี่ยวกับ C ++ iostreams ได้รับการปรับปรุงอย่างมากเมื่อเวลาผ่านไปโดยเฉพาะอย่างยิ่งหลังจากที่ฉันเริ่มขยายความจริงด้วยการใช้คลาสสตรีมของฉันเอง ฉันเริ่มชื่นชมความสามารถในการขยายตัวและการออกแบบโดยรวมแม้จะมีชื่อฟังก์ชันของสมาชิกที่ไม่ดีอย่างน่าขันเช่นxsputnหรืออะไรก็ตาม โดยไม่คำนึงถึงฉันคิดว่าสตรีม I / O เป็นการปรับปรุงครั้งใหญ่เหนือ C stdio.h ซึ่งไม่มีความปลอดภัยและเต็มไปด้วยข้อบกพร่องด้านความปลอดภัยที่สำคัญ

ฉันคิดว่าปัญหาหลักของสตรีม IO คือพวกเขารวมสองแนวคิดที่เกี่ยวข้องกัน แต่ค่อนข้างตั้งฉากกัน: การจัดรูปแบบข้อความและการทำให้เป็นอนุกรม ในอีกด้านหนึ่งสตรีม IO ได้รับการออกแบบมาเพื่อสร้างการแสดงข้อความที่มนุษย์อ่านได้ในรูปแบบของวัตถุและในทางกลับกันเพื่อจัดลำดับวัตถุให้อยู่ในรูปแบบพกพา บางครั้งเป้าหมายทั้งสองนี้เป็นหนึ่งเดียวกัน แต่ในบางครั้งก็ส่งผลให้เกิดความไม่ลงรอยกันที่น่ารำคาญอย่างมาก ตัวอย่างเช่น:

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

...

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

ที่นี่สิ่งที่เราได้รับจากอินพุตไม่ใช่สิ่งที่เราส่งออกไปยังสตรีมในตอนแรก เนื่องจากตัว<<ดำเนินการส่งออกสตริงทั้งหมดในขณะที่>>ตัวดำเนินการจะอ่านจากสตรีมจนกว่าจะพบอักขระเว้นวรรคเนื่องจากไม่มีข้อมูลความยาวที่เก็บไว้ในสตรีม ดังนั้นแม้ว่าเราจะส่งออกสตริงออบเจ็กต์ที่มีคำว่า "hello world" แต่เราจะป้อนเฉพาะสตริงที่มีคำว่า "hello" เท่านั้น ดังนั้นในขณะที่สตรีมทำหน้าที่เป็นอุปกรณ์อำนวยความสะดวกในการจัดรูปแบบ แต่ก็ล้มเหลวในการทำให้เป็นอนุกรมอย่างถูกต้องจากนั้นจึงยกเลิกการกำหนดค่าเริ่มต้นของวัตถุ

คุณอาจพูดได้ว่าสตรีม IO ไม่ได้ออกแบบมาให้เป็นสิ่งอำนวยความสะดวกในการทำให้เป็นอนุกรม แต่ถ้าเป็นเช่นนั้นอินพุตสตรีมมีไว้เพื่ออะไร นอกจากนี้ในทางปฏิบัติสตรีม I / O มักใช้เพื่อทำให้เป็นอนุกรมวัตถุเนื่องจากไม่มีสิ่งอำนวยความสะดวกในการทำให้เป็นอนุกรมมาตรฐานอื่น ๆ พิจารณาboost::date_timeหรือboost::numeric::ublas::matrixหากคุณส่งออกวัตถุเมทริกซ์ด้วยตัว<<ดำเนินการคุณจะได้เมทริกซ์ที่แน่นอนเหมือนกันเมื่อคุณป้อนข้อมูลโดยใช้>>ดำเนินการ แต่เพื่อให้บรรลุเป้าหมายนี้นักออกแบบ Boost ต้องจัดเก็บข้อมูลจำนวนคอลัมน์และข้อมูลการนับแถวเป็นข้อมูลที่เป็นข้อความในเอาต์พุตซึ่งจะส่งผลต่อการแสดงผลที่มนุษย์อ่านได้จริง อีกครั้งเป็นการผสมผสานระหว่างสิ่งอำนวยความสะดวกในการจัดรูปแบบข้อความและการทำให้เป็นอนุกรม

สังเกตว่าภาษาอื่น ๆ ส่วนใหญ่แยกสิ่งอำนวยความสะดวกทั้งสองนี้อย่างไร ตัวอย่างเช่นใน Java การจัดรูปแบบทำได้โดยใช้toString()วิธีการนี้ในขณะที่การทำให้เป็นอนุกรมทำได้ผ่านไฟล์Serializableอินเทอร์เฟซ

ในความคิดของฉันทางออกที่ดีที่สุดคือการแนะนำสตรีมแบบไบต์ควบคู่ไปกับสตรีมตามอักขระมาตรฐาน สตรีมเหล่านี้จะทำงานบนข้อมูลไบนารีโดยไม่ต้องกังวลกับการจัดรูปแบบ / การแสดงผลที่มนุษย์อ่านได้ สามารถใช้เป็นสิ่งอำนวยความสะดวกในการทำให้เป็นอนุกรม / deserialization เพื่อแปลวัตถุ C ++ เป็นลำดับไบต์แบบพกพา


ขอบคุณสำหรับคำตอบ. ฉันอาจจะผิดเกี่ยวกับเรื่องนี้ แต่เกี่ยวกับจุดสุดท้ายของคุณ (สตรีมแบบไบต์กับสตรีมตามอักขระ) ไม่ใช่คำตอบของ IOStream (บางส่วน?) สำหรับสิ่งนี้การแยกระหว่างสตรีมบัฟเฟอร์ (การแปลงอักขระการขนส่งและการบัฟเฟอร์) และสตรีม (การจัดรูปแบบ / การแยกวิเคราะห์)? และคุณไม่สามารถสร้างคลาสสตรีมใหม่สิ่งที่มีไว้สำหรับการทำให้เป็นอนุกรมและ deserializing (อ่านได้โดยเครื่อง) และอื่น ๆ ที่มุ่งเน้นไปที่การจัดรูปแบบและการแยกวิเคราะห์ (ที่มนุษย์อ่านได้) โดยเฉพาะหรือไม่?
stakx - ไม่ร่วมให้ข้อมูลใน

@stakx ใช่และอันที่จริงฉันได้ทำสิ่งนี้แล้ว มันน่ารำคาญกว่าที่คิดเล็กน้อยเนื่องจากstd::char_traitsไม่สามารถพกพาunsigned charไฟล์. อย่างไรก็ตามมีวิธีแก้ปัญหาดังนั้นฉันเดาว่าการขยายความสามารถมาช่วยอีกครั้ง แต่ฉันคิดว่าการสตรีมแบบไบต์ไม่ใช่มาตรฐานเป็นจุดอ่อนของไลบรารี
Charles Salvia

4
นอกจากนี้การดำเนินการไบนารีลำธารคุณจะต้องดำเนินการเรียนกระแสใหม่และstd::streambufเรียนบัฟเฟอร์ใหม่ตั้งแต่การจัดรูปแบบความกังวลไม่ได้แยกออกจากกัน โดยพื้นฐานแล้วสิ่งเดียวที่คุณขยายคือstd::basic_iosคลาส ดังนั้นจึงมีเส้นที่ "ขยาย" ข้ามไปยังพื้นที่ "นำไปใช้ใหม่ทั้งหมด" และการสร้างสตรีมไบนารีจากสิ่งอำนวยความสะดวกสตรีม C ++ I / O ดูเหมือนจะเข้าใกล้จุดนั้น
Charles Salvia

พูดได้ดีและสิ่งที่ฉันสงสัย และความจริงที่ว่าทั้ง C และ C ++ มีความยาวมากเพื่อไม่ให้การรับประกันเกี่ยวกับความกว้างบิตและการแทนค่าเฉพาะอาจกลายเป็นปัญหาเมื่อต้องทำ I / O
stakx - ไม่ร่วมให้ข้อมูลใน

" เพื่อทำให้วัตถุเป็นอนุกรมในรูปแบบพกพา " ไม่พวกเขาไม่เคยตั้งใจที่จะสนับสนุนสิ่งนั้น
ขี้สงสัย

11

ฉันพบว่า C ++ IOStreams ออกแบบมาไม่ดีเสมอ: การใช้งานทำให้ยากมากที่จะกำหนดสตรีมประเภทใหม่อย่างถูกต้อง พวกเขายังผสมผสานคุณสมบัติ io และคุณสมบัติการจัดรูปแบบ (ลองนึกถึงตัวปรับแต่ง)

โดยส่วนตัวแล้วการออกแบบและการใช้งานสตรีมที่ดีที่สุดที่ฉันเคยพบนั้นอยู่ในภาษาโปรแกรม Ada เป็นแบบจำลองในการแยกส่วนความสุขในการสร้างสตรีมรูปแบบใหม่และฟังก์ชันเอาต์พุตจะทำงานตลอดเวลาโดยไม่คำนึงถึงสตรีมที่ใช้ นี่ต้องขอบคุณตัวส่วนร่วมน้อยที่สุด: คุณส่งออกไบต์ไปยังสตรีมและนั่นก็คือมัน ฟังก์ชั่นสตรีมดูแลการใส่ไบต์ลงในสตรีมไม่ใช่หน้าที่ของพวกเขาเช่นจัดรูปแบบจำนวนเต็มให้เป็นเลขฐานสิบหก (แน่นอนว่ามีชุดแอตทริบิวต์ประเภทเทียบเท่ากับสมาชิกคลาสที่กำหนดไว้สำหรับจัดการการจัดรูปแบบ)

ฉันหวังว่า C ++ เป็นเรื่องง่ายสำหรับสตรีม ...


หนังสือที่ฉันกล่าวถึงอธิบายสถาปัตยกรรม IOStreams พื้นฐานดังนี้มีเลเยอร์การขนส่ง (คลาสบัฟเฟอร์สตรีม) และเลเยอร์การแยกวิเคราะห์ / การจัดรูปแบบ (คลาสสตรีม) อดีตมีหน้าที่ในการอ่าน / เขียนอักขระจาก / ไปยัง bytestream ในขณะที่ตัวหลังมีหน้าที่ในการแยกวิเคราะห์อักขระหรือการจัดลำดับค่าเป็นอักขระ ดูเหมือนจะชัดเจนเพียงพอ แต่ฉันไม่แน่ใจว่าข้อกังวลเหล่านี้แยกออกจากกันอย่างชัดเจนในความเป็นจริงหรือไม่โดยเฉพาะ เมื่อภาษาเข้ามามีบทบาท - ฉันเห็นด้วยกับคุณเกี่ยวกับความยากลำบากในการใช้คลาสสตรีมใหม่
stakx - ไม่ร่วมให้ข้อมูลใน

"mix io ​​features and formatting features" <- เกิดอะไรขึ้น? นั่นคือประเด็นของห้องสมุด เกี่ยวกับการสร้างสตรีมใหม่คุณควรสร้างสตรีมบัฟแทนสตรีมและสร้างสตรีมธรรมดารอบสตรีมบัฟ
Billy ONeal

ดูเหมือนว่าคำตอบสำหรับคำถามนี้ทำให้ฉันเข้าใจบางสิ่งที่ฉันไม่เคยอธิบาย: ฉันควรได้รับ streambuf แทนที่จะเป็นสตรีม ...
Adrien Plisson

@stakx: ถ้าเลเยอร์ streambuf ทำตามที่คุณบอกก็คงจะดี แต่การแปลงระหว่างลำดับอักขระและไบต์ทั้งหมดผสมกับ I / O จริง (ไฟล์คอนโซล ฯลฯ ) ไม่มีวิธีใดในการดำเนินการ I / O ของไฟล์โดยไม่ต้องทำการแปลงอักขระซึ่งเป็นเรื่องที่โชคร้ายมาก
Ben Voigt

10

ฉันคิดว่าการออกแบบ IOStreams นั้นยอดเยี่ยมในแง่ของความสามารถในการขยายและประโยชน์

  1. บัฟเฟอร์สตรีม: ดูส่วนขยาย boost.iostream: สร้าง gzip, tee, คัดลอกสตรีมในไม่กี่บรรทัดสร้างตัวกรองพิเศษและอื่น ๆ มันจะเป็นไปไม่ได้เลยถ้าไม่มีมัน
  2. การรวมการแปลและการรวมการจัดรูปแบบ ดูสิ่งที่สามารถทำได้:

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

    สามารถพิมพ์: "หนึ่งร้อย" หรือแม้กระทั่ง:

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

    สามารถพิมพ์ "Bonjour" หรือ "בוקרטוב" ตามสถานที่ที่ต้องการได้ std::cout !

    สิ่งดังกล่าวสามารถทำได้เพียงเพราะ iostreams มีความยืดหยุ่นมาก

จะทำได้ดีกว่านี้ไหม

แน่นอนมันทำได้!ที่จริงมีหลายอย่างที่ควรปรับปรุง ...

วันนี้มันค่อนข้างเจ็บปวดที่จะได้มาอย่างถูกต้อง stream_bufferต้องการเพิ่มข้อมูลการจัดรูปแบบเพิ่มเติมในสตรีมนั้นค่อนข้างไม่สำคัญ แต่เป็นไปได้

แต่เมื่อมองย้อนกลับไปเมื่อหลายปีก่อนฉันยังคงออกแบบห้องสมุดได้ดีพอที่จะนำเสนอสินค้ามากมาย

เนื่องจากคุณไม่สามารถมองเห็นภาพรวมได้เสมอไป แต่ถ้าคุณทิ้งจุดสำหรับส่วนขยายไว้มันจะทำให้คุณมีความสามารถที่ดีขึ้นมากแม้ในจุดที่คุณไม่ได้คิด


5
คุณช่วยแสดงความคิดเห็นได้ไหมว่าทำไมตัวอย่างของคุณสำหรับจุดที่ 2 จึงดีกว่าการใช้สิ่งที่ต้องการprint (spellout(100));และprint (translate("Good morning"));นี่ก็ดูเหมือนจะเป็นความคิดที่ดีเนื่องจากจะแยกการจัดรูปแบบและ i18n ออกจาก I / O
Schedler

3
เพราะสามารถแปลตาม langauage imbued into stream. ได้แก่ : french_output << translate("Good morning"); english_output << translate("Good morning") จะให้คุณ: "Bonjour Good morning"
Artyom

3
การแปลเป็นภาษาท้องถิ่นจะยากกว่ามากเมื่อคุณต้องทำ "<<" ข้อความ "<< ค่า" ในภาษาหนึ่ง แต่ "<< ค่า <<" ข้อความ "ในอีกภาษาหนึ่งเมื่อเทียบกับ printf
Martin Beckett

@ Martin Beckett ฉันรู้ว่าจะดูในห้องสมุด Boost.Locale เป็นสิ่งที่เกิดขึ้นว่าในกรณีดังกล่าวที่คุณทำและมันอาจจะแปลให้out << format("text {1}") % value ดังนั้นจึงทำงานได้ดี"{1} translated" ;-)
Artyom

15
สิ่งที่ "ทำได้" ไม่เกี่ยวข้องมากนัก คุณเป็นโปรแกรมเมอร์ทุกอย่างทำได้ด้วยความพยายามมากพอ แต่ IOStreams ทำให้เจ็บปวดอย่างมากที่จะบรรลุสิ่งที่ทำได้เกือบทั้งหมด และคุณมักจะได้รับประสิทธิภาพที่ไม่ดีสำหรับปัญหาของคุณ
jalf

2

(คำตอบนี้เป็นไปตามความคิดเห็นของฉัน)

ฉันคิดว่า IOStreams มีความซับซ้อนมากกว่าฟังก์ชันที่เทียบเท่ากัน เมื่อฉันเขียนใน C ++ ฉันยังคงใช้ส่วนหัว cstdio สำหรับ I / O "แบบเก่า" ซึ่งฉันพบว่าสามารถคาดเดาได้มากกว่ามาก หมายเหตุด้านข้าง (แม้ว่าจะไม่สำคัญนัก แต่ความแตกต่างของเวลาที่แน่นอนเป็นเรื่องเล็กน้อย) IOStreams ได้รับการพิสูจน์หลายครั้งว่าช้ากว่า CI / O


ฉันคิดว่าคุณหมายถึง "function" มากกว่า "functional" การเขียนโปรแกรมเชิงฟังก์ชันจะสร้างโค้ดที่ยิ่งดูแย่ลงไปอีก
Chris Becke

ขอขอบคุณที่ชี้ให้เห็นความผิดพลาดนั้น ฉันได้แก้ไขคำตอบเพื่อแสดงการแก้ไขแล้ว
Delan Azabani

5
iOStreams จะต้องช้ากว่า stdio แบบคลาสสิกอย่างแน่นอน ถ้าฉันได้รับมอบหมายให้ออกแบบเฟรมเวิร์กสตรีม I / O ที่ขยายได้และใช้งานง่ายฉันอาจจะตัดสินเรื่องความเร็วเป็นอันดับสองเนื่องจากปัญหาคอขวดที่แท้จริงน่าจะเป็นความเร็ว I / O ของไฟล์หรือแบนด์วิธการรับส่งข้อมูลเครือข่าย
stakx - ไม่ร่วมให้ข้อมูลใน

1
ฉันยอมรับว่าสำหรับ I / O หรือเครือข่ายความเร็วในการคำนวณไม่สำคัญมากนัก แต่จำไว้ว่า C ++ สำหรับการแปลงเป็นตัวเลข / sstringstreamสตริงใช้ ฉันคิดว่าความเร็วมีความสำคัญแม้ว่าจะเป็นรอง
Matthieu M.

1
ไฟล์ @stakx I / O และคอขวดของเครือข่ายเป็นฟังก์ชันของค่าใช้จ่าย 'ต่อไบต์' ซึ่งค่อนข้างน้อยและลดลงอย่างมากจากการปรับปรุงเทคโนโลยี นอกจากนี้เมื่อพิจารณาจาก DMA ค่าใช้จ่ายเหล่านี้จะไม่ใช้เวลา CPU ไปจากเธรดอื่น ๆ ในเครื่องเดียวกัน ดังนั้นหากคุณกำลังจัดรูปแบบผลลัพธ์ต้นทุนในการดำเนินการอย่างมีประสิทธิภาพเทียบกับไม่ได้อาจมีนัยสำคัญได้อย่างง่ายดาย (อย่างน้อยก็ไม่ถูกบดบังด้วยดิสก์หรือเครือข่ายมีแนวโน้มที่จะถูกบดบังด้วยการประมวลผลอื่น ๆ ในแอป)
greggo

2

ฉันมักจะพบกับความประหลาดใจเมื่อใช้ IOStream

ไลบรารีดูเหมือนจะเน้นข้อความและไม่เน้นไบนารี นั่นอาจเป็นเรื่องแปลกใจครั้งแรก: การใช้แฟล็กไบนารีในสตรีมไฟล์ไม่เพียงพอที่จะรับพฤติกรรมไบนารี ผู้ใช้ Charles Salvia ข้างต้นสังเกตเห็นได้อย่างถูกต้อง: IOStreams ผสมลักษณะการจัดรูปแบบ (ที่คุณต้องการผลลัพธ์ที่สวยงามเช่นตัวเลขที่ จำกัด สำหรับการลอยตัว) กับลักษณะการทำให้เป็นอนุกรม (ซึ่งคุณไม่ต้องการให้ข้อมูลสูญหาย) อาจจะเป็นการดีที่จะแยกแง่มุมเหล่านี้ Boost.Serialization ทำครึ่งนี้ คุณมีฟังก์ชันซีเรียลไลซ์ที่กำหนดเส้นทางไปยังตัวแทรกและตัวแยกหากคุณต้องการ คุณมีความตึงเครียดระหว่างทั้งสองด้านแล้ว

ฟังก์ชั่นจำนวนมากยังทำให้ความหมายสับสน (เช่น get, getline, ไม่สนใจและอ่านบางตัวแยกตัวคั่นบางตัวก็ไม่ได้ตั้งค่า eof ด้วย) นอกจากนี้ในบางรายกล่าวถึงชื่อฟังก์ชันแปลก ๆ เมื่อใช้สตรีม (เช่น xsputn, uflow, underflow) สิ่งต่างๆเลวร้ายยิ่งขึ้นเมื่อใช้ตัวแปร wchar_t wifstream ทำการแปลเป็นหลายไบต์ในขณะที่ wstringstream ไม่ทำ ไบนารี I / O ไม่ทำงานนอกกรอบด้วย wchar_t: คุณมีการเขียนทับ codecvt

c ที่บัฟเฟอร์ I / O (เช่น FILE) จะไม่ทรงพลังเท่ากับ C ++ คู่กัน แต่มีความโปร่งใสมากกว่าและมีพฤติกรรมที่ตอบโต้ได้ง่ายกว่ามาก

ทุกครั้งที่ฉันสะดุดกับ IOStream ฉันจะดึงดูดมันเหมือนแมลงเม่าที่ลุกเป็นไฟ อาจจะเป็นเรื่องดีถ้าผู้ชายฉลาด ๆ สักคนจะดูสถาปัตยกรรมโดยรวมได้ดี


1

ฉันไม่สามารถช่วยตอบคำถามส่วนแรกได้ (ใครเป็นคนทำ?) แต่ก็ตอบในกระทู้อื่น.

สำหรับส่วนที่สองของคำถาม (ออกแบบมาอย่างดี?) คำตอบของฉันคือ "ไม่!" ดังก้อง นี่คือตัวอย่างเล็ก ๆ น้อย ๆ ที่ทำให้ฉันสั่นหัวด้วยความไม่เชื่อตั้งแต่ปี:

#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;
}

โค้ดด้านบนก่อให้เกิดเรื่องไร้สาระเนื่องจากการออกแบบ iostream ด้วยเหตุผลบางประการที่อยู่นอกเหนือความเข้าใจของฉันพวกเขาถือว่า uint8_t ไบต์เป็นอักขระในขณะที่ประเภทอินทิกรัลที่ใหญ่กว่าจะถือว่าเป็นตัวเลข การออกแบบที่ไม่ดีของ Qed

นอกจากนี้ยังไม่มีวิธีที่ฉันคิดว่าจะแก้ไขปัญหานี้ได้ ประเภทอาจเป็น float หรือ double แทน ... ดังนั้นการโยนไปที่ 'int' เพื่อทำให้ iostream โง่เข้าใจว่าตัวเลขที่ไม่ใช่ตัวอักษรเป็นหัวข้อจะไม่ช่วย

หลังจากได้รับการโหวตลงคะแนนสำหรับการตอบกลับของฉันอาจจะมีคำอธิบายอีกสองสามคำ ... การออกแบบ IOStream มีข้อบกพร่องเนื่องจากไม่ได้ให้วิธีการที่โปรแกรมเมอร์ระบุวิธีการปฏิบัติต่อรายการ การใช้งาน IOStream จะทำการตัดสินใจโดยพลการ (เช่นปฏิบัติกับ uint8_t เป็นอักขระไม่ใช่จำนวนไบต์) นี่เป็นข้อบกพร่องของการออกแบบ IOStream เนื่องจากพวกเขาพยายามที่จะบรรลุสิ่งที่ไม่สามารถทำได้

C ++ ไม่อนุญาตให้จำแนกประเภท - ภาษาไม่มีสิ่งอำนวยความสะดวก ไม่มีสิ่งที่เรียกว่า is_number_type () หรือ is_character_type () IOStream สามารถใช้เพื่อทำการเลือกอัตโนมัติที่สมเหตุสมผลได้ การเพิกเฉยต่อสิ่งนั้นและพยายามหลีกหนีโดยการเดาว่าเป็นข้อบกพร่องของการออกแบบห้องสมุด

ยอมรับว่า printf () จะล้มเหลวในการทำงานในการใช้งาน "ShowVector ()" ทั่วไป แต่นั่นไม่ใช่ข้อแก้ตัวสำหรับพฤติกรรม iostream แต่มีความเป็นไปได้มากว่าในกรณี printf () ShowVector () จะถูกกำหนดเช่นนี้:

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

3
คำตำหนิไม่ได้อยู่กับ iostream ตรวจสอบว่าคุณuint8_tเป็นtypedefสำหรับอะไร เป็นถ่านจริงหรือ? ถ้าอย่างนั้นอย่าโทษ iostreams ที่ปฏิบัติเหมือนถ่าน
Martin Ba

และถ้าคุณต้องการให้แน่ใจว่าคุณได้รับตัวเลขในรหัสทั่วไปคุณสามารถใช้num_putfacetแทนตัวดำเนินการแทรกสตรีมได้
Martin Ba

@Martin Ba คุณพูดถูก - มาตรฐาน c / c ++ เปิดไว้ว่า "short unsigned int" มีกี่ไบต์ "ถ่านที่ไม่ได้ลงนาม" เป็นลักษณะเฉพาะของภาษา ถ้าคุณต้องการไบต์จริงๆคุณต้องใช้ถ่านที่ไม่ได้ลงชื่อ C ++ ยังไม่อนุญาตให้กำหนดข้อ จำกัด เกี่ยวกับอาร์กิวเมนต์ของเทมเพลตเช่น "เฉพาะตัวเลข" ดังนั้นหากฉันเปลี่ยนการนำ ShowVector ไปใช้กับโซลูชัน num_put ที่คุณเสนอ ShowVector จะไม่สามารถแสดงเวกเตอร์ของสตริงได้อีกต่อไปใช่ไหม ;)
BitTickler

1
@Martin Bla: cppreference ระบุว่า int8_t เป็นประเภทจำนวนเต็มที่ลงนามโดยมีความกว้าง 8 บิตฉันเห็นด้วยกับผู้เขียนว่าเป็นเรื่องแปลกที่คุณจะได้รับขยะแม้ว่าจะสามารถอธิบายได้ทางเทคนิคโดย typedef และประเภท char ที่มากเกินไปใน iostream . มันสามารถแก้ไขได้โดยการมี __int8 ชนิดจริงแทน typedef
gast128

โอ้มันค่อนข้างง่ายที่จะแก้ไข: // แก้ไขสำหรับ std :: ostream ซึ่งขาดการสนับสนุนสำหรับประเภทที่ไม่ได้ลงนาม / ลงนาม / ถ่าน // และพิมพ์จำนวนเต็ม 8 บิตเหมือนเป็นอักขระ เนมสเปซ ostream_fixes {อินไลน์ std :: ostream & operator << (std :: ostream & os, unsigned char i) {return os << static_cast <unsigned int> (i); } อินไลน์ std :: ostream & operator << (std :: ostream & os, signed char i) {return os << static_cast <signed int> (i); }} // namespace ostream_fixes
mcv

1

C ++ iostreams มีข้อบกพร่องมากมายดังที่ระบุไว้ในคำตอบอื่น ๆ แต่ฉันต้องการทราบบางอย่างในการป้องกัน

C ++ แทบจะไม่ซ้ำกันระหว่างภาษาที่ใช้งานอย่างจริงจังซึ่งทำให้อินพุตและเอาต์พุตตัวแปรตรงไปตรงมาสำหรับผู้เริ่มต้น ในภาษาอื่นการป้อนข้อมูลของผู้ใช้มีแนวโน้มที่จะเกี่ยวข้องกับการบังคับประเภทหรือการจัดรูปแบบสตริงในขณะที่ C ++ ทำให้คอมไพเลอร์ทำงานทั้งหมด เช่นเดียวกันกับเอาต์พุตเป็นส่วนใหญ่แม้ว่า C ++ จะไม่ซ้ำกันในเรื่องนี้ ถึงกระนั้นคุณสามารถจัดรูปแบบ I / O ได้ค่อนข้างดีใน C ++ โดยไม่ต้องเข้าใจคลาสและแนวคิดเชิงวัตถุซึ่งมีประโยชน์ในการสอนและไม่ต้องเข้าใจไวยากรณ์รูปแบบ อีกครั้งหากคุณกำลังสอนผู้เริ่มต้นนั่นเป็นข้อดีอย่างมาก

ความเรียบง่ายสำหรับผู้เริ่มต้นนี้มาพร้อมกับราคาซึ่งอาจทำให้ปวดหัวกับการจัดการกับ I / O ในสถานการณ์ที่ซับซ้อนมากขึ้น แต่หวังว่าเมื่อถึงจุดนั้นโปรแกรมเมอร์ได้เรียนรู้มากพอที่จะรับมือกับพวกเขาได้หรืออย่างน้อยก็อายุมากพอ ดื่ม.

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.