ใน C ++ ฉันจะจ่ายเงินสำหรับสิ่งที่ฉันไม่กินหรือไม่


170

ลองพิจารณาตัวอย่าง hello world ต่อไปนี้ใน C และ C ++:

main.c

#include <stdio.h>

int main()
{
    printf("Hello world\n");
    return 0;
}

main.cpp

#include <iostream>

int main()
{
    std::cout<<"Hello world"<<std::endl;
    return 0;
}

เมื่อฉันรวบรวมใน godbolt เพื่อประกอบขนาดของรหัส C เพียง 9 บรรทัด ( gcc -O3):

.LC0:
        .string "Hello world"
main:
        sub     rsp, 8
        mov     edi, OFFSET FLAT:.LC0
        call    puts
        xor     eax, eax
        add     rsp, 8
        ret

แต่ขนาดของรหัส C ++ คือ 22 บรรทัด ( g++ -O3):

.LC0:
        .string "Hello world"
main:
        sub     rsp, 8
        mov     edx, 11
        mov     esi, OFFSET FLAT:.LC0
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
        xor     eax, eax
        add     rsp, 8
        ret
_GLOBAL__sub_I_main:
        sub     rsp, 8
        mov     edi, OFFSET FLAT:_ZStL8__ioinit
        call    std::ios_base::Init::Init() [complete object constructor]
        mov     edx, OFFSET FLAT:__dso_handle
        mov     esi, OFFSET FLAT:_ZStL8__ioinit
        mov     edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
        add     rsp, 8
        jmp     __cxa_atexit

... ซึ่งใหญ่กว่ามาก

มันมีชื่อเสียงว่าใน C ++ คุณจ่ายสำหรับสิ่งที่คุณกิน ดังนั้นในกรณีนี้ฉันต้องจ่ายเงินเพื่ออะไร


3
ความคิดเห็นไม่ได้มีไว้สำหรับการอภิปรายเพิ่มเติม การสนทนานี้ได้รับการย้ายไปแชท
Samuel Liew


26
ไม่เคยได้ยินคำศัพท์ที่eatเกี่ยวข้องกับ C ++ ฉันเชื่อว่าคุณหมายถึง: "คุณจ่ายเฉพาะสิ่งที่คุณใช้ "
Giacomo Alzetta

7
@GiacomoAlzetta, ... มันเป็นภาษาพูดที่ใช้แนวคิดของบุฟเฟ่ต์แบบทานได้ไม่อั้น การใช้คำที่แม่นยำยิ่งขึ้นนั้นเป็นที่ต้องการของผู้ชมทั่วโลก แต่ในฐานะผู้พูดภาษาอังกฤษแบบอเมริกันพื้นเมืองชื่อเรื่องนี้สมเหตุสมผลสำหรับฉัน
ชาร์ลส์ดัฟฟี่

5
@ trolley813 การรั่วไหลของหน่วยความจำไม่มีส่วนเกี่ยวข้องกับคำพูดและคำถาม OP จุดของ "คุณจ่ายเฉพาะสิ่งที่คุณใช้" / "คุณไม่จ่ายเงินสำหรับสิ่งที่คุณไม่ได้ใช้" กล่าวคือไม่มีการตีประสิทธิภาพหากคุณไม่ได้ใช้คุณสมบัติ / สิ่งที่เป็นนามธรรม หน่วยความจำรั่วไม่มีส่วนเกี่ยวข้องกับสิ่งนี้เลยและสิ่งนี้แสดงให้เห็นว่าคำeatนั้นคลุมเครือมากขึ้นและควรหลีกเลี่ยง
Giacomo Alzetta

คำตอบ:


60

สิ่งที่คุณจ่ายสำหรับการโทรห้องสมุดใหญ่ (ไม่หนักเท่าการพิมพ์ลงในคอนโซล) คุณเริ่มต้นostreamวัตถุ มีที่เก็บข้อมูลที่ซ่อนอยู่บางส่วน จากนั้นคุณโทรซึ่งไม่ได้เป็นคำพ้องความหมายสำหรับstd::endl ห้องสมุดช่วยให้คุณสามารถปรับการตั้งค่าจำนวนมากและการวางภาระในการประมวลผลมากกว่าโปรแกรมเมอร์ นี่คือสิ่งที่คุณจ่าย\niostream

ลองตรวจสอบรหัส:

.LC0:
        .string "Hello world"
main:

การเริ่มต้นวัตถุ ostream + cout

    sub     rsp, 8
    mov     edx, 11
    mov     esi, OFFSET FLAT:.LC0
    mov     edi, OFFSET FLAT:_ZSt4cout
    call    std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)

โทรcoutอีกครั้งเพื่อพิมพ์บรรทัดใหม่และล้างข้อมูล

    mov     edi, OFFSET FLAT:_ZSt4cout
    call    std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
    xor     eax, eax
    add     rsp, 8
    ret

การเริ่มต้นการจัดเก็บแบบคงที่:

_GLOBAL__sub_I_main:
        sub     rsp, 8
        mov     edi, OFFSET FLAT:_ZStL8__ioinit
        call    std::ios_base::Init::Init() [complete object constructor]
        mov     edx, OFFSET FLAT:__dso_handle
        mov     esi, OFFSET FLAT:_ZStL8__ioinit
        mov     edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
        add     rsp, 8
        jmp     __cxa_atexit

นอกจากนี้ยังเป็นสิ่งสำคัญที่จะต้องแยกความแตกต่างระหว่างภาษาและห้องสมุด

BTW นี่เป็นเพียงส่วนหนึ่งของเรื่องราว คุณไม่ทราบว่าสิ่งที่เขียนในฟังก์ชั่นที่คุณโทร


5
ในฐานะที่เป็นบันทึกเพิ่มเติมการทดสอบอย่างละเอียดจะแสดงว่าการเตรียมโปรแกรม C ++ ด้วย "ios_base :: sync_with_stdio (false);" และ "cin.tie (NULL);" จะทำให้ cout เร็วกว่า printf (Printf มีรูปแบบสตริงเหนือหัว) ครั้งแรกที่กำจัดค่าใช้จ่ายจากการทำให้แน่ใจว่าการcout; printf; coutเขียนตามลำดับ (เนื่องจากพวกเขามีบัฟเฟอร์ของตัวเอง) ที่สองจะ desync coutและcinทำให้cout; cinอาจขอข้อมูลจากผู้ใช้ก่อน การล้างจะบังคับให้ซิงค์เฉพาะเมื่อคุณต้องการ
Nicholas Pipitone

สวัสดีนิโคลัสขอบคุณมากสำหรับการเพิ่มบันทึกที่มีประโยชน์เหล่านี้
Arash

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

211

ดังนั้นในกรณีนี้ฉันต้องจ่ายเงินเพื่ออะไร

