C # ให้“ เชือกน้อยกว่าให้คุณแขวน” กว่า C ++ หรือไม่? [ปิด]


14

โจ Spolsky ลักษณะ C ++ เป็น "เชือกพอที่จะแขวนตัวเอง" อันที่จริงเขาสรุป "Effective C ++" โดย Scott Meyers:

มันเป็นหนังสือที่บอกว่าโดยทั่วไปแล้ว C ++ นั้นมีเชือกพอที่จะแขวนตัวคุณเองและจากนั้นอีกสองสามไมล์ของเชือกแล้วก็มียาฆ่าตัวตายสองตัวที่ปลอมตัวเป็น M & Ms ...

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

นี่คือคำถามของฉัน:

  1. C # หลีกเลี่ยงข้อผิดพลาดที่หลีกเลี่ยงใน C ++ โดยการโปรแกรมอย่างระมัดระวังหรือไม่ ถ้าเป็นเช่นนั้นในระดับใดและพวกเขาหลีกเลี่ยง?
  2. มีข้อผิดพลาดใหม่ ๆ ใน C # ที่โปรแกรมเมอร์ C # ควรรู้หรือไม่? ถ้าเป็นเช่นนั้นทำไมพวกเขาถึงไม่สามารถออกแบบ C # ได้?

10
จากคำถามที่พบบ่อย : Your questions should be reasonably scoped. If you can imagine an entire book that answers your question, you’re asking too much.. ฉันเชื่อว่าสิ่งนี้มีคุณสมบัติเป็นคำถามดังกล่าว ...
Oded

@Oded คุณอ้างถึงคำถามพาดหัวที่ จำกัด ตัวละครหรือไม่? หรือคำถามที่แม่นยำมากกว่า 3 ข้อของฉันในเนื้อหาโพสต์ของฉัน
alx9r

3
ตรงไปตรงมา - ทั้งชื่อและแต่ละ "คำถามที่แม่นยำยิ่งขึ้น"
Oded

3
ฉันได้เริ่มการสนทนา Metaเกี่ยวกับคำถามนี้
Oded

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

คำตอบ:


33

พื้นฐานความแตกต่างระหว่าง C ++ และ C # เกิดจากพฤติกรรมที่ไม่ได้กำหนด

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

C / C ++:

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

อาจจะอ่านซีรี่ส์ 3 ส่วนนี้ในพฤติกรรมที่ไม่ได้กำหนด

นี่คือสิ่งที่ทำให้ C ++ เร็วขึ้น - คอมไพเลอร์ไม่ต้องกังวลกับสิ่งที่เกิดขึ้นเมื่อสิ่งผิดปกติดังนั้นจึงสามารถหลีกเลี่ยงการตรวจสอบความถูกต้องได้

C #, Java, ฯลฯ

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

ทุกสิ่งทุกอย่างเป็นเพียงน้ำเกรวี่


สิ่งที่ไม่ได้กำหนดทั้งหมดนั้นถูกกำหนดโดยการนำไปใช้จริงดังนั้นหากคุณล้นจำนวนเต็มที่ไม่ได้ลงนามใน Visual Studio คุณจะได้รับข้อยกเว้นหากคุณเปิดใช้งานคอมไพเลอร์แฟล็กที่ถูกต้อง ตอนนี้ฉันรู้ว่านี่คือสิ่งที่คุณกำลังพูดถึง แต่มันไม่ได้เป็นพฤติกรรมที่ไม่ได้กำหนดแต่เป็นเพียงที่คนทั่วไปมักจะไม่ตรวจสอบ (เช่นเดียวกับพฤติกรรมที่ไม่ได้กำหนดอย่างแท้จริงเช่นโอเปอเรเตอร์ ++ มันถูกกำหนดอย่างดีโดยคอมไพเลอร์แต่ละตัว) คุณสามารถพูดแบบเดียวกันกับ C # เพียงแค่มีการใช้งานเพียง 1 - พฤติกรรมที่ไม่ได้กำหนดจำนวนมากหากคุณทำงานใน Mono - เช่น bugzilla.xamarin.com/show_bug.cgi?id=310
gbjbaanb

1
มันกำหนดไว้จริง ๆ หรือถูกกำหนดโดยการใช้. net ในปัจจุบันกับ Windows รุ่นปัจจุบันเท่านั้น แม้กระทั่งพฤติกรรมที่ไม่ได้กำหนดของ c ++ จะถูกกำหนดอย่างสมบูรณ์หากคุณกำหนดให้เป็นสิ่งที่ g ++ ทำ
Martin Beckett

