Ch18: Inheritance¶
TL;DR: Inheritance & Access Modes: Class Inheritance (
class Derived : public Base) จะฝังออบเจกต์ของคลาสฐานไว้ในคลาสที่สืบทอด โดยโหมดการสืบทอด (public,protected,private) จะคอยกรองสิทธิ์ในการเข้าถึงสมาชิกไล่ลงไปด้านล่าง
⚡ Quick Reference¶
#include <iostream>
#include <memory>
#include <utility>
class Base {
protected:
int m_id {0};
public:
Base() = default;
Base(int id) : m_id(id) {}
virtual void print() const { std::cout << "Base: " << m_id << std::endl; }
};
class Derived : public Base {
public:
using Base::Base;
int m_id = 999;
void print() const override {
std::cout << "Derived: " << m_id << std::endl;
std::cout << "Base's hidden ID: " << Base::m_id << std::endl;
}
};
class Person {
public:
Person() = default;
void add() {}
};
class Engineer : private Person {
public:
using Person::add;
};
struct Point {
int x, y;
Point operator+(const Point& right) const {
return Point{x + right.x, y + right.y};
}
Point& operator=(const Point& right) {
if (this != &right) {
x = right.x;
y = right.y;
}
return *this;
}
bool operator==(const Point& other) const {
return x == other.x && y == other.y;
}
auto operator<=>(const Point&) const = default;
};
int main() {
std::unique_ptr<Base> up = std::make_unique<Derived>(10);
std::unique_ptr<Base> up2 = std::move(up);
up2.reset();
std::shared_ptr<Base> sp = std::make_shared<Base>(2);
std::weak_ptr<Base> wp = sp;
return 0;
}
🧠 Core Concepts¶
- Inheritance & Access Modes: Class Inheritance (
class Derived : public Base) จะฝังออบเจกต์ของคลาสฐานไว้ในคลาสที่สืบทอด โดยโหมดการสืบทอด (public,protected,private) จะคอยกรองสิทธิ์ในการเข้าถึงสมาชิกไล่ลงไปด้านล่าง - RAII & Smart Pointers: C++ ควบคุมอายุขัยของ Heap อัตโนมัติด้วย Smart Pointer ทั้งแบบสิทธิ์ผูกขาด
std::unique_ptr(ย้ายได้อย่างเดียวห้าม Copy) และแบบนับจำนวนอ้างอิงstd::shared_ptrโดยใช้std::weak_ptrเพื่อแก้ปัญหาชี้วนลูป - Operator Overloading: เราสามารถเขียนทับตัวดำเนินการ (Operator Overloading) ให้กับชนิดข้อมูลสร้างเองได้ทั้งในแบบสมาชิกและภายนอก โดยใน C++20 มีการเพิ่มยานอวกาศโอเปอเรเตอร์ (
<=>) เพื่อช่วยสร้างเปรียบเทียบทุกคู่โดยอัตโนมัติ
⚠️ Pitfalls (Quick Scan)¶
| ข้อผิดพลาด | วิธีแก้ |
|---|---|
| การพยายามเข้าถึงสมาชิกระดับ private ของคลาสฐานจากคลาสลูก | ดึงข้อมูลผ่าน Getter สาธารณะ หรือเปลี่ยนคุณสมบัติคลาสฐานเป็น protected |
| การสืบทอดคลาสในโหมด private ส่งต่อไปยังระดับล่าง | ใช้การสืบทอดแบบ public/protected หรือใช้คำสั่ง using เปิดช่องทางสมาชิกเฉพาะจุด |
| กำหนดค่าเริ่มต้นให้สมาชิกคลาสฐานในเนื้อหา (Body) ของ Constructor คลาสลูก | ประกาศเริ่มต้นคลาสฐานในรูปแบบ Initializer List เสมอ: Derived() : Base(args) |
| ไม่ได้เรียกใช้งาน Base Copy Constructor ใน Custom Derived Copy Constructor | ใส่คำสั่งเรียก Base Copy Constructor ในรายการตั้งค่าเริ่มต้นเสมอ: Derived(const Derived& src) : Base(src) |
สร้าง smart pointer ประเภท shared_ptr ชี้อ้างอิงวนหากันระหว่างวัตถุ |
ปรับตัวแปรฝั่งใดฝั่งหนึ่งให้ชี้หาคู่แบบสังเกตการณ์ผ่าน std::weak_ptr |
พยายาม Copy ตัวแปร smart pointer ประเภท unique_ptr |
ใช้คำสั่ง std::move เพื่อโยกย้ายความเป็นเจ้าของแทนการทำสำเนา |
| สร้างตัวแปรในคลาสลูกชื่อซ้ำกับคลาสแม่ (Name Hiding) | ระบุขอบเขตเรียกใช้สัญลักษณ์คลาสแม่ตรงๆ: obj.Base::member |
| เขียนโอเปอเรเตอร์กำหนดค่า (=) โดยลืมตรวจเช็คเงื่อนไขการโคลนตัวเอง (Self-assignment) | เพิ่มเงื่อนไขการตรวจสอบ if (this != &right_operand) เป็นบรรทัดแรกเสมอ |
ลืมส่งค่ากลับเป็น this ในฟังก์ชันโอเปอเรเตอร์กำหนดค่า (=) |
กำหนดประเภทขาส่งกลับเป็น Reference ของคลาสตัวเอง (T&) และสั่งคืนค่า *this เสมอ |
เขียนโอเปอเรเตอร์เปรียบเทียบซ้อนกันจนตีกับเนมสเปซ std::rel_ops |
เลือกประกาศตัวเปรียบเทียบให้ครบด้วยตนเอง หรือหันไปใช้ Spaceship Operator ของ C++20 แทน |
ระบุขาส่งกลับแบบคงระดับเข้มข้น std::strong_ordering ในการเปรียบเทียบค่าเลขทศนิยม float/double |
ใช้ประเภท std::partial_ordering ในการประเมินเทียบระดับข้อความประเภททศนิยม |
| คาดหวังให้คลาสลูกสืบทอด Constructor ของคลาสฐานมาให้โดยอัตโนมัติ | ใช้คำสั่ง using Base::Base; ในการดึง Constructor ของคลาสฐานเข้ามาใช้งาน |
เข้าใจผิดว่าคำสั่ง using Base::Base; จะสืบทอด Copy/Move Constructor มาด้วย |
เขียน Copy และ Move Constructor แบบเจาะจงขึ้นมาเองในคลาสลูกหากจำเป็น |
📖 Full Details¶
Cause → Effect → Fix พร้อม timestamp (คลิกเพื่อดู)
* **การพยายามเข้าถึงสมาชิกระดับ private ของคลาสฐานจากคลาสลูก** -> **เกิด Compile-time error บ่งชี้สมาชิกเป็น private** -> **ดึงข้อมูลผ่าน Getter สาธารณะ หรือเปลี่ยนคุณสมบัติคลาสฐานเป็น `protected` (~08:30)** * **การสืบทอดคลาสในโหมด private ส่งต่อไปยังระดับล่าง** -> **สมาชิกทั้งหมดของคลาสต้นกำเนิดจะถูกปิดกั้นการเข้าถึงในคลาสสืบทอดระดับถัดไป** -> **ใช้การสืบทอดแบบ public/protected หรือใช้คำสั่ง `using` เปิดช่องทางสมาชิกเฉพาะจุด (~28:00)** * **กำหนดค่าเริ่มต้นให้สมาชิกคลาสฐานในเนื้อหา (Body) ของ Constructor คลาสลูก** -> **เกิด Compile-time error เนื่องจากคลาสฐานจำเป็นต้องถูกประเมินค่าก่อนลูปคลาสลูกทำงาน** -> **ประกาศเริ่มต้นคลาสฐานในรูปแบบ Initializer List เสมอ: `Derived() : Base(args)` (~03:00)** * **ไม่ได้เรียกใช้งาน Base Copy Constructor ใน Custom Derived Copy Constructor** -> **Compiler จะลอบเรียกใช้ Default Constructor ของคลาสฐานแทน ซึ่งอาจได้ข้อมูลคลาสฐานไม่ครบ** -> **ใส่คำสั่งเรียก Base Copy Constructor ในรายการตั้งค่าเริ่มต้นเสมอ: `Derived(const Derived& src) : Base(src)` (~04:00)** * **สร้าง smart pointer ประเภท `shared_ptr` ชี้อ้างอิงวนหากันระหว่างวัตถุ** -> **ตัวเลขนับอ้างอิงไม่มีทางเป็นศูนย์ ทำให้เกิดปัญหาหน่วยความจำรั่วไหล (Memory Leak)** -> **ปรับตัวแปรฝั่งใดฝั่งหนึ่งให้ชี้หาคู่แบบสังเกตการณ์ผ่าน `std::weak_ptr` (~02:00)** * **พยายาม Copy ตัวแปร smart pointer ประเภท `unique_ptr`** -> **เกิด Compile-time error เนื่องจากคลาสบล็อกฟังก์ชัน Copy เพื่อความเป็นเจ้าของรายเดียว** -> **ใช้คำสั่ง `std::move` เพื่อโยกย้ายความเป็นเจ้าของแทนการทำสำเนา (~07:00)** * **สร้างตัวแปรในคลาสลูกชื่อซ้ำกับคลาสแม่ (Name Hiding)** -> **คำสั่งเรียกใช้งานสมาชิกจะวิ่งหาคลาสลูกเป็นหลัก บดบังอาร์กิวเมนต์ตัวแปรคลาสแม่** -> **ระบุขอบเขตเรียกใช้สัญลักษณ์คลาสแม่ตรงๆ: `obj.Base::member` (~02:00)** * **เขียนโอเปอเรเตอร์กำหนดค่า (=) โดยลืมตรวจเช็คเงื่อนไขการโคลนตัวเอง (Self-assignment)** -> **การล้างหน่วยความจำบนวัตถุเดียวกันจะลบล้างค่าต้นฉบับจนโปรแกรมแครช** -> **เพิ่มเงื่อนไขการตรวจสอบ `if (this != &right_operand)` เป็นบรรทัดแรกเสมอ (~02:00)** * **ลืมส่งค่ากลับเป็น `*this` ในฟังก์ชันโอเปอเรเตอร์กำหนดค่า (=)** -> **ไม่สามารถเรียกใช้คำสั่งคัดลอกข้อมูลแบบต่อเนื่อง เช่น `a = b = c` ได้** -> **กำหนดประเภทขาส่งกลับเป็น Reference ของคลาสตัวเอง (`T&`) และสั่งคืนค่า `*this` เสมอ (~03:00)** * **เขียนโอเปอเรเตอร์เปรียบเทียบซ้อนกันจนตีกับเนมสเปซ `std::rel_ops`** -> **เกิดข้อผิดพลาดกำกวมเนื่องจาก Compiler สับสนระหว่างเวอร์ชันเขียนเองกับเวอร์ชันเจเนอเรต** -> **เลือกประกาศตัวเปรียบเทียบให้ครบด้วยตนเอง หรือหันไปใช้ Spaceship Operator ของ C++20 แทน (~04:00)** * **ระบุขาส่งกลับแบบคงระดับเข้มข้น `std::strong_ordering` ในการเปรียบเทียบค่าเลขทศนิยม float/double** -> **เกิด Compile-time error เนื่องจากทศนิยมมีสัญลักษณ์ NaN ที่ไม่สามารถจัดลำดับที่ชัดเจนได้** -> **ใช้ประเภท `std::partial_ordering` ในการประเมินเทียบระดับข้อความประเภททศนิยม (~05:00)** * **คาดหวังให้คลาสลูกสืบทอด Constructor ของคลาสฐานมาให้โดยอัตโนมัติ** -> **เกิด Compile-time error เมื่อสร้างออบเจกต์ เนื่องจาก Constructor ไม่ได้สืบทอดกันตามค่าเริ่มต้น** -> **ใช้คำสั่ง `using Base::Base;` ในการดึง Constructor ของคลาสฐานเข้ามาใช้งาน (~04:00)** * **เข้าใจผิดว่าคำสั่ง `using Base::Base;` จะสืบทอด Copy/Move Constructor มาด้วย** -> **Copy และ Move Constructor จะถูกยกเว้นการดึงสิทธิ์ ทำให้ได้ระบบ default โคลนอัตโนมัติแทน** -> **เขียน Copy และ Move Constructor แบบเจาะจงขึ้นมาเองในคลาสลูกหากจำเป็น (~04:00)**📎 Repo Files¶
33.SmartPointers/33.2UniquePointers/main.cpp33.SmartPointers/33.5SharedPointers/main.cpp33.SmartPointers/33.9WeakPointers/main.cpp34.OperatorOverloading/34.2AdditionOperatorAsMember/main.cpp34.OperatorOverloading/34.7StreamInsertionOperator/main.cpp34.OperatorOverloading/34.17CopyAssignmentOperator/main.cpp35.LogicalOperatorsAndThreeWayComparison/35.2AllLogicalOperators/main.cpp35.LogicalOperatorsAndThreeWayComparison/35.3Rel_OpsNamespace/main.cpp35.LogicalOperatorsAndThreeWayComparison/35.5ThreeWayComparisonOperator/main.cpp35.LogicalOperatorsAndThreeWayComparison/35.7CustomEqualityOperator/main.cpp35.LogicalOperatorsAndThreeWayComparison/35.8DefaultOrderingWithSpaceship/main.cpp35.LogicalOperatorsAndThreeWayComparison/35.10CustomSpaceshipOperatorForOrdering/main.cpp36.Inheritance/36.2FirstTryOnInheritance/main.cpp36.Inheritance/36.3ProtectedMembers/main.cpp36.Inheritance/36.5BaseClassAccessSpecifiersADemo/main.cpp36.Inheritance/36.6ClosingInOnPrivateInheritance/main.cpp36.Inheritance/36.7ResurectingMembersBackInContext/main.cpp36.Inheritance/36.8DefaultArgConstructorsWithInheritance/main.cpp36.Inheritance/36.9ConstructorsWithInheritance/main.cpp36.Inheritance/36.10CopyConstructorsWithInheritance/main.cpp36.Inheritance/36.11InheritingBaseConstructors/main.cpp36.Inheritance/36.12InheritanceAndDestructors/main.cpp36.Inheritance/36.13ReusedSymbolsInInheritance/main.cpp