std::cout มีประสิทธิภาพและซับซ้อนกว่า printfมีประสิทธิภาพมากขึ้นและซับซ้อนกว่าสนับสนุนสิ่งต่าง ๆ เช่นตำแหน่งที่ตั้งการตั้งค่าสถานะการจัดรูปแบบ stateful และอื่น ๆ

หากคุณไม่จำเป็นต้องมีผู้ใช้std::printfหรือstd::puts- <cstdio>พวกเขาในกำลังที่มีอยู่


มันมีชื่อเสียงว่าใน C ++ คุณจ่ายสำหรับสิ่งที่คุณกิน

ฉันต้องการทำให้ชัดเจนว่า C ++ ! = The C ++ Standard Library ไลบรารี่มาตรฐานควรจะเป็นจุดประสงค์ทั่วไปและ "เร็วพอ" แต่มักจะช้ากว่าการใช้สิ่งที่คุณต้องการเป็นพิเศษ

ในทางกลับกันภาษา C ++ มุ่งมั่นที่จะทำให้สามารถเขียนโค้ดโดยไม่ต้องจ่ายค่าใช้จ่ายพิเศษเพิ่มเติมที่ไม่จำเป็น (เช่นการเลือกใช้virtual, ไม่มีการรวบรวมขยะ)


4
+1 สำหรับการพูดว่าไลบรารีมาตรฐานควรจะเป็นจุดประสงค์ทั่วไปและ "เร็วพอ" แต่บ่อยครั้งจะช้ากว่าการใช้สิ่งที่คุณต้องการเป็นพิเศษ ดูเหมือนว่าหลายคนจะใช้ส่วนประกอบของ STL โดยไม่คำนึงถึงผลกระทบด้านประสิทธิภาพเทียบกับการม้วนของคุณเอง
Craig Estey

7
@Craig OTOH หลาย ๆ ส่วนของไลบรารีมาตรฐานมักจะเร็วกว่าและถูกต้องมากกว่าสิ่งที่ปกติสามารถผลิตได้
ปีเตอร์ - Reinstate Monica

2
@ PeterA.Schneider OTOH เมื่อรุ่น STL ช้ากว่า 20x-30x การกลิ้งของคุณเองเป็นสิ่งที่ดี ดูคำตอบของฉันที่นี่: codereview.stackexchange.com/questions/191747/…ในที่นี้คนอื่น ๆ ก็แนะนำ [อย่างน้อยก็บางส่วน] ม้วนตัวคุณเอง
Craig Estey

1
@CraigEstey เวกเตอร์คือ (นอกเหนือจากการจัดสรรแบบไดนามิกเริ่มต้นซึ่งอาจมีความสำคัญขึ้นอยู่กับจำนวนงานที่จะทำในที่สุดด้วยอินสแตนซ์ที่กำหนด) ซึ่งไม่ได้มีประสิทธิภาพน้อยกว่าอาร์เรย์ C มันถูกออกแบบมาไม่ให้เป็น ต้องใช้ความระมัดระวังไม่ให้คัดลอกไปรอบ ๆ จองพื้นที่เพียงพอในตอนแรก ฯลฯ แต่สิ่งที่ต้องทำด้วยอาร์เรย์เช่นกันและปลอดภัยน้อยกว่า ด้วยความเคารพต่อตัวอย่างที่เชื่อมโยงของคุณ: ใช่เวกเตอร์ของเวกเตอร์จะ (ยกเว้นปรับให้เหมาะสม) จะมีทิศทางพิเศษเมื่อเทียบกับอาเรย์ 2 มิติ แต่ฉันคิดว่าประสิทธิภาพ 20x ไม่ได้ถูกรูท แต่ในอัลกอริทึม
ปีเตอร์ - Reinstate Monica

174

คุณไม่ได้เปรียบเทียบ C และ C ++ คุณกำลังเปรียบเทียบprintfและstd::coutซึ่งมีความสามารถในสิ่งต่าง ๆ (ตำแหน่งที่ตั้งการจัดรูปแบบ stateful ฯลฯ )

ลองใช้รหัสต่อไปนี้เพื่อการเปรียบเทียบ Godbolt สร้างชุดเดียวกันสำหรับไฟล์ทั้งสอง (ทดสอบด้วย gcc 8.2, -O3)

main.c:

#include <stdio.h>

int main()
{
    int arr[6] = {1, 2, 3, 4, 5, 6};
    for (int i = 0; i < 6; ++i)
    {
        printf("%d\n", arr[i]);
    }
    return 0;
}

main.cpp:

#include <array>
#include <cstdio>

int main()
{
    std::array<int, 6> arr {1, 2, 3, 4, 5, 6};
    for (auto x : arr)
    {
        std::printf("%d\n", x);
    }
}


ไชโยสำหรับการแสดงรหัสที่เทียบเท่าและอธิบายเหตุผล
HackSlash

134

รายชื่อของคุณกำลังเปรียบเทียบแอปเปิ้ลกับส้ม แต่ไม่ใช่สำหรับเหตุผลที่บอกเป็นนัย ๆ ในคำตอบอื่น ๆ

ลองตรวจสอบว่ารหัสของคุณทำอะไร:

ค:

  • พิมพ์สตริงเดียว "Hello world\n"

C ++:

  • สตรีมสตริง"Hello world"ลงในstd::cout
  • สตรีมstd::endlหุ่นยนต์เข้าstd::cout

เห็นได้ชัดว่ารหัส C ++ ของคุณทำงานได้มากเป็นสองเท่า สำหรับการเปรียบเทียบที่เป็นธรรมเราควรรวมสิ่งนี้:

#include <iostream>

int main()
{
    std::cout<<"Hello world\n";
    return 0;
}

…และทันใดนั้นรหัสชุดประกอบของคุณก็mainดูคล้ายกับของ C:

main:
        sub     rsp, 8
        mov     esi, OFFSET FLAT:.LC0
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
        xor     eax, eax
        add     rsp, 8
        ret

ในความเป็นจริงเราสามารถเปรียบเทียบโค้ด C และ C ++ ต่อบรรทัดและมีความแตกต่างน้อยมาก :

sub     rsp, 8                      sub     rsp, 8
mov     edi, OFFSET FLAT:.LC0   |   mov     esi, OFFSET FLAT:.LC0
                                >   mov     edi, OFFSET FLAT:_ZSt4cout
call    puts                    |   call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
xor     eax, eax                    xor     eax, eax
add     rsp, 8                      add     rsp, 8
ret                                 ret

ความแตกต่างที่แท้จริงเพียงอย่างเดียวคือใน C ++ เราเรียกoperator <<ด้วยอาร์กิวเมนต์สองตัว ( std::coutและสตริง) เราสามารถลบความแตกต่างเล็กน้อยนั้นได้โดยใช้ C eqivalent ที่ใกล้กว่า: fprintfซึ่งมีอาร์กิวเมนต์แรกที่ระบุสตรีม

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