6
จำนวนเต็มล้นที่ไม่ได้ลงนามนั้นไม่ใช่ UB เลย มันเต็มไปด้วยจำนวนเต็มลงนามว่า UB
DeadMG

6
@gbjbaanb: ชอบ DeadMG กล่าวว่า - ลงนามล้นจำนวนเต็มจะไม่ได้กำหนด มันไม่ได้กำหนดการใช้งาน วลีเหล่านั้นมีความหมายเฉพาะในมาตรฐาน C ++ และไม่เหมือนกัน อย่าทำผิดพลาด
541686

1
@CharlesSalvia: เอ่อ "C ++ ช่วยให้การใช้ CPU แคช" ง่ายกว่า C # ได้อย่างไร? และ C + + ประเภทใดที่ให้การควบคุมหน่วยความจำเกินขนาดที่คุณไม่มีใน C #
user541686

12

C # หลีกเลี่ยงข้อผิดพลาดที่หลีกเลี่ยงใน C ++ โดยการโปรแกรมอย่างระมัดระวังหรือไม่ ถ้าเป็นเช่นนั้นในระดับใดและพวกเขาหลีกเลี่ยง?

ส่วนใหญ่ทำบางอย่างทำไม่ได้ และแน่นอนว่ามันสร้างสิ่งใหม่ขึ้นมา

  1. พฤติกรรมที่ไม่ได้กำหนด - ข้อผิดพลาดที่ใหญ่ที่สุดของ C ++ คือมีภาษาจำนวนมากที่ไม่ได้กำหนด ผู้แปลสามารถระเบิดจักรวาลเมื่อคุณทำสิ่งเหล่านี้และมันจะไม่เป็นไร โดยปกติแล้วนี่เป็นเรื่องแปลก แต่ก็เป็นเรื่องปกติที่โปรแกรมของคุณจะทำงานได้ดีบนเครื่องหนึ่งและไม่มีเหตุผลที่ดีเลยที่จะไม่ทำงานในอีกเครื่องหนึ่ง หรือแย่กว่านั้นคือทำตัวต่าง ๆ อย่างละเอียด C # มีบางกรณีของพฤติกรรมที่ไม่ได้กำหนดในข้อกำหนดของมัน แต่มันหายากและในพื้นที่ของภาษาที่มีการเดินทางไม่บ่อยนัก C ++ มีความเป็นไปได้ที่จะพบกับพฤติกรรมที่ไม่ได้กำหนดทุกครั้งที่คุณสร้างคำสั่ง

  2. Memory Leaks - นี่เป็นเรื่องที่น่ากังวลน้อยกว่าสำหรับ C ++ ที่ทันสมัย ​​แต่สำหรับผู้เริ่มต้นและในช่วงครึ่งชีวิต C ++ ทำให้มันง่ายในการรั่วไหลของหน่วยความจำ C ++ ที่มีประสิทธิภาพมาพร้อมกับวิวัฒนาการของการปฏิบัติเพื่อขจัดข้อกังวลนี้ ที่กล่าวว่า C # ยังคงสามารถรั่วไหลหน่วยความจำ กรณีที่คนส่วนใหญ่พบเจอคือการจับภาพเหตุการณ์ หากคุณมีวัตถุและวางวิธีการอย่างใดอย่างหนึ่งเป็นตัวจัดการกับเหตุการณ์เจ้าของกิจกรรมนั้นจะต้องเป็น GC'd เพื่อให้วัตถุตาย ผู้เริ่มต้นส่วนใหญ่ไม่ทราบว่าตัวจัดการเหตุการณ์ถือเป็นข้อมูลอ้างอิง นอกจากนี้ยังมีปัญหาเกี่ยวกับการไม่ทิ้งทรัพยากรที่ใช้แล้วทิ้งซึ่งสามารถรั่วไหลของหน่วยความจำได้ แต่สิ่งเหล่านี้ไม่ได้เป็นเรื่องธรรมดาเหมือนพอยน์เตอร์ใน C ++ ที่มีประสิทธิภาพ

  3. รวบรวม - c ++ มีรูปแบบการรวบรวมปัญญาอ่อน สิ่งนี้นำไปสู่กลเม็ดต่าง ๆ ที่จะเล่นได้ดีกับมันและทำให้เวลามีน้อยลง

  4. Strings - Modern C ++ ทำให้สิ่งนี้ดีขึ้นเล็กน้อย แต่char*รับผิดชอบต่อการละเมิดความปลอดภัยประมาณ 95% ก่อนปี 2000 สำหรับโปรแกรมเมอร์ที่มีประสบการณ์พวกเขาจะมุ่งเน้นstd::stringแต่ก็ยังมีบางสิ่งที่ต้องหลีกเลี่ยงและมีปัญหาในไลบรารีเก่า / แย่ลง . และนั่นคือการอธิษฐานที่คุณไม่ต้องการการสนับสนุน Unicode

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

