0Pricing
C++ Academy · บทเรียน

สำนวน CRTP

รูปแบบเทมเพลตแบบวนกลับอย่างน่าพิศวง

สำนวน CRTP เป็นบทเรียน C++ Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน C++ Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส C++ Academy มีบทเรียนทั้งหมด 4 บทเรียน

CRTP คืออะไร

รูปแบบแม่แบบที่เกิดซ้ำอย่างน่าประหลาด (CRTP) เป็นสำนวนการเขียน C++ ที่คลาส Derived สืบทอดจากคลาสพื้นฐานแม่แบบที่สร้างอินสแตนซ์ด้วยตัวมันเองซึ่งก็คือ Derived

  • รูปแบบ: class Derived : public Base<Derived>
  • คลาสพื้นฐานจะทราบชนิดที่สืบทอดขณะคอมไพล์
template <typename T>
class Base {};

class Derived : public Base<Derived> {};

กลไกหลัก

ภายในคลาสพื้นฐาน คุณสามารถใช้ static_cast แปลงพอยน์เตอร์ this เป็นชนิดที่สืบทอดได้ เนื่องจากชนิดที่สืบทอดเป็นพารามิเตอร์แม่แบบ การแปลงนี้จึงเกิดขึ้นทั้งหมดขณะคอมไพล์และไม่มีต้นทุนขณะทำงาน

#include <iostream>

template <typename T>
class Base {
public:
    void interface() {
        static_cast<T*>(this)->implementation();
    }
};

class Derived : public Base<Derived> {
public:
    void implementation() { std::cout << "Derived impl\n"; }
};

int main() {
    Derived d;
    d.interface();
    return 0;
}

ทำไมจึงเรียกว่า "เกิดซ้ำอย่างน่าประหลาด"

ชื่อนี้มาจากความขัดแย้งที่ดูเหมือนจะเกิดขึ้น นั่นคือคลาสพื้นฐานอ้างถึงคลาสที่สืบทอด ทั้งที่คลาสนั้นยังไม่ได้ถูกกำหนดอย่างสมบูรณ์เมื่อเขียนแม่แบบคลาสพื้นฐาน

  • รูปแบบนี้ทำงานได้เพราะแม่แบบจะถูกสร้างอินสแตนซ์เมื่อมีการใช้งานเท่านั้น
  • เมื่อถึงเวลาสร้างอินสแตนซ์ Derived จะเป็นชนิดที่สมบูรณ์แล้ว

การใช้งานจริงครั้งแรก

CRTP ช่วยให้คลาสพื้นฐานจัดเตรียมฟังก์ชันที่นำกลับมาใช้ใหม่ได้ โดยเรียกกลับไปยังคลาสที่สืบทอด ในที่นี้คลาสพื้นฐานจัดเตรียม print() ซึ่งส่งต่อการทำงานไปยัง name() ของคลาสที่สืบทอด

#include <iostream>
#include <string>

template <typename T>
struct Named {
    void print() {
        std::cout << static_cast<T*>(this)->name() << "\n";
    }
};

struct Cat : Named<Cat> {
    std::string name() { return "Cat"; }
};

int main() {
    Cat c;
    c.print();
    return 0;
}

ไม่มีตารางเสมือน

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

  • ออบเจกต์มีขนาดเล็กลง (ไม่มี vptr ที่ซ่อนอยู่)
  • การเรียกสามารถแทรกโค้ดได้

การนับอินสแตนซ์

การประยุกต์ใช้ CRTP แบบคลาสสิกคือการนับออบเจกต์ คลาสที่สืบทอดแต่ละคลาสจะได้รับตัวนับแบบสแตติกของตนเองโดยอัตโนมัติ

#include <iostream>

template <typename T>
struct Counter {
    static inline int count = 0;
    Counter() { ++count; }
    ~Counter() { --count; }
};

struct Widget : Counter<Widget> {};

int main() {
    Widget a, b;
    std::cout << Widget::count << "\n";
    return 0;
}

คลาสที่สืบทอดแต่ละคลาสมีสถานะของตนเอง

เนื่องจาก Counter<Widget> และ Counter<Gadget> เป็นอินสแตนซ์ของแม่แบบที่แตกต่างกัน แต่ละรายการจึงมี count แบบสแตติกที่เป็นอิสระต่อกัน การแยกเช่นนี้ไม่สามารถทำได้ด้วยคลาสพื้นฐานที่ไม่ใช่แม่แบบเพียงคลาสเดียว