แต่ดังที่อธิบายไว้ในคำตอบอื่น ๆ ความแตกต่างที่เกี่ยวข้องระหว่างสองโปรแกรมนี้ไม่สามารถพบได้ในแอสเซมบลีเอาท์พุทของmainฟังก์ชั่นเนื่องจากการยกของหนักทั้งหมดเกิดขึ้นหลังฉาก


21
อนึ่งรันไทม์ C ยังจะต้องมีการตั้งค่าและนี้เกิดขึ้นในฟังก์ชั่นที่เรียกว่า_startแต่รหัสที่เป็นส่วนหนึ่งของห้องสมุดรันไทม์ C สิ่งนี้จะเกิดขึ้นกับทั้ง C และ C ++
Konrad Rudolph

2
@Dupuplicator: ตามจริงแล้วโดยปกติแล้วไลบรารี่ iostream จะไม่ทำการบัฟเฟอร์ใด ๆstd::coutและส่งผ่าน I / O ไปยังการใช้ stdio แทน (ซึ่งใช้กลไกการบัฟเฟอร์ของตัวเอง) โดยเฉพาะอย่างยิ่งเมื่อเชื่อมต่อกับ (สิ่งที่เป็นที่รู้จักกัน) std::coutสถานีโต้ตอบแล้วโดยเริ่มต้นที่คุณจะไม่เคยเห็นเอาท์พุทบัฟเฟอร์อย่างเต็มที่เมื่อเขียนถึง คุณจะต้องปิดการใช้งานอย่างชัดเจนการประสานกับ stdio ถ้าคุณต้องการห้องสมุด iostream std::coutที่จะใช้กลไกบัฟเฟอร์ของตัวเองสำหรับ

6
@ KonradRudolph: ที่จริงแล้วprintfไม่จำเป็นต้องล้างกระแสข้อมูลที่นี่ ในความเป็นจริงในกรณีการใช้งานทั่วไป (ผลลัพธ์ที่เปลี่ยนเส้นทางไปยังไฟล์) คุณมักจะพบว่าprintfคำสั่งนั้นไม่ได้ล้างออก เฉพาะเมื่อเอาต์พุตถูกบัฟเฟอร์บรรทัดหรือไม่บัฟเฟอร์จะprintfทริกเกอร์ฟลัช

2
@PeterCordes: ใช่คุณไม่สามารถบล็อกด้วยบัฟเฟอร์เอาต์พุตที่ไม่ได้ฟลัช แต่คุณสามารถพบกับความประหลาดใจที่โปรแกรมได้รับอินพุตของคุณแล้วเดินต่อไปโดยไม่แสดงผลลัพธ์ที่คาดหวัง ฉันรู้สิ่งนี้เพราะฉันมีโอกาสที่จะดีบั๊ก "วิธีใช้โปรแกรมของฉันถูกระงับระหว่างการป้อนข้อมูล แต่ฉันไม่สามารถหาสาเหตุได้!" ที่ให้นักพัฒนาคนอื่นเหมาะกับสองสามวัน

2
@PeterCordes: อาร์กิวเมนต์ที่ฉันทำคือ "เขียนสิ่งที่คุณหมายถึง" - การขึ้นบรรทัดใหม่มีความเหมาะสมเมื่อคุณหมายถึงว่าผลลัพธ์จะพร้อมใช้งานในที่สุดและ endl จะเหมาะสมเมื่อคุณหมายถึงผลลัพธ์ที่จะพร้อมใช้งานได้ทันที

53

มันมีชื่อเสียงว่าใน C ++ คุณจ่ายสำหรับสิ่งที่คุณกิน ดังนั้นในกรณีนี้ฉันต้องจ่ายเงินเพื่ออะไร

ง่ายมาก คุณจ่ายstd::coutไป "คุณจ่ายเฉพาะสิ่งที่คุณกิน" ไม่ได้แปลว่า "คุณจะได้ราคาที่ดีที่สุดเสมอ" แน่นอนว่าprintfถูกกว่า หนึ่งสามารถยืนยันว่าstd::coutปลอดภัยและหลากหลายมากขึ้นดังนั้นค่าใช้จ่ายมากขึ้นเป็นธรรม (ค่าใช้จ่ายมากขึ้น แต่ให้ค่ามากกว่า) แต่ที่พลาดจุด คุณไม่ได้ใช้printfคุณสามารถใช้เพื่อให้คุณจ่ายสำหรับการใช้std::cout คุณไม่ต้องจ่ายสำหรับการใช้std::coutprintf

ตัวอย่างที่ดีคือฟังก์ชั่นเสมือนจริง ฟังก์ชั่นเสมือนจริงมีค่าใช้จ่ายด้านเวลาและความต้องการเนื้อที่ - แต่ถ้าคุณใช้งานจริง หากคุณไม่ใช้ฟังก์ชันเสมือนคุณไม่ต้องจ่ายอะไรเลย

ข้อสังเกตเล็กน้อย

  1. แม้ว่ารหัส C ++ จะประเมินคำแนะนำการประกอบเพิ่มเติม แต่ก็ยังมีคำสั่งไม่มากนักและค่าใช้จ่ายในการปฏิบัติงานก็ยังคงถูกแคระโดยการปฏิบัติการ I / O จริง

  2. จริงๆแล้วบางครั้งมันดีกว่า "ใน C ++ ที่คุณจ่ายไปสำหรับสิ่งที่คุณกิน" ตัวอย่างเช่นคอมไพเลอร์สามารถอนุมานได้ว่าการเรียกใช้ฟังก์ชันเสมือนไม่จำเป็นในบางสถานการณ์และแปลงเป็นการโทรที่ไม่ใช่แบบเสมือน นั่นหมายความว่าคุณอาจได้รับฟังก์ชั่นเสมือนจริงสำหรับฟรี มันยอดเยี่ยมใช่ไหม


6
คุณไม่ได้รับฟังก์ชั่นเสมือนจริงฟรี คุณยังต้องจ่ายค่าใช้จ่ายในการเขียนครั้งแรกจากนั้นทำการดีบั๊กการแปลงคอมไพเลอร์ของรหัสของคุณเมื่อมันไม่ตรงกับความคิดของคุณเกี่ยวกับสิ่งที่ควรจะทำ
alephzero

2
@alephzero ฉันไม่แน่ใจว่ามีความเกี่ยวข้องเป็นพิเศษในการเปรียบเทียบต้นทุนการพัฒนากับต้นทุนประสิทธิภาพ

โอกาสที่ดีเช่นนี้สำหรับปุนก็สูญเปล่า ... คุณอาจใช้คำว่า 'แคลอรี่' แทน 'ราคา' จากที่คุณสามารถบอกได้ว่า C ++ นั้นอ้วนกว่า C หรืออย่างน้อย ... รหัสเฉพาะที่เป็นปัญหา อนิจจา. @Bilkokuya มันอาจจะไม่เกี่ยวข้องในทุกกรณี แต่มันเป็นสิ่งที่ไม่ควรมองข้าม ดังนั้นจึงมีความเกี่ยวข้องในภาพรวม
Pryftan

46

"รายการแอสเซมบลีสำหรับ printf" ไม่ได้สำหรับ printf แต่สำหรับใส่ (ชนิดของการเพิ่มประสิทธิภาพคอมไพเลอร์?); printf ซับซ้อนกว่า prety มากกว่ามาก ... อย่าลืม!


13
นี่คือคำตอบที่ดีที่สุดเนื่องจากคนอื่น ๆ ทั้งหมดได้ถูกแขวนอยู่บนปลาเฮอริ่งแดงเกี่ยวกับstd::coutภายในของซึ่งไม่ปรากฏในรายการแอสเซมบลี
Konrad Rudolph

12
รายการแอสเซมบลีใช้สำหรับการโทรไป putsซึ่งมีลักษณะเหมือนกับการโทรไปprintfหากคุณส่งสตริงรูปแบบเดียวและมีส่วนเกินพิเศษเป็นศูนย์ (ยกเว้นจะมีxor %eax,%eaxเพราะเราผ่านศูนย์ FP args ในการลงทะเบียนไปยังฟังก์ชัน Variadic) ทั้งสองสิ่งนี้ไม่ได้ใช้งานเพียงแค่ส่งตัวชี้ไปยังสตริงไปยังฟังก์ชันไลบรารี แต่ใช่การปรับprintfให้เหมาะสมputsคือสิ่งที่ gcc ทำสำหรับรูปแบบที่มี"%s"หรือเมื่อไม่มีการแปลงและสตริงจะลงท้ายด้วยการขึ้นบรรทัดใหม่
Peter Cordes

45

ฉันเห็นคำตอบที่ถูกต้องที่นี่ แต่ฉันจะได้รับรายละเอียดเพิ่มเติมเล็กน้อย

ข้ามไปที่บทสรุปด้านล่างเพื่อดูคำตอบสำหรับคำถามหลักของคุณหากคุณไม่ต้องการอ่านเนื้อหาทั้งหมด


สิ่งที่เป็นนามธรรม

ดังนั้นในกรณีนี้ฉันต้องจ่ายเงินเพื่ออะไร

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

  1. การสร้างวัตถุโดยทั่วไปการจัดสรรหน่วยความจำสำหรับวัตถุเองและข้อมูล
  2. การเริ่มต้นวัตถุ (มักจะผ่านinit()วิธีการบางอย่าง) โดยปกติการจัดสรรหน่วยความจำจะเกิดขึ้นภายใต้ประทุนเป็นสิ่งแรกในขั้นตอนนี้
  3. การทำลายวัตถุ (ไม่เสมอไป)

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

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

เกิดอะไรขึ้นใน C ++

นี่มันพัง:

  1. std::ios_baseระดับเริ่มต้นซึ่งเป็นชั้นฐานสำหรับทุกอย่าง I / O ที่เกี่ยวข้อง
  2. std::coutวัตถุจะเริ่มต้นได้
  3. สตริงของคุณถูกโหลดและส่งผ่านไปยังstd::__ostream_insertซึ่ง (ตามที่คุณคิดโดยใช้ชื่อแล้ว) เป็นวิธีการstd::cout(โดยทั่วไปคือ<<โอเปอเรเตอร์) ซึ่งเพิ่มสตริงลงในสตรีม
  4. cout::endlstd::__ostream_insertนอกจากนี้ยังมีการส่งผ่านไปยัง
  5. __std_dso_handleถูกส่งไปยัง__cxa_atexitซึ่งเป็นฟังก์ชั่นระดับโลกที่รับผิดชอบในการ "ล้าง" ก่อนออกจากโปรแกรม __std_dso_handleตัวมันเองถูกเรียกใช้โดยฟังก์ชั่นนี้เพื่อจัดสรรคืนและทำลายวัตถุระดับโลกที่เหลืออยู่

ดังนั้นการใช้ C == ไม่จ่ายอะไรเลยเหรอ?

ในรหัส C มีขั้นตอนน้อยมากเกิดขึ้น:

  1. สตริงของคุณจะถูกโหลดและส่งผ่านไปยังputsการediลงทะเบียน
  2. puts ถูกเรียก

ไม่มีวัตถุใด ๆ จึงไม่จำเป็นต้องเริ่มต้น / ทำลายสิ่งใด

อย่างไรก็ตามเรื่องนี้ไม่ได้หมายความว่าคุณไม่ได้ "จ่ายเงิน" เพื่ออะไรใน C คุณยังคงจ่ายเงินเพื่อให้เป็นนามธรรมและการเริ่มต้นของไลบรารีมาตรฐาน C และการแก้ปัญหาแบบไดนามิกprintfฟังก์ชั่น (หรือที่จริงแล้วputsซึ่งปรับให้เหมาะสมโดยคอมไพเลอร์เนื่องจากคุณไม่ต้องการสตริงรูปแบบใด ๆ ) ยังคงเกิดขึ้นภายใต้ประทุน

หากคุณต้องเขียนโปรแกรมนี้ในแอสเซมบลีบริสุทธิ์มันจะมีลักษณะดังนี้:

jmp start

msg db "Hello world\n"

start:
    mov rdi, 1
    mov rsi, offset msg
    mov rdx, 11
    mov rax, 1          ; write
    syscall
    xor rdi, rdi
    mov rax, 60         ; exit
    syscall

ซึ่งโดยทั่วไปแล้วจะส่งผลให้เรียกใช้write syscallตามด้วยexitsyscall ตอนนี้จะเป็นขั้นต่ำเปล่าเพื่อทำสิ่งเดียวกัน


เพื่อสรุป

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

ตอบคำถามหลักของคุณ :

ฉันต้องจ่ายเงินสำหรับสิ่งที่ไม่ได้กินหรือไม่?

ในกรณีนี้โดยเฉพาะใช่ คุณไม่ได้ใช้ประโยชน์จากสิ่งที่ C ++ มีให้มากกว่า C แต่เป็นเพราะไม่มีอะไรในรหัสง่ายๆที่ C ++ สามารถช่วยคุณได้: มันง่ายมากที่คุณไม่ต้องการ C ++ เลย


โอ้และอีกหนึ่งสิ่ง!

ข้อดีของ C ++ อาจดูไม่ชัดในตอนแรกเนื่องจากคุณเขียนโปรแกรมที่ง่ายและเล็ก แต่ดูตัวอย่างที่ซับซ้อนขึ้นเล็กน้อยและดูความแตกต่าง (ทั้งสองโปรแกรมทำสิ่งเดียวกัน)

:

#include <stdio.h>
#include <stdlib.h>

int cmp(const void *a, const void *b) {
    return *(int*)a - *(int*)b;
}

int main(void) {
    int i, n, *arr;

    printf("How many integers do you want to input? ");
    scanf("%d", &n);

    arr = malloc(sizeof(int) * n);

    for (i = 0; i < n; i++) {
        printf("Index %d: ", i);
        scanf("%d", &arr[i]);
    }

    qsort(arr, n, sizeof(int), cmp)

    puts("Here are your numbers, ordered:");

    for (i = 0; i < n; i++)
        printf("%d\n", arr[i]);

    free(arr);

    return 0;
}