มีข้อผิดพลาดใหม่ ๆ ใน C # ที่โปรแกรมเมอร์ C # ควรรู้หรือไม่? ถ้าเป็นเช่นนั้นทำไมพวกเขาถึงไม่สามารถออกแบบ C # ได้?

ฉันพูดถึงเหตุการณ์ "ความจำรั่ว" นี่ไม่ใช่ปัญหาภาษามากนักเนื่องจากโปรแกรมเมอร์คาดหวังว่าบางสิ่งที่ภาษาไม่สามารถทำได้

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

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

List<Action> actions = new List<Action>();
for(int x = 0; x < 10; ++x ){
    actions.Add(() => Console.WriteLine(x));
}

foreach(var action in actions){
    action();
}

ไม่ได้ทำในสิ่งที่คิดอย่างไร้เดียงสา สิ่งนี้พิมพ์1010 ครั้ง

ฉันแน่ใจว่ามีคนอื่นอีกหลายคนที่ฉันลืม แต่ประเด็นหลักคือพวกเขาแพร่หลายน้อยกว่า


4
การรั่วไหลของความจำเป็นเรื่องของอดีตและเป็นchar*เช่นนั้น ไม่ต้องพูดถึงว่าคุณยังสามารถรั่วไหลของหน่วยความจำใน C # ได้ดี
DeadMG

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

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

2
@deadMG แน่นอนแม้ว่าฉันจะยืนยันว่าหลายเทคนิคการเขียนโปรแกรมแม่แบบเมตาที่ใช้ / จำเป็นใน C ++ จะ ... ดำเนินการได้ดีขึ้นผ่านกลไกที่แตกต่างกัน
Telastyn

2
@Telastyn จุดรวมของ type_traits คือการรับข้อมูลชนิดในเวลารวบรวมเพื่อให้คุณสามารถใช้ข้อมูลนี้เพื่อทำสิ่งต่าง ๆ เช่นเทมเพลตที่เชี่ยวชาญหรือฟังก์ชั่นโอเวอร์โหลดในรูปแบบที่เฉพาะเจาะจงโดยใช้enable_if
Charles Salvia

10

ในความคิดของฉันอันตรายของ C ++ ค่อนข้างพูดเกินจริง

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

"เชือกพิเศษ" นี้เพื่อที่จะพูดซึ่งโจเอล Spolsky พูดถึงโดยทั่วไปมาถึงสิ่งหนึ่ง: การเขียนชั้นเรียนที่ภายในจัดการหน่วยความจำของตัวเองหรือที่เรียกว่า " กฎ 3 " (ซึ่งตอนนี้สามารถเรียกว่ากฎ ของ 4 หรือกฎ 5 ใน C ++ 11) ซึ่งหมายความว่าหากคุณต้องการเขียนคลาสที่จัดการการจัดสรรหน่วยความจำของตัวเองภายในคุณต้องรู้ว่าคุณกำลังทำอะไรอยู่มิฉะนั้นโปรแกรมของคุณอาจมีปัญหา คุณต้องสร้าง Constructor คัดลอก Constructor, Destructor และ Operator อย่างระมัดระวังซึ่งเป็นเรื่องง่ายที่จะเกิดความผิดพลาดซึ่งมักจะทำให้เกิดความผิดพลาดที่แปลกประหลาดที่รันไทม์

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

class Foo
{
    public:

    Foo(const std::string& s) 
        : m_first_name(s)
    { }

    private:

    std::string m_first_name;
};

คลาสนี้ดูใกล้เคียงกับสิ่งที่คุณทำใน Java หรือ C # - ไม่ต้องการการจัดการหน่วยความจำอย่างชัดเจน (เนื่องจากคลาสไลบรารีstd::stringจะดูแลทุกอย่างโดยอัตโนมัติ) และไม่จำเป็นต้องใช้สิ่ง "Rule of 3" ตั้งแต่เริ่มต้น ตัวสร้างการคัดลอกและผู้ประกอบการที่ได้รับมอบหมายเป็นเรื่องปกติ

เมื่อคุณพยายามทำสิ่งที่ชอบเท่านั้น:

class Foo
{
    public:

    Foo(const char* s)
    { 
        std::size_t len = std::strlen(s);
        m_name = new char[len + 1];
        std::strcpy(m_name, s);
    }

