[C++] Variadic Template กับการสร้างวัตถุที่มี constructor parameter ที่แตกต่างกัน

ในเอนจินที่ผมพัฒนาอยู่เนี่ย มันจะมีการทำ command list โดยอารมณ์มันจะเหมือนกับ virtual machine ง่าย ๆ ตัวนึงครับ โดยผมเขียนคลาสขึ้นมาตัวนึงคือ Command มีหน้าตาแบบนี้

class Command
{
public:
    virtual void Execute(const Scene* &pScene) = 0;
};

และก็มีคลาสที่เป็น Command อื่น ๆ อีกมากมาย (ตอนนี้มีประมาณ 10 คลาสแล้วครับ และไม่มีทีท่าว่าจะลด 555) ที่สำคัญคือแต่ละคลาสนั้นมี constructor ที่หน้าตาไม่เหมือนกันเลย เช่น ผมมี

class MessageCommand : public Command
{
public:
    MessageCommand() = delete;
    MessageCommand(const std::string& text);
}

class ShowDrawableCommand : public Command
{
    ShowDrawableCommand() = delete;
    ShowDrawableCommand(const pgengine::drawable& object);
}

ในคลาส Scene ผมจะมี command list อยู่ตัวนึง ซึ่งเป็น std::list ของ pointer ที่ชี้ไปยังคลาส Command ด้วยเหตุผลที่ว่าผมใช้หลัก poloymorphism ในการออกแบบคลาสชุดนี้ เมื่อก่อนถ้าเกิดผมออกแบบคลาสชุดนี้ผมก็คงจะเขียนฟังก์ชั่น AddCommand() โดยรับพารามิเตอร์เป็น Command* ซึ่งก็ง่ายดี แบบนี้

void AddCommand(const Command*& command)
{
    commandList.push_back(command);
}

แต่วิธีนี้ก็สร้างปัญหาเกี่ยวกับการจัดการ lifetime ของวัตถุ และความเป็นเจ้าของ (ownership - โดยหลัก ๆ ก็ใครจะเป็นคนทำลายวัตถุนั้นๆ) ซึ่งก็กลายเป็นเรื่องวุ่นวายขึ้นมาได้อีก

หลังจากนั้นไม่นาน ผมออกแบบฟังก์ชั่นนี้ใหม่โดยการแยกฟังก์ชั่นสำหรับแต่ละ Command ไปเลย กลายเป็นว่าตัว Scene นั้นจะมีหนึ่งฟังก์ชั่นสำหรับแต่ละคลาส ... แต่ก็อย่างที่เห็นข้างบนว่า ตอนนี้ผมมี 10 คลาสเข้าไปแล้ว และมันจะบวมขึ้นไปเรื่อย ๆ ดังนั้นวิธีนี้ไม่เวิร์ค

และที่สำคัญคือ เราไม่สามารถใช้ Template ธรรมดามาช่วยตรงนี้ เพราะว่าในภาษา C++98 นั้นระบุว่าต้องระบุว่า template class/function นั้น ๆ จะมีกี่ type ก็คือ ต่อให้เขียนฟังก์ชั่นที่รับ type ที่แตกต่างกันในแต่ละพารามิเตอร์ได้ ก็ต้องระบุอยู่ดีว่าจะเป็น type อะไร

แต่พอดีว่าเราไม่ได้อยู่ในปี 1998 แล้ว ปีนี้เป็นปี 2014 3 ปีหลังจาก C++11 ถูกสร้างเสร็จ ก็จะแนะนำฟีเจอร์หนึ่งที่เรียกว่า variadic template ก็พูดกันง่าย ๆ คือ template class หรือ function ที่ไม่ระบุจำนวน type ที่แน่นอน สำหรับ syntax นั้นก็ใกล้เคียงกับ variadic function ก็คือจะใช้ ... ในการบอกว่ามี type ไม่ระบุจำนวนอยู่ใน template parameter list

ยกตัวอย่าง variadic template function สักหนึ่งตัว ...

template<class ...T> void Print(T ...param);

(เอาจริง ๆ ผมก็ยังเขียนไม่ค่อยเป็น 555)

ก็เรียกใช้ฟังก์ชั่นนี้แบบข้างล่างนี้ได้หมดเลย

Print();
Print<int>(10);
Print<char, std::string>('a', "Hello World");

อะไรก็ว่ากันไป

ที่เด็ดกว่านั้นก็คือ เราสามารถเอา variadic template function มาใช้คู่กับ class/function ธรรมดาได้ด้วย อย่าง ...

template<class Type, class ...ParamType>
void DoSomething(Type obj, ParamType ...param)
{
    obj.perform(...param);
}

ซึ่ง obj.perform() นั้นจะสามารถถูกเรียกได้โดยที่ไม่เกี่ยวกับว่ามี type เป็นอะไร ขอแค่ obj นั้นมีฟังก์ชั่น perform() และใส่ parameter เข้ามาครบก็ถือว่าโอเค (ถ้าใส่ไม่ครบจะคอมไพล์ไม่ผ่าน)

เช่น

class A
{
public:
    void perform();
};

class B
{
public:
    bool perform(const std::string&);
}

A a;
B b;

DoSomething(a);
DoSomething(b, "Hello World");

อะไรทำนองนี้

่ส่วนตัวผมใช้เทคนิคนี้กับฟังก์ชั่น AddCommand ข้างบน แบบนี้

template<class Type, class ...ParamType>
void AddCommand(ParamType... param)
{
    commandList.push_back(new Type(...param));
}

AddCommand<MessageCommand>("Hello World");
Drawable drawable;
AddCommand<ShowDrawableCommand>(drawable);

กลายเป็นว่าผมเขียนฟังก์ชั่นทีเดียวแต่ใช้กับกี่คลาสก็ได้ สบายแฮ

Wutipong Wongsakuldej

Programmer, interested in frontend applications, music and multimedia.

Latest posts by Wutipong Wongsakuldej (see all)

4 thoughts on “[C++] Variadic Template กับการสร้างวัตถุที่มี constructor parameter ที่แตกต่างกัน

  1. sleepyfurious

    อ่านแล้วทำให้ผมคิดถึง smart_ptr กับการจัดการ lifetime ด้วย..อาจจะเช่น std::unique_ptr พี่คิดอย่างไรกับ smart_ptr บ้างครับ? ผมคิดว่า c++11 template หลายอย่างดีมากเลย แต่ compiler error message ในสายตาผมก็หน้าตาเหมือนเดิม @~@ (ต้องใช้พลังจิตเยอะมากในการเข้าถึง 55)

    Reply
    1. เรื่องของ smart pointer นี่เป็นเรื่องที่เข้ามาช่วยการจัดการ resource ซึ่งผมมองว่าคนที่เขียน C++ ทุกคนควรจะรู้เอาไว้ครับ

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

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *