ในเอนจินที่ผมพัฒนาอยู่เนี่ย มันจะมีการทำ 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
Latest posts by Wutipong Wongsakuldej (see all)
- Java 8 Date & Time API - January 29, 2016
- วิธีการตั้งชื่อ method - January 26, 2016
- อย่าจับ Exception เร็วเกินไป - January 21, 2016
อ้างอิง http://en.cppreference.com/w/cpp/language/parameter_pack
อ่านแล้วทำให้ผมคิดถึง smart_ptr กับการจัดการ lifetime ด้วย..อาจจะเช่น std::unique_ptr พี่คิดอย่างไรกับ smart_ptr บ้างครับ? ผมคิดว่า c++11 template หลายอย่างดีมากเลย แต่ compiler error message ในสายตาผมก็หน้าตาเหมือนเดิม @~@ (ต้องใช้พลังจิตเยอะมากในการเข้าถึง 55)
เรื่องของ smart pointer นี่เป็นเรื่องที่เข้ามาช่วยการจัดการ resource ซึ่งผมมองว่าคนที่เขียน C++ ทุกคนควรจะรู้เอาไว้ครับ
แต่ผมว่าเรื่องที่สำคัญกว่าคือรู้ที่จะใช้ pointer ในกรณีที่จำเป็น คือการใช้ pointer จำนวนมากในโปรแกรมเป็นข้อบ่งชี้อย่างหนึ่งที่บอกว่าโปรแกรมกำลังมีปัญหาครับ เพราะว่าเมื่อมีมากการตรวจสอบการใช้งานก็ย่อมทำได้ยากขึ้น (ผิดกับตัวแปรใน stack ที่ไม่ว่าอย่างไรก็ถูกทำลายทิ้งอยู่ดี)
จะว่าไป คลาสนี้ถูกสร้างด้วย Command Pattern นะครับ 😉