    Foo(const Foo& f); // must implement proper copy constructor

    Foo& operator = (const Foo& f); // must implement proper assignment operator

    ~Foo(); // must free resource in destructor

    private:

    char* m_name;
};

ในกรณีนี้มันอาจเป็นเรื่องยากสำหรับมือใหม่ที่จะได้รับการมอบหมาย destructor และคัดลอกคอนสตรัคที่ถูกต้อง แต่สำหรับกรณีส่วนใหญ่ไม่มีเหตุผลที่จะทำเช่นนี้ c ++ ทำให้มันง่ายมากที่จะหลีกเลี่ยงคู่มือการจัดการหน่วยความจำ 99% ของเวลาเรียนโดยใช้ห้องสมุดเหมือนและstd::stringstd::vector

ปัญหาที่เกี่ยวข้องอีกประการหนึ่งคือการจัดการหน่วยความจำด้วยตนเองในลักษณะที่ไม่คำนึงถึงความเป็นไปได้ของข้อยกเว้นที่เกิดขึ้น ชอบ:

char* s = new char[100];
some_function_which_may_throw();
/* ... */
delete[] s;

ถ้าsome_function_which_may_throw()จริงจะโยนยกเว้นคุณจะทิ้งให้อยู่กับหน่วยความจำรั่วเพราะหน่วยความจำที่จัดสรรไว้สำหรับsจะไม่ถูกยึด แต่ในทางกลับกันในทางปฏิบัติมันแทบจะไม่มีปัญหาอีกต่อไปด้วยเหตุผลเดียวกับที่ว่า "กฎ 3 ข้อ" ไม่ใช่ปัญหาอีกต่อไปแล้ว มันยากมาก (และโดยปกติไม่จำเป็น) เพื่อจัดการหน่วยความจำของคุณด้วยพอยน์เตอร์ เพื่อหลีกเลี่ยงปัญหาข้างต้นสิ่งที่คุณต้องทำคือใช้std::stringหรือstd::vectorและตัวทำลายจะถูกเรียกใช้โดยอัตโนมัติในระหว่างการคลี่คลายคลี่คลายหลังจากโยนข้อยกเว้น

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

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


มันเป็นกฎสามข้อใน C ++ 03 และเป็นกฎข้อสี่ใน C ++ 11 ทันที
DeadMG

1
คุณสามารถเรียกมันว่า "Rule of 5" สำหรับ copy constructor, ย้าย constructor, copy copy มอบหมาย, move assignment และ destructor แต่ความหมายของการย้ายไม่จำเป็นเสมอไปเพียงแค่การจัดการทรัพยากรที่เหมาะสม
ชาร์ลส์ซัลเวีย

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

2
Does C# avoid pitfalls that are avoided in C++ only by careful programming?มันตอบคำถาม คำตอบคือ "ไม่จริงเพราะเป็นเรื่องง่ายที่จะหลีกเลี่ยงหลุมพรางที่ Joel กำลังพูดถึงใน C ++ ที่ทันสมัย"
Charles Salvia

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

3

ฉันจะไม่เห็นด้วยจริงๆ อาจผิดพลาดน้อยกว่า C ++ ตามที่มีอยู่ในปี 1985

C # หลีกเลี่ยงข้อผิดพลาดที่หลีกเลี่ยงใน C ++ โดยการโปรแกรมอย่างระมัดระวังหรือไม่ ถ้าเป็นเช่นนั้นในระดับใดและพวกเขาหลีกเลี่ยง?

