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: การลบอาร์เรย์ด้วย "ลบ" หรือใช้ตัวชี้อัจฉริยะ
การสร้างอาร์เรย์ชั่วคราวของขนาดไดนามิกมักมีความจำเป็น หลังจากที่ไม่จำเป็นต้องใช้อีกต่อไป สิ่งสำคัญคือต้องเพิ่มหน่วยความจำที่จัดสรรไว้ ปัญหาใหญ่ที่นี่คือ 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 อย่างไม่ถูกต้อง สี่สิ่งที่สำคัญมากที่ต้องจำเกี่ยวกับพวกเขาคือ:
ห้ามใช้ auto_ptr ภายในคอนเทนเนอร์ STL การคัดลอกคอนเทนเนอร์จะทำให้คอนเทนเนอร์ต้นทางมีข้อมูลที่ไม่ถูกต้อง อัลกอริธึม STL บางตัวอาจทำให้ "auto_ptr" ใช้งานไม่ได้
ห้ามใช้ auto_ptr เป็นอาร์กิวเมนต์ของฟังก์ชัน เนื่องจากจะนำไปสู่การคัดลอก และปล่อยให้ค่าที่ส่งผ่านไปยังอาร์กิวเมนต์ไม่ถูกต้องหลังจากการเรียกใช้ฟังก์ชัน
หาก auto_ptr ใช้สำหรับสมาชิกของคลาส ตรวจสอบให้แน่ใจว่าได้ทำสำเนาที่ถูกต้องภายในตัวสร้างการคัดลอกและตัวดำเนินการที่ได้รับมอบหมาย หรือไม่อนุญาตให้ดำเนินการเหล่านี้โดยกำหนดให้เป็นแบบส่วนตัว
เมื่อใดก็ตามที่เป็นไปได้ ให้ใช้ตัวชี้อัจฉริยะที่ทันสมัยอื่นแทน 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: ส่งผ่านวัตถุด้วยมูลค่า
คุณอาจรู้ว่าไม่ควรส่งผ่านอ็อบเจ็กต์ด้วยค่าเนื่องจากผลกระทบต่อประสิทธิภาพ หลายคนปล่อยให้เป็นเช่นนั้นเพื่อหลีกเลี่ยงการพิมพ์อักขระพิเศษ หรืออาจคิดว่าจะกลับมาทำการปรับให้เหมาะสมในภายหลัง โดยปกติแล้วจะไม่มีวันเสร็จสิ้น และเป็นผลให้โค้ดและโค้ดที่มีประสิทธิภาพน้อยกว่าซึ่งมีแนวโน้มที่จะเกิดพฤติกรรมที่ไม่คาดคิด:
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 ข้อในการหลีกเลี่ยงปัญหาดังกล่าว:
กำหนดคอนสตรัคเตอร์ด้วยคีย์เวิร์ดที่ชัดเจนเพื่อไม่อนุญาตให้มีการแปลงโดยนัย
แทนที่จะใช้โอเปอเรเตอร์การแปลง ให้ใช้วิธีการสนทนาที่ชัดเจน ต้องใช้การพิมพ์เพิ่มขึ้นอีกเล็กน้อย แต่การอ่านจะสะอาดกว่ามาก และสามารถช่วยหลีกเลี่ยงผลลัพธ์ที่คาดเดาไม่ได้
บทสรุป
C ++ เป็นภาษาที่ทรงพลัง อันที่จริง แอปพลิเคชั่นมากมายที่คุณใช้ทุกวันบนคอมพิวเตอร์ของคุณและกลายเป็นที่รัก อาจสร้างขึ้นโดยใช้ C++ ในฐานะที่เป็นภาษา C++ ให้ความยืดหยุ่นจำนวนมหาศาลแก่นักพัฒนา ผ่านคุณลักษณะที่ซับซ้อนที่สุดบางอย่างที่พบในภาษาโปรแกรมเชิงวัตถุ อย่างไรก็ตาม คุณลักษณะหรือความยืดหยุ่นที่ซับซ้อนเหล่านี้มักจะทำให้เกิดความสับสนและความยุ่งยากสำหรับนักพัฒนาหลายๆ คน หากไม่ได้ใช้อย่างรับผิดชอบ หวังว่ารายการนี้จะช่วยให้คุณเข้าใจว่าข้อผิดพลาดทั่วไปเหล่านี้ส่งผลต่อสิ่งที่คุณทำได้ด้วย C ++ อย่างไร
อ่านเพิ่มเติมในบล็อก Toptal Engineering:
- วิธีเรียนรู้ภาษา C และ C ++: รายการที่ดีที่สุด
- C # กับ C ++: Core คืออะไร?