C ++ :

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main(void) {
    int n;

    cout << "How many integers do you want to input? ";
    cin >> n;

    vector<int> vec(n);

    for (int i = 0; i < vec.size(); i++) {
        cout << "Index " << i << ": ";
        cin >> vec[i];
    }

    sort(vec.begin(), vec.end());

    cout << "Here are your numbers:" << endl;

    for (int item : vec)
        cout << item << endl;

    return 0;
}

หวังว่าคุณจะเห็นชัดเจนว่าฉันหมายถึงที่นี่ นอกจากนี้ให้สังเกตว่าใน C คุณต้องจัดการหน่วยความจำในระดับที่ต่ำกว่าโดยใช้อย่างไรmallocและfreeคุณจะต้องระมัดระวังเกี่ยวกับการจัดทำดัชนีและขนาดมากขึ้นอย่างไรและคุณต้องมีความเจาะจงมากเพียงใดเมื่อรับอินพุตและการพิมพ์


27

มีความเข้าใจผิดบางประการที่จะเริ่มต้นด้วย อย่างแรกโปรแกรม C ++ ไม่ส่งผลให้มี 22 คำสั่งมันเหมือนกันมากกว่า 22,000 คำ (ฉันดึงหมายเลขนั้นออกจากหมวกของฉัน แต่มันประมาณไว้ใน ballpark) นอกจากนี้รหัส C ไม่ได้ส่งผลให้มี 9 คำแนะนำเช่นกัน นี่เป็นเพียงสิ่งที่คุณเห็น

สิ่งที่โค้ด C ทำคือหลังจากทำสิ่งต่าง ๆ มากมายที่คุณไม่เห็นมันเรียกใช้ฟังก์ชันจาก CRT (ซึ่งโดยปกติแล้วจะไม่จำเป็นต้องแสดงเป็น lib ที่ใช้ร่วมกัน) จากนั้นจะไม่ตรวจสอบค่าส่งคืนหรือการจัดการ ข้อผิดพลาดและ bail out ขึ้นอยู่กับการตั้งค่าคอมไพเลอร์และการเพิ่มประสิทธิภาพมันไม่ได้เรียกจริง ๆprintfแต่putsหรือบางสิ่งบางอย่างยิ่งดั้งเดิม
คุณสามารถเขียนโปรแกรมเดียวกันได้มากขึ้นหรือน้อยลง (ยกเว้นฟังก์ชั่น init บางตัวที่มองไม่เห็น) ใน C ++ เช่นกันถ้ามีเพียงคุณที่เรียกใช้ฟังก์ชันเดียวกันในลักษณะเดียวกัน หรือถ้าคุณต้องการให้ถูกต้องสุด ๆ ฟังก์ชั่นเดียวกันstd::นั้นจะขึ้นต้นด้วย

รหัส C ++ ที่สอดคล้องกันนั้นในความเป็นจริงไม่ใช่สิ่งเดียวกัน ในขณะที่ทั้งหมด<iostream>นั้นเป็นที่รู้จักกันดีว่าเป็นหมูอ้วนที่น่าเกลียดซึ่งเพิ่มค่าใช้จ่ายอันยิ่งใหญ่สำหรับโปรแกรมขนาดเล็ก (ในโปรแกรม "ของจริง" ที่คุณไม่ได้สังเกตเห็นมากนัก) การตีความที่ค่อนข้างยุติธรรมคือว่ามันแย่มาก จำนวนมากของสิ่งที่คุณไม่เห็นและที่เป็นเพียงแค่งาน รวมถึง แต่ไม่ จำกัด เพียงการจัดรูปแบบที่น่าอัศจรรย์ของสิ่งที่จับจดมากรวมถึงรูปแบบตัวเลขและตำแหน่งที่ตั้งและ whatnot และบัฟเฟอร์และการจัดการข้อผิดพลาดที่เหมาะสม จัดการข้อผิดพลาด? ใช่แล้วเดาว่าการส่งออกสตริงสามารถล้มเหลวได้จริงและไม่เหมือนกับโปรแกรม C โปรแกรม C ++ จะไม่ทำงานเพิกเฉยต่อสิ่งนี้อย่างเงียบ ๆ พิจารณาอะไรstd::ostreamทำภายใต้ประทุนและถ้าไม่มีใครรู้ว่ามันเบาจริง ๆ ไม่ชอบฉันใช้เพราะฉันเกลียดไวยากรณ์ของสตรีมด้วยความหลงใหล แต่ถึงกระนั้นมันก็ค่อนข้างน่ากลัวถ้าคุณพิจารณาสิ่งที่มันทำ

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

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


16
คำตอบที่ดีมากยกเว้นว่าการยืนยันนี้:“ แต่แน่นอนว่า C ++ โดยรวมนั้นไม่ได้มีประสิทธิภาพมากเท่าที่ C สามารถเป็น” ได้อย่างไม่ถูกต้อง C ++ สามารถเป็นที่มีประสิทธิภาพเป็น C และพอรหัสระดับสูงสามารถอื่น ๆ อีกมากมายที่มีประสิทธิภาพกว่าเทียบเท่ารหัส C ใช่ C ++ มีค่าโสหุ้ยเนื่องจากต้องจัดการกับข้อยกเว้น แต่ในคอมไพเลอร์สมัยใหม่ค่าใช้จ่ายนั้นน้อยมากเมื่อเทียบกับประสิทธิภาพที่เพิ่มขึ้นจาก abstractions ที่ไม่มีต้นทุนดีกว่า
Konrad Rudolph

หากฉันเข้าใจถูกต้องจะstd::coutโยนข้อยกเว้นด้วยหรือไม่
Saher

6
@ Saa: ใช่ไม่บางที std::coutคือ a std::basic_ostreamและสามารถโยนได้และสามารถสร้างข้อยกเว้นที่เกิดขึ้นได้หากกำหนดให้ทำเช่นนั้นหรือสามารถกลืนข้อยกเว้นได้ สิ่งนั้นคือสิ่งที่สามารถล้มเหลวและ C ++ เช่นเดียวกับ lib มาตรฐาน C ++ เป็นส่วนใหญ่สร้างขึ้นเพื่อให้ความล้มเหลวไม่ได้สังเกตได้ง่าย นี่คือความน่ารำคาญและเป็นพร (แต่ยิ่งกว่าความน่ารำคาญ) ในทางกลับกันเพียงแสดงให้คุณเห็นนิ้วกลาง คุณไม่ตรวจสอบรหัสส่งคืนคุณไม่มีทางรู้ว่าเกิดอะไรขึ้น
เดมอน