ไม่ได้จริงๆ กฎเช่นกฎสามได้สูญเสียความสำคัญมากใน C ++ 11 ต้องขอบคุณunique_ptrและshared_ptrเป็นมาตรฐาน การใช้คลาสมาตรฐานในแบบที่สมเหตุสมผลไม่ได้เป็น "การเข้ารหัสอย่างระมัดระวัง" แต่เป็น "การเข้ารหัสพื้นฐาน" นอกจากนี้สัดส่วนของประชากร C ++ ที่ยังคงโง่เง่าไม่ได้รู้หรือทั้งสองอย่างเพื่อทำสิ่งต่าง ๆ เช่นการจัดการหน่วยความจำด้วยตนเองนั้นต่ำกว่าเมื่อก่อนมาก ความจริงก็คืออาจารย์ที่ต้องการแสดงกฎเช่นนั้นต้องใช้เวลาหลายสัปดาห์พยายามหาตัวอย่างที่พวกเขายังคงใช้เพราะชั้นเรียนมาตรฐานครอบคลุมการใช้งานจริงทุกกรณีเท่าที่จะเป็นไปได้ เทคนิค C ++ ที่มีประสิทธิผลมากมายได้ไปในแนวทางเดียวกันกับวิธีของโดโด คนอื่น ๆ จำนวนมากไม่ได้เจาะจง C ++ จริงๆ ให้ฉันดู. ข้ามรายการแรกสิบถัดไปคือ:

  1. อย่าใช้รหัส C ++ เหมือนเป็น C นี่เป็นเพียงสามัญสำนึก
  2. จำกัด อินเทอร์เฟซของคุณและใช้การห่อหุ้ม OOP
  3. ผู้เขียนโค้ดการเริ่มต้นสองเฟสควรถูกเบิร์นที่สเตค OOP
  4. รู้ว่าความหมายของคุณค่าคืออะไร นี่เป็น C ++ ที่เฉพาะเจาะจงจริงๆเหรอ?
  5. จำกัด อินเทอร์เฟซของคุณอีกครั้งคราวนี้ในวิธีที่แตกต่างกันเล็กน้อย OOP
  6. destructors เสมือน ใช่. อันนี้น่าจะยังคงใช้ได้ - ค่อนข้าง finalและoverrideช่วยเปลี่ยนแปลงเกมนี้โดยเฉพาะให้ดีขึ้น ทำให้ destructor ของคุณoverrideและคุณรับประกันการรวบรวมข้อผิดพลาดที่ดีถ้าคุณได้รับมรดกจากคนที่ไม่ได้ทำให้ virtualdestructor ทำให้ชั้นเรียนของคุณfinalและไม่มีการขัดผิวที่ไม่ดีสามารถเข้ามาและสืบทอดโดยไม่ได้ตั้งใจโดยไม่ต้องใช้ตัวทำลายเสมือน
  7. สิ่งเลวร้ายเกิดขึ้นหากฟังก์ชันการล้างข้อมูลล้มเหลว นี่ไม่เฉพาะเจาะจงกับ C ++ - คุณสามารถดูคำแนะนำเดียวกันสำหรับทั้ง Java และ C # - และดีมากสำหรับทุกภาษา การมีฟังก์ชั่นการล้างข้อมูลที่อาจล้มเหลวนั้นเป็นเพียงข้อผิดพลาดธรรมดาและไม่มี C ++ หรือแม้แต่ OOP เกี่ยวกับรายการนี้
  8. ระวังการสั่งซื้อคอนสตรัคเตอร์มีผลต่อฟังก์ชั่นเสมือน อย่างสนุกสนานใน Java (ปัจจุบันหรืออดีต) มันจะเรียกฟังก์ชันของคลาส Derived อย่างไม่ถูกต้องซึ่งยิ่งแย่กว่าพฤติกรรมของ C ++ ไม่ว่าปัญหานี้จะไม่เฉพาะกับ C ++
  9. ตัวดำเนินการโอเวอร์โหลดควรทำงานตามที่ผู้คนคาดหวัง ไม่เจาะจงจริงๆ นรกมันแทบจะไม่ได้ใช้ตัวดำเนินการมากเกินไปเหมือนกันสามารถนำไปใช้กับฟังก์ชั่นใด ๆ - อย่าตั้งชื่อเดียว
  10. ตอนนี้ถือว่าจริงแล้วการปฏิบัติที่ไม่ดี ผู้ประกอบการที่ได้รับการยกเว้นอย่างปลอดภัยทั้งหมดจัดการกับการมอบหมายตัวเองได้ดีและการมอบหมายด้วยตนเองนั้นเป็นข้อผิดพลาดของโปรแกรมเชิงตรรกะอย่างมีประสิทธิภาพและการตรวจสอบการมอบหมายด้วยตนเองนั้นไม่คุ้มค่าประสิทธิภาพ

เห็นได้ชัดว่าฉันจะไม่ผ่านทุกรายการ C ++ ที่มีประสิทธิภาพ แต่ส่วนใหญ่จะใช้แนวคิดพื้นฐานกับ C ++ คุณจะพบคำแนะนำเดียวกันในภาษาของผู้ให้บริการโอเวอร์โหลดable-operator destructors เสมือนเป็นเพียงสิ่งเดียวที่เป็นอันตราย C ++ และยังคงใช้ได้แม้ว่าเนื้อหาของfinalคลาส C ++ 11 จะไม่ถูกต้องเหมือนเดิม โปรดจำไว้ว่า Effective C ++ นั้นถูกเขียนขึ้นเมื่อแนวคิดของการใช้ OOP และคุณลักษณะเฉพาะของ C ++ นั้นยังคงใหม่อยู่ รายการเหล่านี้แทบจะไม่เกี่ยวกับข้อผิดพลาดของ C ++ และอื่น ๆ เกี่ยวกับวิธีรับมือกับการเปลี่ยนแปลงจาก C และวิธีใช้ OOP อย่างถูกต้อง

