10 ข้อผิดพลาด C++ ที่พบบ่อยที่สุดที่นักพัฒนาสร้างขึ้น

เผยแพร่แล้ว: 2022-03-11

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

ข้อผิดพลาดทั่วไป #1: การใช้คู่ "ใหม่" และ "ลบ" อย่างไม่ถูกต้อง

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

 void SomeMethod() { ClassA *a = new ClassA; SomeOtherMethod(); // it can throw an exception delete a; }

หากมีข้อยกเว้น ออบเจ็กต์ "a" จะไม่ถูกลบ ตัวอย่างต่อไปนี้แสดงวิธีที่ปลอดภัยและสั้นกว่าในการทำเช่นนั้น ใช้ auto_ptr ซึ่งเลิกใช้แล้วใน C++11 แต่มาตรฐานเก่ายังคงใช้กันอย่างแพร่หลาย สามารถแทนที่ด้วย C++11 unique_ptr หรือ scoped_ptr จาก Boost หากเป็นไปได้

 void SomeMethod() { std::auto_ptr<ClassA> a(new ClassA); // deprecated, please check the text SomeOtherMethod(); // it can throw an exception }

ไม่ว่าจะเกิดอะไรขึ้น หลังจากสร้างอ็อบเจ็กต์ “a” ออบเจ็กต์จะถูกลบทันทีที่การทำงานของโปรแกรมออกจากขอบเขต

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

ข้อผิดพลาดทั่วไป #2: ตัวทำลายเสมือนที่ถูกลืม

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

อย่างไรก็ตาม ในกรณีส่วนใหญ่คลาสสามารถสืบทอดได้แม้ว่าจะไม่ได้ตั้งใจก็ตาม ดังนั้นจึงเป็นแนวทางที่ดีในการเพิ่มตัวทำลายเสมือนเมื่อมีการประกาศคลาส มิฉะนั้น หากคลาสต้องไม่มีฟังก์ชันเสมือนเนื่องจากเหตุผลด้านประสิทธิภาพ จะเป็นแนวปฏิบัติที่ดีที่จะใส่ความคิดเห็นในไฟล์การประกาศคลาสที่ระบุว่าคลาสไม่ควรสืบทอด หนึ่งในตัวเลือกที่ดีที่สุดในการหลีกเลี่ยงปัญหานี้คือการใช้ IDE ที่รองรับการสร้างตัวทำลายเสมือนระหว่างการสร้างคลาส

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

 class MyString : public std::string { ~MyString() { // ... } }; int main() { std::string *s = new MyString(); delete s; // May not invoke the destructor defined in MyString }

เพื่อหลีกเลี่ยงปัญหา C++ ดังกล่าว วิธีที่ปลอดภัยกว่าในการนำคลาส/เทมเพลตกลับมาใช้ใหม่จากไลบรารีมาตรฐานคือการใช้การสืบทอดหรือองค์ประกอบส่วนตัว

ข้อผิดพลาดทั่วไป #3: การลบอาร์เรย์ด้วย "ลบ" หรือใช้ตัวชี้อัจฉริยะ

ข้อผิดพลาดทั่วไป #3

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

การใช้ตัวชี้อัจฉริยะ เช่น auto_ptr, unique_ptr<T>, shared_ptr กับอาร์เรย์ก็ไม่ถูกต้องเช่นกัน เมื่อตัวชี้อัจฉริยะออกจากขอบเขต มันจะเรียกตัวดำเนินการลบโดยไม่มีเครื่องหมายวงเล็บ [] ซึ่งส่งผลให้เกิดปัญหาเดียวกันกับที่อธิบายไว้ข้างต้น หากจำเป็นต้องใช้ตัวชี้อัจฉริยะสำหรับอาร์เรย์ คุณสามารถใช้ scoped_array หรือ shared_array จาก Boost หรือความเชี่ยวชาญเฉพาะด้าน unique_ptr<T[]>

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

ข้อผิดพลาดทั่วไป #4: การส่งคืนวัตถุในเครื่องโดยการอ้างอิง

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

 Complex& SumComplex(const Complex& a, const Complex& b) { Complex result; ….. return result; } Complex& sum = SumComplex(a, b);

ตอนนี้ "ผลรวม" ของวัตถุจะชี้ไปที่ "ผลลัพธ์" ของวัตถุในเครื่อง แต่วัตถุ "ผลลัพธ์" อยู่ที่ไหนหลังจากดำเนินการฟังก์ชัน SumComplex? ไม่มีที่ไหนเลย มันตั้งอยู่บนสแต็ก แต่หลังจากที่ฟังก์ชันส่งคืน สแต็กถูกเปิดออก และอ็อบเจ็กต์ในเครื่องทั้งหมดจากฟังก์ชันถูกทำลาย ในที่สุดสิ่งนี้จะส่งผลให้เกิดพฤติกรรมที่ไม่ได้กำหนดไว้ แม้แต่ในประเภทดั้งเดิม เพื่อหลีกเลี่ยงปัญหาด้านประสิทธิภาพ บางครั้งอาจใช้การเพิ่มประสิทธิภาพมูลค่าส่งกลับ:

 Complex SumComplex(const Complex& a, const Complex& b) { return Complex(a.real + b.real, a.imaginar + b.imaginar); } Complex sum = SumComplex(a, b);

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

ข้อผิดพลาดทั่วไป #5: การใช้การอ้างอิงไปยังทรัพยากรที่ถูกลบ

ปัญหา C++ เหล่านี้เกิดขึ้นบ่อยกว่าที่คุณคิด และมักพบในแอปพลิเคชันแบบมัลติเธรด ให้เราพิจารณารหัสต่อไปนี้:

หัวข้อที่ 1:

 Connection& connection= connections.GetConnection(connectionId); // ...

หัวข้อที่ 2:

 connections.DeleteConnection(connectionId); // …

หัวข้อที่ 1:

 connection.send(data);

ในตัวอย่างนี้ หากทั้งสองเธรดใช้ ID การเชื่อมต่อเดียวกัน จะส่งผลให้เกิดการทำงานที่ไม่ได้กำหนดไว้ ข้อผิดพลาดในการละเมิดการเข้าถึงมักจะหายากมาก

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

ข้อผิดพลาดทั่วไป #6: การยอมให้ข้อยกเว้นออกจากผู้ทำลาย

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

 class A { public: A(){} ~A() { writeToLog(); // could cause an exception to be thrown } }; // … try { A a1; A a2; } catch (std::exception& e) { std::cout << "exception caught"; }

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

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

 try { writeToLog(); // could cause an exception to be thrown } catch (...) {}

ข้อผิดพลาดทั่วไป #7: การใช้ “auto_ptr” (ไม่ถูกต้อง)

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

 auto_ptr<ClassA> a(new ClassA); // deprecated, please check the text auto_ptr<ClassA> b = a; a->SomeMethod(); // will result in access violation error

… จะส่งผลให้เกิดข้อผิดพลาดในการละเมิดการเข้าถึง เฉพาะวัตถุ "b" เท่านั้นที่จะมีตัวชี้ไปยังวัตถุของ Class A ในขณะที่ "a" จะว่างเปล่า การพยายามเข้าถึงสมาชิกคลาสของวัตถุ "a" จะส่งผลให้เกิดข้อผิดพลาดในการละเมิดการเข้าถึง มีหลายวิธีในการใช้ auto_ptr อย่างไม่ถูกต้อง สี่สิ่งที่สำคัญมากที่ต้องจำเกี่ยวกับพวกเขาคือ:

  1. ห้ามใช้ auto_ptr ภายในคอนเทนเนอร์ STL การคัดลอกคอนเทนเนอร์จะทำให้คอนเทนเนอร์ต้นทางมีข้อมูลที่ไม่ถูกต้อง อัลกอริธึม STL บางตัวอาจทำให้ "auto_ptr" ใช้งานไม่ได้

  2. ห้ามใช้ auto_ptr เป็นอาร์กิวเมนต์ของฟังก์ชัน เนื่องจากจะนำไปสู่การคัดลอก และปล่อยให้ค่าที่ส่งผ่านไปยังอาร์กิวเมนต์ไม่ถูกต้องหลังจากการเรียกใช้ฟังก์ชัน

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

  4. เมื่อใดก็ตามที่เป็นไปได้ ให้ใช้ตัวชี้อัจฉริยะที่ทันสมัยอื่นแทน auto_ptr

ข้อผิดพลาดทั่วไป #8: การใช้ตัววนซ้ำและการอ้างอิงที่ไม่ถูกต้อง

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

 vector<string> v; v.push_back(“string1”); string& s1 = v[0]; // assign a reference to the 1st element vector<string>::iterator iter = v.begin(); // assign an iterator to the 1st element v.push_back(“string2”); cout << s1; // access to a reference of the 1st element cout << *iter; // access to an iterator of the 1st element

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

ข้อผิดพลาดทั่วไป #9: ส่งผ่านวัตถุด้วยมูลค่า

ข้อผิดพลาดทั่วไป #9

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

 class A { public: virtual std::string GetName() const {return "A";} … }; class B: public A { public: virtual std::string GetName() const {return "B";} ... }; void func1(A a) { std::string name = a.GetName(); ... } B b; func1(b);

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

ปัญหาที่คล้ายกันเกิดขึ้นเมื่อพยายามตรวจจับข้อยกเว้น ตัวอย่างเช่น:

 class ExceptionA: public std::exception; class ExceptionB: public ExceptionA; try { func2(); // can throw an ExceptionB exception } catch (ExceptionA ex) { writeToLog(ex.GetDescription()); throw; }

เมื่อข้อยกเว้นของประเภท ExceptionB ถูกส่งออกจากฟังก์ชัน "func2" ฟังก์ชันจะถูกดักจับโดย catch block แต่เนื่องจากปัญหาการแบ่งส่วน เฉพาะส่วนหนึ่งจากคลาส ExceptionA เท่านั้นที่จะถูกคัดลอก เมธอดที่ไม่ถูกต้องจะถูกเรียกและโยนใหม่ จะโยนข้อยกเว้นที่ไม่ถูกต้องไปยังบล็อก try-catch ภายนอก

ในการสรุป ให้ส่งผ่านวัตถุด้วยการอ้างอิงเสมอ ไม่ใช่โดยค่า

ข้อผิดพลาดทั่วไป #10: การใช้คอนเวอร์ชั่นที่ผู้ใช้กำหนดโดยคอนสตรัคเตอร์และโอเปอเรเตอร์การแปลง

แม้แต่ Conversion ที่กำหนดโดยผู้ใช้ก็มีประโยชน์มากในบางครั้ง แต่ก็สามารถนำไปสู่ ​​Conversion ที่ไม่คาดคิดซึ่งหาได้ยาก สมมติว่ามีคนสร้างไลบรารีที่มีคลาสสตริง:

 class String { public: String(int n); String(const char *s); …. }

วิธีแรกมีจุดมุ่งหมายเพื่อสร้างสตริงที่มีความยาว n และวิธีที่สองมีจุดมุ่งหมายเพื่อสร้างสตริงที่มีอักขระที่กำหนด แต่ปัญหาจะเริ่มต้นทันทีที่คุณมีสิ่งเช่นนี้:

 String s1 = 123; String s2 = 'abc';

ในตัวอย่างข้างต้น s1 จะกลายเป็นสตริงขนาด 123 ไม่ใช่สตริงที่มีอักขระ "123" ตัวอย่างที่สองประกอบด้วยเครื่องหมายอัญประกาศเดี่ยวแทนที่จะเป็นอัญประกาศคู่ (ซึ่งอาจเกิดขึ้นโดยบังเอิญ) ซึ่งจะส่งผลให้มีการเรียกคอนสตรัคเตอร์ตัวแรกและสร้างสตริงที่มีขนาดที่ใหญ่มาก นี่เป็นตัวอย่างง่ายๆ และมีหลายกรณีที่ซับซ้อนมากขึ้นซึ่งนำไปสู่ความสับสนและการแปลงที่ไม่คาดคิดซึ่งหายากมาก มีกฎทั่วไป 2 ข้อในการหลีกเลี่ยงปัญหาดังกล่าว:

  1. กำหนดคอนสตรัคเตอร์ด้วยคีย์เวิร์ดที่ชัดเจนเพื่อไม่อนุญาตให้มีการแปลงโดยนัย

  2. แทนที่จะใช้โอเปอเรเตอร์การแปลง ให้ใช้วิธีการสนทนาที่ชัดเจน ต้องใช้การพิมพ์เพิ่มขึ้นอีกเล็กน้อย แต่การอ่านจะสะอาดกว่ามาก และสามารถช่วยหลีกเลี่ยงผลลัพธ์ที่คาดเดาไม่ได้

บทสรุป

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


อ่านเพิ่มเติมในบล็อก Toptal Engineering:

  • วิธีเรียนรู้ภาษา C และ C ++: รายการที่ดีที่สุด
  • C # กับ C ++: Core คืออะไร?