1
@ KonradRudolph: จริงนี่คือสิ่งที่ฉันพยายามที่จะชี้ให้เห็นว่า "ฉันไม่ค่อยพบ C ++ ที่ทำงานได้ดีขึ้นเพราะเหตุผลหนึ่งหรืออย่างอื่นดูเหมือนว่าจะให้ยืมเพื่อเพิ่มประสิทธิภาพที่ดีขึ้นอย่าถามฉันว่าทำไมโดยเฉพาะ" . ไม่ชัดเจนว่าทำไมในทันที แต่ไม่บ่อยนักที่จะปรับให้เหมาะสมที่สุด ด้วยเหตุผลใดก็ตาม คุณจะคิดว่ามันเป็นสิ่งเดียวกันกับเครื่องมือเพิ่มประสิทธิภาพ แต่ไม่ใช่
เดมอน

22

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

<iomanip>

ส่วนที่น่ารังเกียจที่สุดของสตรีม C ++ io api คือการมีอยู่ของไลบรารีส่วนหัวการจัดรูปแบบนี้ นอกเหนือจากการเป็นรัฐและน่าเกลียดและข้อผิดพลาดได้ง่ายมันคู่การจัดรูปแบบเพื่อสตรีม

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

// <cstdio>
std::printf( "%08x %.3lf\n", ival, fval );

// <ostream> & <iomanip>
std::ios old_fmt {nullptr};
old_fmt.copyfmt (std::cout);
std::cout << std::right << std::setfill('0') << std::setw(8) << std::hex << ival;
std::cout.copyfmt (old_fmt);
std::cout << " " << std::fixed << std::setprecision(3) << fval << "\n";
std::cout.copyfmt (old_fmt);

ผู้ประกอบการมากไป

<iostream> เป็นชายด์โปสเตอร์ของวิธีที่จะไม่ใช้ตัวดำเนินการโอเวอร์โหลด:

std::cout << 2 << 3 && 0 << 5;

ประสิทธิภาพ

std::coutprintf()มีหลายครั้งที่ช้าลง featuritis อาละวาดและการจัดส่งเสมือนจริงใช้ค่าโทร

ความปลอดภัยด้าย

ทั้งสอง<cstdio>และ<iostream>เป็นเธรดที่ปลอดภัยในทุกการเรียกใช้ฟังก์ชันคืออะตอมมิก แต่printf()ทำได้มากขึ้นต่อการโทร หากคุณเรียกใช้โปรแกรมต่อไปนี้พร้อมกับ<cstdio>ตัวเลือกคุณจะเห็นเพียงแถวfเดียว หากคุณใช้<iostream>กับเครื่องมัลติคอร์คุณอาจเห็นสิ่งอื่น

// g++ -Wall -Wextra -Wpedantic -pthread -std=c++17 cout.test.cpp

#define USE_STREAM 1
#define REPS 50
#define THREADS 10

#include <thread>
#include <vector>

#if USE_STREAM
    #include <iostream>
#else
    #include <cstdio>
#endif

void task()
{
    for ( int i = 0; i < REPS; ++i )
#if USE_STREAM
        std::cout << std::hex << 15 << std::dec;
#else
        std::printf ( "%x", 15);
#endif

}

int main()
{
    auto threads = std::vector<std::thread> {};
    for ( int i = 0; i < THREADS; ++i )
        threads.emplace_back(task);

    for ( auto & t : threads )
        t.join();

#if USE_STREAM
        std::cout << "\n<iostream>\n";
#else
        std::printf ( "\n<cstdio>\n" );
#endif
}

การโต้กลับไปที่ตัวอย่างนี้คือคนส่วนใหญ่ใช้วินัยในการที่จะไม่เขียนถึง file descriptor เดียวจากหลาย ๆ เธรด ทั้งในกรณีที่คุณจะต้องสังเกตที่<iostream>เป็นประโยชน์จะคว้าล็อคในทุกคนและทุก<< >>ในขณะที่<cstdio>คุณจะไม่ล็อคบ่อยและคุณยังมีตัวเลือกที่ไม่ล็อค

<iostream> เพิ่มการล็อคเพื่อให้ได้ผลลัพธ์ที่สอดคล้องน้อยลง


2
การนำไปใช้งานส่วนใหญ่ของ printf มีคุณสมบัติที่มีประโยชน์อย่างยิ่งสำหรับการแปลหลายภาษา: พารามิเตอร์ที่กำหนดหมายเลข หากคุณต้องการสร้างผลลัพธ์บางอย่างในสองภาษาที่แตกต่างกัน (เช่นอังกฤษและฝรั่งเศส) และการเรียงลำดับคำนั้นแตกต่างกันคุณสามารถใช้ printf เดียวกันกับสตริงการจัดรูปแบบที่แตกต่างกันและสามารถพิมพ์พารามิเตอร์ตามลำดับที่แตกต่างกัน
gnasher729

2
การจัดรูปแบบสตรีมที่ไม่ดีนั้นต้องมีข้อบกพร่องมากมายที่หายากฉันไม่รู้ว่าจะพูดอะไร คำตอบที่ดี จะโหวตมากกว่าหนึ่งครั้งถ้าทำได้
คณิตศาสตร์ที่

6
std::coutช้ากว่าหลายเท่าprintf()” - การอ้างสิทธิ์นี้ซ้ำไปซ้ำมาทั่วเน็ต แต่มันก็ไม่เป็นความจริงในทุกยุคทุกสมัย การใช้งานที่ทันสมัย iostream printfดำเนินการในหุ้นที่มี หลังยังดำเนินการจัดส่งเสมือนภายในเพื่อจัดการกับกระแสข้อมูลบัฟเฟอร์และ IO แปล (ทำโดยระบบปฏิบัติการ แต่ทำอย่างไรก็ตาม)
Konrad Rudolph

3
@KevinZ และมันก็เยี่ยมมาก แต่มันก็เป็นการเปรียบเทียบการโทรหนึ่งเดียวที่เจาะจงซึ่งแสดงถึงความแข็งแกร่งที่เฉพาะเจาะจงของ fmt (รูปแบบที่แตกต่างกันมากมายในสตริงเดียว) ในการใช้งานทั่วไปมากกว่าความแตกต่างระหว่างprintfและcoutลดขนาด อนึ่งมีการเปรียบเทียบจำนวนมากบนเว็บไซต์นี้
Konrad Rudolph

3
@ KonradRudolph ไม่จริงเช่นกัน Microbenchmarks มักจะประเมินค่าใช้จ่ายของการขยายตัวและทางอ้อมเนื่องจากไม่ได้ จำกัด ทรัพยากรบางอย่าง (ไม่ว่าจะเป็นรีจิสเตอร์, icache, หน่วยความจำ, ตัวพยากรณ์สาขา) ซึ่งโปรแกรมจริงจะ เมื่อคุณพูดถึง "การใช้งานทั่วไปมากขึ้น" โดยทั่วไปแล้วมันบอกว่าคุณมีการขยายตัวมากขึ้นในที่อื่นซึ่งเป็นเรื่องปกติ แต่ไม่อยู่ในหัวข้อ ในความคิดของฉันถ้าคุณไม่มีข้อกำหนดด้านประสิทธิภาพคุณไม่จำเป็นต้องเขียนโปรแกรมใน C ++
KevinZ