#include <iostream>

template <typename T>
struct Counter {
    static inline int count = 0;
    Counter() { ++count; }
};

struct A : Counter<A> {};
struct B : Counter<B> {};

int main() {
    A a1, a2;
    B b1;
    std::cout << A::count << " " << B::count << "\n";
    return 0;
}

static_cast ปลอดภัยในกรณีนี้

การแปลงชนิดลงภายในคลาสพื้นฐานจะปลอดภัยก็ต่อเมื่อออบเจกต์ทุกตัวของชนิด Base<T> เป็น T จริง ๆ เท่านั้น CRTP รับประกันสิ่งนี้ตั้งแต่โครงสร้าง เพราะ T คือคลาสที่สืบทอด

  • อย่าสร้างอินสแตนซ์ Base<Wrong> ให้กับคลาสที่ไม่เกี่ยวข้อง

ตัวดำเนินการเปรียบเทียบผ่าน CRTP

คุณสามารถสร้างชุดตัวดำเนินการเปรียบเทียบครบชุดจาก operator< เพียงรายการเดียว โดยสืบทอดจากตัวช่วยด้านการเปรียบเทียบ

#include <iostream>

template <typename T>
struct Comparable {
    friend bool operator>(const T& a, const T& b) { return b < a; }
    friend bool operator==(const T& a, const T& b) { return !(a < b) && !(b < a); }
};

struct Num : Comparable<Num> {
    int v;
    Num(int x) : v(x) {}
    friend bool operator<(const Num& a, const Num& b) { return a.v < b.v; }
};

int main() {
    std::cout << (Num(3) > Num(2)) << "\n";
    return 0;
}

การป้องกันด้วยตัวสร้างแบบ protected

เพื่อป้องกันการใช้คลาสพื้นฐานผิดโดยไม่ตั้งใจ ให้คลาสพื้นฐานมีตัวสร้างแบบ protected เพื่อให้มีเพียงคลาสที่สืบทอดเท่านั้นที่สร้างคลาสนี้ได้

#include <iostream>

template <typename T>
class Base {
protected:
    Base() = default;
public:
    void run() { static_cast<T*>(this)->step(); }
};

class Job : public Base<Job> {
public:
    void step() { std::cout << "step\n"; }
};

int main() {
    Job j;
    j.run();
    return 0;
}

CRTP ในไลบรารีมาตรฐาน

พบ CRTP อยู่ทั่วทั้งไลบรารี C++ ที่ใช้งานจริง

  • std::enable_shared_from_this<T> ใช้รูปแบบนี้
  • ไลบรารีคณิตศาสตร์ที่ใช้แม่แบบนิพจน์จำนวนมาก (Eigen) อาศัยรูปแบบนี้
  • ตัวช่วยสำหรับช่วงและตัววนซ้ำก็ใช้รูปแบบที่คล้ายกัน

การจดจำรูปแบบ X : Base<X> ช่วยให้คุณอ่านโค้ดขั้นสูงได้

ตรวจสอบความเข้าใจอย่างรวดเร็ว

ทดสอบความเข้าใจของคุณเกี่ยวกับรูปแบบ CRTP

สรุปทบทวน

คุณได้เรียนรู้สำนวนการเขียน CRTP

  • Derived : Base<Derived> ทำให้คลาสพื้นฐานทราบชนิดรูปธรรมของตน
  • static_cast<T*>(this) ช่วยให้เลือกเรียกขณะคอมไพล์
  • ไม่มีตารางเสมือน ออบเจกต์มีขนาดเล็กลง และการเรียกสามารถแทรกโค้ดได้
  • ใช้กับตัวนับ ส่วนผสมสำหรับการเปรียบเทียบ และตัวช่วยในไลบรารี

คำถามที่พบบ่อย

บทเรียน “สำนวน CRTP” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “สำนวน CRTP” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส C++ Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส C++ Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “สำนวน CRTP”

รูปแบบเทมเพลตแบบวนกลับอย่างน่าพิศวง คุณปฏิบัติ C++ Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน C++ Academy หรือไม่

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

บทเรียน “สำนวน CRTP” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน C++ Academy นี้ได้ไหม

ได้ บทเรียน C++ Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. สำนวน CRTP
  2. พหุรูปแบบแบบคงที่
  3. มิกซินด้วย CRTP
  4. เมื่อใดควรใช้ CRTP
← กลับไปที่ C++ Academy