แก้ไข: ข้อผิดพลาดของ C ++ mallocไม่รวมถึงสิ่งต่างๆเช่นข้อผิดพลาดของ ฉันหมายความว่าหนึ่งข้อผิดพลาดทุกครั้งที่คุณพบในรหัส C คุณสามารถค้นหาได้อย่างเท่าเทียมกันในรหัส C # ดังนั้นจึงไม่เกี่ยวข้องกันเป็นพิเศษและประการที่สองเพียงเพราะมาตรฐานกำหนดไว้สำหรับการทำงานร่วมกันไม่ได้หมายความว่า รหัส. มาตรฐานกำหนดgotoเช่นกัน แต่ถ้าคุณต้องเขียนกองสปาเก็ตตี้ที่ยุ่งเหยิงโดยใช้มันฉันคิดว่าปัญหาของคุณไม่ใช่ภาษา มีความแตกต่างอย่างมากระหว่าง "การเข้ารหัสอย่างระมัดระวัง" และ "การติดตามสำนวนพื้นฐานของภาษา"

มีข้อผิดพลาดใหม่ ๆ ใน C # ที่โปรแกรมเมอร์ C # ควรรู้หรือไม่? ถ้าเป็นเช่นนั้นทำไมพวกเขาถึงไม่สามารถออกแบบ C # ได้?

usingดูด มันทำจริงๆ และฉันก็ไม่รู้ว่าทำไมบางอย่างถึงไม่ดีขึ้น ยิ่งไปกว่านั้นBase[] = Derived[]และทุก ๆ การใช้งาน Object ซึ่งมีอยู่เพราะนักออกแบบดั้งเดิมไม่สามารถสังเกตเห็นความสำเร็จอันยิ่งใหญ่ที่เทมเพลตอยู่ใน C ++ และตัดสินใจว่า "เรามีทุกสิ่งที่สืบทอดมาจากทุกสิ่ง . ฉันยังเชื่อว่าคุณสามารถพบความประหลาดใจที่น่ารังเกียจในสิ่งต่าง ๆ เช่นสภาพการแข่งขันกับผู้ได้รับมอบหมายและความสนุกสนานอื่น ๆ จากนั้นก็มีสิ่งทั่วไปอื่น ๆ เช่นวิธีที่ generics ดูดอย่างน่ากลัวเมื่อเปรียบเทียบกับเทมเพลตการวางบังคับทุกอย่างที่ไม่จำเป็นจริงๆใน a classและสิ่งต่าง ๆ


5
ฐานข้อมูลผู้ใช้ที่มีการศึกษาหรือสิ่งก่อสร้างใหม่ ๆ นั้นไม่ได้ลดความสำคัญลงของเชือก พวกเขาเป็นเพียงการทำงานเพื่อให้คนน้อยลงมาแขวน แม้ว่านี่จะเป็นคำอธิบายที่ดีเกี่ยวกับ Effective C ++ และบริบทในการวิวัฒนาการของภาษา
Telastyn

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

2
การใช้ส่วน C ของ C ++ นั้นไม่ต่างกับการเขียนโค้ดทั้งหมดของคุณunsafeใน C # ซึ่งไม่ดีเท่า ฉันสามารถบันทึกข้อผิดพลาดของการเข้ารหัส C # เช่น C เช่นกันทุกครั้งถ้าคุณต้องการ
DeadMG

@DeadMG ดังนั้นจริงๆคำถามที่ควรจะเป็น "c ++ โปรแกรมเมอร์มีเชือกพอที่จะแขวนตัวเองตราบใดที่เขาเป็นโปรแกรมเมอร์ C"
gbjbaanb

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

3

C # หลีกเลี่ยงข้อผิดพลาดที่หลีกเลี่ยงใน C ++ โดยการโปรแกรมอย่างระมัดระวังหรือไม่ ถ้าเป็นเช่นนั้นในระดับใดและพวกเขาหลีกเลี่ยง?