18

นอกเหนือไปจากสิ่งที่ทุกคำตอบอื่น ๆ ได้กล่าวว่า
นอกจากนี้ยังมีความจริงที่ว่าstd::endlเป็นไม่ได้'\n'เช่นเดียวกับ

นี่เป็นความเข้าใจผิดที่น่าเสียดาย std::endlไม่ได้หมายถึง "บรรทัดใหม่"
หมายความว่า "พิมพ์บรรทัดใหม่แล้วล้างข้อมูลในสตรีม " ล้างไม่ถูก!

ไม่สนใจความแตกต่างระหว่างprintfและstd::coutชั่วขณะอย่างสมบูรณ์เพื่อให้ eqvuialent ใช้งานได้กับตัวอย่าง C ของคุณตัวอย่าง C ++ ของคุณควรมีลักษณะดังนี้:

#include <iostream>

int main()
{
    std::cout << "Hello world\n";
    return 0;
}

และนี่คือตัวอย่างของสิ่งที่ตัวอย่างของคุณควรเป็นถ้าคุณรวมถึงการล้าง

#include <stdio.h>

int main()
{
    printf("Hello world\n");
    fflush(stdout);
    return 0;
}

C ++

#include <iostream>

int main()
{
    std::cout << "Hello world\n";
    std::cout << std::flush;
    return 0;
}

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


ที่จริงแล้วการใช้std::endl นั้นเทียบเท่ากับการทำงานเพื่อเขียนบรรทัดใหม่ไปยังสตรีม stdio ที่บัฟเฟอร์บรรทัด stdoutโดยเฉพาะอย่างยิ่งจะต้องเป็นแบบ line-buffered หรือ unbuffered เมื่อเชื่อมต่อกับอุปกรณ์แบบโต้ตอบ ฉันเชื่อว่า Linux ใช้ตัวเลือก line-buffered

อันที่จริงแล้วไลบรารี iostream ไม่มีโหมด line-buffered ... วิธีที่จะทำให้เอฟเฟ็กต์ของ line-buffering นั้นแม่นยำเพื่อใช้std::endlในการแสดงบรรทัดใหม่

@Hurkyl ยืนยัน? แล้วการใช้setvbuf(3)คืออะไร? หรือคุณหมายถึงว่าค่าเริ่มต้นคือบัฟเฟอร์บรรทัด? FYI: โดยปกติไฟล์ทั้งหมดจะถูกบล็อกบัฟเฟอร์ หากสตรีมอ้างถึงเทอร์มินัล (ตามปกติ stdout จะเป็นเช่นนั้น) มันจะถูกบัฟเฟอร์บรรทัด สตรีมข้อผิดพลาดมาตรฐาน stderr จะไม่มีการบัฟเฟอร์โดยค่าเริ่มต้นเสมอ
Pryftan

ไม่printfล้างโดยอัตโนมัติเมื่อพบอักขระขึ้นบรรทัดใหม่หรือไม่
bool3max

1
@ bool3max แค่บอกฉันว่าสภาพแวดล้อมของฉันทำอะไรมันอาจจะแตกต่างจากสภาพแวดล้อมอื่น ๆ แม้ว่ามันจะทำงานได้เหมือนกันในการใช้งานที่ได้รับความนิยมสูงสุด แต่นั่นไม่ได้หมายความว่าจะมีบางกรณี นั่นเป็นเหตุผลที่มาตรฐานมีความสำคัญมาก - มาตรฐานกำหนดว่าจะต้องมีอะไรที่เหมือนกันสำหรับการนำไปใช้ทั้งหมดหรือไม่หรือว่าอนุญาตให้มีการเปลี่ยนแปลงในระหว่างการนำไปใช้งานหรือไม่
Pharap

16

ในขณะที่คำตอบทางเทคนิคที่มีอยู่นั้นถูกต้อง แต่ฉันคิดว่าท้ายที่สุดคำถามนั้นเกิดจากความเข้าใจผิดนี้:

มันมีชื่อเสียงว่าใน C ++ คุณจ่ายสำหรับสิ่งที่คุณกิน

นี่เป็นเพียงการพูดคุยด้านการตลาดจากชุมชน C ++ (เพื่อความเป็นธรรมมีการพูดคุยด้านการตลาดในชุมชนภาษาทุกภาษา) มันไม่ได้มีความหมายอะไรเลยที่คุณสามารถพึ่งพาได้อย่างจริงจัง

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

โดยทั่วไปภาษาจำนวนมาก (แม้ว่าจะไม่ใช่ทั้งหมด) พยายามอย่างมีประสิทธิภาพโดยมีระดับความสำเร็จที่แตกต่างกัน C ++ อยู่ในระดับหนึ่ง แต่ไม่มีอะไรพิเศษหรือน่าอัศจรรย์เกี่ยวกับการออกแบบที่จะช่วยให้ประสบความสำเร็จอย่างสมบูรณ์แบบในเป้าหมายนี้


1
มีสองสิ่งที่ฉันสามารถคิดได้ว่าคุณจะจ่ายเงินให้กับสิ่งที่คุณไม่ได้ใช้คืออะไร: ข้อยกเว้นและ RTTI และฉันไม่คิดว่ามันเป็นการพูดคุยด้านการตลาด C ++ นั้นเป็น C ที่ทรงพลังกว่าซึ่งก็คือ "ไม่ต้องจ่ายสำหรับสิ่งที่คุณใช้"
Rakete1111

2
@ Rakete1111 เป็นที่ยอมรับกันมานานแล้วว่าหากข้อยกเว้นไม่ได้ถูกโยนออกไปพวกเขาก็ไม่ต้องเสียค่าใช้จ่าย หากโปรแกรมของคุณมีการขว้างปาอย่างต่อเนื่องควรออกแบบใหม่ หากเงื่อนไขของความล้มเหลวอยู่นอกเหนือการควบคุมของคุณคุณควรตรวจสอบเงื่อนไขด้วยการตรวจสอบสติที่คืนกลับมาก่อนที่จะเรียกวิธีการที่ขึ้นอยู่กับเงื่อนไขว่าเป็นเท็จ
schulmaster

1
@schulmaster: ข้อยกเว้นสามารถกำหนดข้อ จำกัด ในการออกแบบเมื่อโค้ดที่เขียนใน C ++ จำเป็นต้องโต้ตอบกับโค้ดที่เขียนในภาษาอื่นเนื่องจากการโอนการควบคุมที่ไม่ใช่ในท้องถิ่นสามารถทำงานได้อย่างราบรื่นเฉพาะในโมดูลหากโมดูลทราบวิธีการประสานงานกัน
supercat

