ข้ามไปที่เนื้อหา

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.cpp
  • 33.SmartPointers/33.5SharedPointers/main.cpp
  • 33.SmartPointers/33.9WeakPointers/main.cpp
  • 34.OperatorOverloading/34.2AdditionOperatorAsMember/main.cpp
  • 34.OperatorOverloading/34.7StreamInsertionOperator/main.cpp
  • 34.OperatorOverloading/34.17CopyAssignmentOperator/main.cpp
  • 35.LogicalOperatorsAndThreeWayComparison/35.2AllLogicalOperators/main.cpp
  • 35.LogicalOperatorsAndThreeWayComparison/35.3Rel_OpsNamespace/main.cpp
  • 35.LogicalOperatorsAndThreeWayComparison/35.5ThreeWayComparisonOperator/main.cpp
  • 35.LogicalOperatorsAndThreeWayComparison/35.7CustomEqualityOperator/main.cpp
  • 35.LogicalOperatorsAndThreeWayComparison/35.8DefaultOrderingWithSpaceship/main.cpp
  • 35.LogicalOperatorsAndThreeWayComparison/35.10CustomSpaceshipOperatorForOrdering/main.cpp
  • 36.Inheritance/36.2FirstTryOnInheritance/main.cpp
  • 36.Inheritance/36.3ProtectedMembers/main.cpp
  • 36.Inheritance/36.5BaseClassAccessSpecifiersADemo/main.cpp
  • 36.Inheritance/36.6ClosingInOnPrivateInheritance/main.cpp
  • 36.Inheritance/36.7ResurectingMembersBackInContext/main.cpp
  • 36.Inheritance/36.8DefaultArgConstructorsWithInheritance/main.cpp
  • 36.Inheritance/36.9ConstructorsWithInheritance/main.cpp
  • 36.Inheritance/36.10CopyConstructorsWithInheritance/main.cpp
  • 36.Inheritance/36.11InheritingBaseConstructors/main.cpp
  • 36.Inheritance/36.12InheritanceAndDestructors/main.cpp
  • 36.Inheritance/36.13ReusedSymbolsInInheritance/main.cpp