C # มีข้อดีดังนี้:

  • ไม่สามารถใช้งานร่วมกับ C ย้อนหลังได้ดังนั้นหลีกเลี่ยงการมีคุณลักษณะภาษา "ชั่วร้าย" (เช่นพอยน์เตอร์พอยน์เตอร์) ที่ยาวซึ่งสะดวกในการสร้างประโยค แต่ตอนนี้ถือว่าเป็นรูปแบบที่ไม่ดี
  • การมีซีแมนทิกส์อ้างอิงแทนซีแมนทิกส์คุณค่าซึ่งทำให้ไอเท็มc ++ ประสิทธิผลอย่างน้อย 10 รายการ (แต่แนะนำข้อผิดพลาดใหม่)
  • มีพฤติกรรมที่กำหนดการใช้งานน้อยกว่า C ++
    • โดยเฉพาะอย่างยิ่งใน C ++ อักขระการเข้ารหัสของchar, stringฯลฯ คือการดำเนินการที่กำหนดไว้ ความแตกต่างระหว่างวิธี Windows กับ Unicode ( wchar_tสำหรับ UTF-16, charสำหรับ "รหัสเพจ" ที่ล้าสมัย) และวิธี * nix (UTF-8) ทำให้เกิดปัญหาอย่างมากในรหัสข้ามแพลตฟอร์ม C #, OTOH รับประกันว่า a stringคือ UTF-16

มีข้อผิดพลาดใหม่ ๆ ใน C # ที่โปรแกรมเมอร์ C # ควรรู้หรือไม่?

ใช่: IDisposable

มีหนังสือเทียบเท่ากับ "Effective C ++" สำหรับ C # หรือไม่

มีหนังสือที่เรียกว่าเป็นที่มีประสิทธิภาพ C #ซึ่งเป็นที่คล้ายกันในโครงสร้างที่จะมีผลบังคับใช้ C


0

ไม่ C # (และ Java) ปลอดภัยน้อยกว่า C ++

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

C ++:

{
   some_resource r(...);  // resource initialized
   ...
}  // resource destructor called, no leaks here

ค#:

{
   SomeResource r = new SomeResource(...); // resource initialized
   ...
} // did I need to finalize that?  May I should have used 'using' 
  // (or in Java, a grotesque try/finally construct)?  No way to tell
  // without checking the documentation for SomeResource

C ++:

{
    auto_ptr<SomeInterface> i = SomeFactory.create(...);
    i->f(...);
} // automatic finalization and memory release.  A new implementation of
  // SomeInterface can allocate and free resources with no impact
  // on existing code

ค#:

{
   SomeInterface i = SomeFactory.create(...);
   i.f(...);
   ...
} // Sure hope someone didn't create an implementation of SomeInterface
  // that requires finalization.  In C# and Java it is necessary to decide whether
  // any implementation could require finalization when the interface is defined.
  // If the initial decision is 'no finalization', then no future implementation  
  // can acquire any resource without creating potential leaks in existing code.

3
... มันค่อนข้างน่าสนใจใน IDEs ที่ทันสมัยเพื่อตรวจสอบว่ามีบางสิ่งที่สืบทอดมาจาก IDisposable ปัญหาหลักคือคุณต้องรู้ที่จะใช้auto_ptr(หรือบางส่วนของญาติ) นั่นคือเชือกสุภาษิต
Telastyn

2
@Tastastyn ไม่ประเด็นคือคุณใช้ตัวชี้สมาร์ทเสมอเว้นแต่ว่าคุณรู้ว่าคุณไม่ต้องการ ใน C # การใช้ข้อความเหมือนกับเชือกที่คุณอ้างถึง (เช่นใน C ++ คุณต้องจำไว้ว่าให้ใช้ตัวชี้สมาร์ทเหตุใด C # จึงไม่เลวแม้ว่าคุณจะต้องจำให้ใช้คำสั่งการใช้เสมอ)
gbjbaanb

1
@gbjbaanb เพราะอะไรนะ? 5% ส่วนใหญ่ของคลาส C # เป็นแบบใช้แล้วทิ้ง? และคุณรู้ว่าคุณต้องกำจัดพวกเขาหากพวกเขาทิ้ง ใน C ++ วัตถุทุกชิ้นจะถูกทิ้ง และคุณไม่รู้ว่าต้องจัดการกับอินสแตนซ์ของคุณหรือไม่ เกิดอะไรขึ้นสำหรับพอยน์เตอร์ที่ส่งคืนซึ่งไม่ได้มาจากโรงงาน เป็นความรับผิดชอบของคุณหรือไม่ในการทำความสะอาด มันไม่ควรจะเป็น แต่บางครั้งก็เป็น และอีกครั้งเพียงเพราะคุณควรใช้ตัวชี้สมาร์ทเสมอไม่ได้หมายความว่าตัวเลือกที่จะไม่หยุดอยู่ สำหรับผู้เริ่มต้นโดยเฉพาะนี่เป็นข้อผิดพลาดที่สำคัญ
Telastyn