1
( แต่เนื้อหาไม่ทั้งหมด) ภาษามุ่งมั่นที่จะมีประสิทธิภาพ ไม่ใช่ทุกอย่างแน่นอน: ภาษาการเขียนโปรแกรมลึกลับมีจุดมุ่งหมายที่จะแปลกใหม่ / น่าสนใจและไม่มีประสิทธิภาพ esolangs.org บางคนเช่น BrainFuck นั้นไม่มีประสิทธิภาพที่มีชื่อเสียง หรือตัวอย่างเช่นการเช็คสเปียร์เขียนโปรแกรมภาษา, 227 ไบต์ขนาดต่ำสุด (codegolf) เพื่อพิมพ์จำนวนเต็มทั้งหมด ภาษาที่มีไว้สำหรับใช้ในการผลิตส่วนใหญ่มีเป้าหมายเพื่อประสิทธิภาพ แต่บางคน (เช่นทุบตี) มีจุดมุ่งหมายเพื่อความสะดวกและเป็นที่รู้กันว่าช้า
Peter Cordes

2
มันคือการตลาด แต่มันเกือบจะเป็นจริงโดยสิ้นเชิง คุณสามารถติด<cstdio>และไม่รวมเช่นเดียวกับวิธีการที่คุณสามารถรวบรวมกับ<iostream> -fno-exceptions -fno-rtti -fno-unwind-tables -fno-asynchronous-unwind-tables
KevinZ

11

ฟังก์ชั่นอินพุต / เอาต์พุตใน C ++ เขียนขึ้นอย่างหรูหราและออกแบบมาให้ใช้งานง่าย ในหลาย ๆ แง่มุมพวกเขาเป็นการนำเสนอสำหรับคุณสมบัติเชิงวัตถุใน C ++

แต่คุณจะได้รับผลตอบแทนเล็กน้อย แต่ก็เล็กน้อยเมื่อเทียบกับเวลาที่ระบบปฏิบัติการของคุณจัดการกับฟังก์ชั่นในระดับที่ต่ำกว่า

คุณสามารถย้อนกลับไปยังฟังก์ชั่นสไตล์ C ได้เนื่องจากเป็นส่วนหนึ่งของมาตรฐาน C ++ หรืออาจยกเลิกการพกพาทั้งหมดและใช้การโทรโดยตรงไปยังระบบปฏิบัติการของคุณ


23
"ฟังก์ชั่นอินพุต / เอาท์พุตใน C ++ เป็นสัตว์ประหลาดที่น่ากลัวที่พยายามซ่อนธรรมชาติของชาวคธูเลียนไว้เบื้องหลังแผ่นไม้อัดบาง ๆ ที่มีประโยชน์ในหลาย ๆ ด้านพวกเขาแสดงให้เห็นว่าจะไม่ออกแบบรหัส C ++ ที่ทันสมัย" อาจจะแม่นยำมากขึ้น
673679

3
@ user673679: จริงมาก ปัญหาที่ดีกับ C ++ I / O ลำธารคือสิ่งที่อยู่ภายใต้: มีจริงๆ เป็นจำนวนมากของความซับซ้อนที่เกิดขึ้นและทุกคนที่เคยจัดการกับพวกเขา (ผมหมายถึงstd::basic_*streamลง) รู้ eadaches เข้า พวกเขาถูกออกแบบมาให้กว้างขวางโดยทั่วไปและขยายผ่านการสืบทอด แต่ในที่สุดก็ไม่มีใครทำเช่นนั้นเพราะความซับซ้อนของพวกเขา (มีหนังสือที่เขียนไว้ใน iostreams) มากจนห้องสมุดใหม่เกิดมาเพื่อสิ่งนั้น (เช่นเพิ่ม, ICU เป็นต้น) ฉันสงสัยว่าเราจะหยุดจ่ายเงินสำหรับความผิดพลาดนี้
edmz

1

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

  1. Barne มีหลักการออกแบบหลักที่จะไม่ปล่อยให้ประสิทธิภาพเป็นเหตุผลสำหรับการอยู่ใน C มากกว่า C ++ ที่กล่าวว่าหนึ่งต้องระมัดระวังเพื่อให้ได้ประสิทธิภาพเหล่านี้และมีประสิทธิภาพเป็นครั้งคราวที่ทำงานได้เสมอ แต่ไม่ได้ 'เทคนิค' ภายใน C ข้อมูลจำเพาะ ตัวอย่างเช่นรูปแบบของเขตข้อมูลบิตไม่ได้ระบุจริงๆ

  2. ลองมองดูนกกระจอกเทศ โอ้พระเจ้าของฉันป่อง! ฉันไม่แปลกใจเลยที่จะพบกับเครื่องจำลองการบินในนั้น แม้แต่ printf ของ stdlib () ก็สามารถใช้งานได้ประมาณ 50K โปรแกรมเมอร์เหล่านี้ไม่ได้ขี้เกียจ: ครึ่งหนึ่งของขนาด printf นั้นเกี่ยวข้องกับข้อโต้แย้งที่มีความแม่นยำทางอ้อมที่คนส่วนใหญ่ไม่เคยใช้ ไลบรารีของตัวประมวลผลที่ จำกัด เกือบทุกตัวจะสร้างโค้ดเอาต์พุตของตัวเองแทน printf

  3. การเพิ่มขนาดมักจะให้ประสบการณ์ที่มีอยู่และยืดหยุ่นมากขึ้น ในฐานะที่เป็นการเปรียบเทียบเครื่องขายสินค้าอัตโนมัติจะขายถ้วยกาแฟเหมือนสารสำหรับเหรียญไม่กี่และการทำธุรกรรมทั้งหมดจะใช้เวลาไม่กี่นาที การแวะเข้าไปในร้านอาหารที่ดีนั้นเกี่ยวข้องกับการจัดโต๊ะการนั่งสั่งการรอรับถ้วยสวยรับใบเรียกเก็บเงินการจ่ายเงินในรูปแบบที่คุณเลือกเพิ่มเคล็ดลับและอยากได้วันที่ดี มันเป็นประสบการณ์ที่แตกต่างและสะดวกกว่าถ้าคุณออกไปกับเพื่อน ๆ เพื่อทานอาหารที่ซับซ้อน

  4. ผู้คนยังคงเขียน ANSI C แต่ไม่ค่อยมี K&R C. ประสบการณ์ของฉันคือเรามักจะรวบรวมมันด้วยคอมไพเลอร์ C ++ โดยใช้ tweaks การกำหนดค่าบางอย่างเพื่อ จำกัด สิ่งที่ถูกลากเข้ามามีข้อโต้แย้งที่ดีสำหรับภาษาอื่น ๆ : ; มีข้อโต้แย้งที่ดีสำหรับการบรรจุฟิลด์อย่างชาญฉลาดและการจัดวางหน่วยความจำ IMHO ผมคิดว่าการออกแบบภาษาใด ๆ ควรเริ่มต้นด้วยรายชื่อของเป้าหมายเหมือนเซนของงูใหญ่

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

ไม่มีคำตอบ จะไม่มีคำตอบ นั่นคือคำตอบ

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