2
@Telastyn: การใช้งานauto_ptrนั้นง่ายพอ ๆ กับการใช้งานIEnumerableหรือรู้จักที่จะใช้อินเตอร์เฟสหรือไม่ใช้เลขทศนิยมสำหรับสกุลเงิน มันเป็นแอพพลิเคชั่นพื้นฐานของ DRY ไม่มีใครรู้พื้นฐานของวิธีการโปรแกรมที่จะทำผิดพลาด usingแตกต่าง ปัญหาที่เกิดขึ้นusingคือคุณต้องรู้สำหรับทุก ๆ ชั้นว่าจะทิ้งหรือไม่ (และฉันหวังว่าจะไม่มีการเปลี่ยนแปลง) และถ้าไม่ใช่ทิ้งคุณจะต้องห้ามคลาสที่ได้รับทั้งหมดโดยอัตโนมัติ
DeadMG

2
kevin: เอ่อคำตอบของคุณไม่สมเหตุสมผล ไม่ใช่ความผิดของ C # ที่คุณทำผิด คุณจะไม่ได้ขึ้นอยู่กับ finalizers เป็นลายลักษณ์อักษรอย่างถูกต้องรหัส หากคุณมีเขตข้อมูลที่มีDisposeวิธีการคุณต้องดำเนินการIDisposable(วิธีที่ 'เหมาะสม') หากคลาสของคุณทำเช่นนั้น (ซึ่งเทียบเท่ากับการใช้ RAII สำหรับคลาสของคุณใน C ++) และคุณใช้using(ซึ่งเหมือนกับตัวชี้สมาร์ทใน C ++) มันทำงานได้อย่างสมบูรณ์แบบ Finalizer ส่วนใหญ่มีไว้เพื่อป้องกันอุบัติเหตุ - Disposeมีหน้าที่รับผิดชอบต่อความถูกต้องและหากคุณไม่ได้ใช้งานนั่นเป็นความผิดของคุณไม่ใช่ของ C #
user541686

0

ใช่ 100% ใช่ฉันคิดว่ามันเป็นไปไม่ได้ที่จะเพิ่มหน่วยความจำและใช้ใน C # (สมมติว่ามีการจัดการและคุณจะไม่เข้าสู่โหมดที่ไม่ปลอดภัย)

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

สำหรับการพิมพ์ผิดความผิดพลาดโง่ ๆ และอื่น ๆ (เช่น: if (i=0)bc คีย์ค้างเมื่อคุณกด == เร็วมาก) คอมไพเลอร์บ่นว่าอะไรดีเพราะมันปรับปรุงคุณภาพของรหัส ตัวอย่างอื่น ๆ กำลังลืมbreakในคำสั่ง switch และไม่อนุญาตให้คุณประกาศตัวแปรสแตติกในฟังก์ชัน (ซึ่งบางครั้งฉันไม่ชอบ แต่เป็นไอเดียที่ดี)


4
Java และ C # ทำให้=/ ==ปัญหาแย่ลงโดยใช้==เพื่อความเท่าเทียมในการอ้างอิงและแนะนำ.equalsสำหรับความเท่าเทียมกันของคุณค่า ตอนนี้โปรแกรมเมอร์ผู้น่าสงสารต้องคอยติดตามว่าตัวแปรเป็น 'double' หรือ 'Double' และต้องแน่ใจว่าได้เรียกตัวแปรที่ถูกต้อง
วินไคลน์

@kevincline +1 แต่ใน C # structคุณสามารถ==ทำงานได้ดีอย่างไม่น่าเชื่อเพราะส่วนใหญ่เวลาจะมีเพียงสตริง ints และลอย (เช่นสมาชิก struct เท่านั้น) ในรหัสของฉันฉันไม่เคยได้รับปัญหานั้นยกเว้นเมื่อฉันต้องการเปรียบเทียบอาร์เรย์ ฉันไม่คิดว่าฉันเคยเปรียบเทียบรายการหรือประเภทที่ไม่ใช่ struct (สตริง, int, float, DateTime, KeyValuePair และอื่น ๆ อีกมากมาย)

2
Python ทำให้ถูกต้องโดยใช้==เพื่อความเท่าเทียมกันของค่าและisเพื่อความเท่าเทียมในการอ้างอิง
dan04

@ dan04 - คุณคิดว่า C # มีความเท่าเทียมกันกี่ประเภท ดูการพูดสายฟ้าแลบของ ACCU ที่ยอดเยี่ยม: วัตถุบางอย่างมีความเท่าเทียมกันมากกว่าวัตถุอื่น
มาร์คบูธ
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.