พูดถึง C++11 แล้ว ฟีเจอร์สำคัญตัวหนึ่งก็คงหนีไม่พ้น smart pointer (ที่ยกมาจาก boost อีกทีนึง) ผมคิดว่าบางคนคงสงสัยว่า เมื่อไหร่ควรใช้ smart pointer และเมื่อไหร่ควรใช้ raw pointer หลายคนคงคิดว่าเราจะใช้ smart pointer แทน raw pointer ได้ทั้งหมด แต่ก็มีบางกรณีที่ถ้าคุณจะใช้ smart pointer แล้วคุณจะเสียใจ :)

(จะพูดอ้อมค้อมทำไมหว่า ?)

smart pointer นั้นออกแบบมาให้เก็บค่า reference ไปยังวัตถุที่อยู่ใน heap ถ้าคุณดันเอาไปใช้กับวัตถุที่อยู่ใน stack เช่น ...

int a = 50;
if (a) {
    std::shared_ptr<int> ptr(&50);
}

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

ดังนั้นกรณีนี้จะใช้ smart pointer ไม่ได้นะครับ

ส่วนตัวผมจะใช้ raw pointer ในการเก็บ reference ของวัตถุใน stack เอาไว้ใน container เพื่อการเอาไปทำอะไรสักอย่าง คือเราไม่สามารถสร้าง container สำหรับ reference ได้ เราก็เลยใช้ container ของ pointer มาเก็บ reference ของวัตถุแทนนั่นเองครับ

เวลาเราเขียนคลาสง่าย ๆ สำหรับการอ่าน resource จากไฟล์ขึ้นมาเพื่อจะใช้อะไรบางอย่าง ถ้าคิดง่าย ๆ เราอาจจะเขียนแบบนี้ ...

class Image {
public:
    Image(const std::string& path);
    ~Image();
private:
    Image::Image(){}
    char* buffer;
}

Image::Image() {
    buffer = new char[file_size(path)];
    fill(path, buffer.get(), file_size(path));
}

Image::~Image() {
    delete[] buffer;
}

มองผ่าน ๆ ก็ดูเหมือนจะไม่มีปัญหาอะไร โดยเฉพาะกับคนที่เขียนภาษาอย่าง Java หรือ C# มาก่อนจะดูไม่ออกเลย ปัญหาอยู่ใน constructor ครับ คือ ในบรรทัดแรกเรามีการสร้าง dynamic array ขึ้นมาหนึ่งตัว แล้วเราก็ fill บัฟเฟอร์ลงไปในบรรทัดที่สอง ถ้าเกิดว่าบรรทัดที่ 2 นั้นมี exception เกิดขึ้นล่ะจะเกิดอะไรขึ้น ?

คำตอบคือ constructor จะหยุดทำงานและ stack จะถูก unwind ขึ้นไปเรื่อย ๆ จนกว่า exception นั้นจะถูกจับด้วย try...catch ซึ่งตัวแปรที่อยู่ใน stack จะทำลายทิ้งไปหมด

สิ่งที่ขาดหายไปก็คือ desctructor ของคลาสนี้นั้นจะไม่ถูกเรียกเลยในกรณีนี้ และหน่วยความจำส่วนที่ถูกจองมาสำหรับ Image::buffer ก็จะยังคงอยู่อย่างนั้น เป็นหน่วยความจำที่รั่วต่อไป

นี่แค่ตัวแปรตัวเดียวนะครับ ถ้าเกิดมีมากกว่านี้ก็วุ่นวายกว่านี้อีก

คำแนะนำทั่วไปสำหรับวิธีนี้คือ ตัว constructor ไม่ควรมีโค๊ดที่โยน exception ได้ ... กลายเป็นว่าเวลาเขียนก็จะวุ่นวาย กลายเป็นต้องมาสร้าง factory method อีก แบบนี้

class Image {
public:
    Image * Load(const std::string& path);
    ~Image();
private:
    Image::Image(){}
    char* buffer;
}

Image Image::Load(const std::string& path) {
    char* buffer = new char[file_size(path)];
    try {
        fill(path, buffer.get(), file_size(path));
    } catch (...) {
        delete[] buffer;
        throws std::runtime_error("Error");
    }
    Image output;
    output.buffer = buffer;

    return output;
}

Image::~Image() {
    delete[] buffer;
}

ข้อเสียของวิธีนี้คือ ยิ่งคุณมี resource ที่อยู่ใน heap มากขึ้น โค๊ดจะยิ่งรกและเละมากขึ้นเรื่อย ๆ โดยเฉพาะตัว Factory Method

สมมติว่าคลาส Image ผมดันมีข้อมูล palette ด้วย

class Image {
public:
    Image * Load(const std::string& path);
    ~Image();
private:
    Image::Image(){}
    char* buffer;
    char* palette;
}

Image Image::Load(const std::string& path) {
    char* buffer = new char[file_size(path)];
    try {
        fill(path, buffer.get(), file_size(path));
    } catch (...) {
        delete[] buffer;
        throws std::runtime_error("Error");
    }
    char *palette = new char[palette_size()];
    try {
        fill_palette(path, palette.get, palette_size(path));
    } catch (...) {
        delete[] palette;
        delete[] buffer;
        throws std::runtime_error("Error");
    }
    Image output;
    output.buffer = buffer;
    output.palette = palette;

    return output;
}

Image::~Image() {
    delete[] buffer;
}

พอผมเพิ่มตัวแปรทีเ่ป็น dynamic array เพิ่มหนึ่งตัว ผมต้องเขียนโค๊ดที่คืนหน่วยความจำที่ชี้ไปโดยตัวแปรตัวก่อนหน้าด้วย (ดังนั้นมันจะเพิ่มแบบ exponential) และยิ่งเพิ่มมากโค๊ดส่วนที่เพิ่มก็จะขยายขึ้นเรื่อย ๆ ดูปวดหัวใช่ไหมครับ ?

วันนี้จะเสนออีกวิธีครับ นั่นก็คือการใช้ smart pointer นั่นเอง (อีกแล้ว) คลาสด้านบนผมจะเขียนให้อยู่ในรูปของ C++11 ได้แบบนี้

class Image {
public:
     Image(const std::string& path);
     Image::Image() = delete;

private:
    std::shared_ptr<char> buffer;
    std::shared_ptr<char> palette;
}

Image::Image(const std::string& path) {
    buffer = std::shared_ptr<char>(
            new char[file_size(path)],
            [](char* array){
                delete [] array;
            });

    fill(path, buffer.get(), file_size(path));

    palette= std::shared_ptr<char>(
            new char[palette_size()],
            [](char* array){
                delete [] array;
            });
    fill_palette(path, palette.get, palette_size(path));
}

สังเกตไหมครับว่า เวลาผมเพิ่มตัวแปรตัวใหม่เข้ามา ผมไม่ต้องไปยุ่งอะไรกับตัวแปรก่อนหน้านั้น ทุกอย่างถูกจัดการโดยตัว smart pointer เอง และเวลาที่มี exception โยนเข้ามาใน constructor ตัวแปรที่เป็น smart pointer นั้นจะเข้าไปคืนหน่วยความจำส่วนที่มันชี้ไปด้วย ดังนั้นผมจึงไม่ต้องไปยุ่งอะไรกับมันอีก ผมสามารถลบ destructor ทิ้งไปได้ด้วยซ้ำ

ทีนี้น่าจะมีคนสงสัยว่าทำไมผมต้องใส่ lambda ตรงที่ผม assign ค่าตัว smart pointer ทั้งหลาย ? คืองี้ครับ โดยปรกติแล้ว เวลาที่ smart pointer จะไปคืนหน่วยความจำส่วนที่จองมาใน heap เนี่ย มันจะใช้ delete operator ครับ ซึ่งถ้าไม่ใส่ lambda ที่ผมระบุไว้เนี่ย การคืนมันจะกลายเป็น delete buffer ซึ่งมันจะคืนแค่ตัวแรกสุดตัวเดียว และส่วนที่เหลือก็จะกลายเป็นหน่วยความจำรั่วไป lamba ที่ผมใส่เอาไว้เป็นการไป override วิธีการคืนหน่วยความความจำของเจ้า smart pointer ตัวนี้ (เรียกว่า deleter) โดยผมไปสั่งให้มันใช้ delete[] operator แทน ทำให้สามารถเรียกคืนหน่วยความจำมาได้หมด

เราสามารถใช้วิธีเดียวกันกับหน่วยความจำที่จองโดยคำสั่งพิเศษของ Library อื่น ๆ ที่เราใช้ได้ด้วย อย่างเช่น

Music::Music(const std::string& path)
{
    Mix_Music * pMusic = Mix_LoadMUS(path.c_str());
    if(!pMusic)
    {
        auto error =
                std::string("Load Music Failed : ") +
                Mix_GetError();

        throw std::runtime_error(error);
    }

    mixMusic = std::shared_ptr<Mix_Music>(
                pMusic,
                [](Mix_Music* pMusic) {
                    Mix_FreeMusic(pMusic);
                });
}

อันนี้เป็นคลาสเพลงที่เรียกใช้คำสั่งจาก SDL_mixer ครับ

วันนี้จั่วหัวเป็นภาษาอังกฤษ เพราะถ้าใส่ภาษาไทย มันคงมีอยู่ไม่กี่คำหรอกครับ

Code indentation ก็คือการใส่ที่ว่างหน้าแต่ละบรรทัดของโค้ด เพื่อที่จะระบุว่าโค๊ดปัจจุบันอยู่ในสโคประดับไหน บนคีย์บอร์ดเราจะสามารถใส่ที่ว่างได้สองวิธี นั่นคือการใช้ space bar กับการใช้ tab

ผมคิดว่าทุกคนคงรู้นะว่าปุ่มไหนอยู่ตรงไหน

เรื่อง space กับ tab นี่ถือได้ว่า เป็นเรื่องที่ทำให้คนทะเลาะกันได้ เวลาที่จะกำหนด coding standard หรือทำ code review เพราะทั้งสองอย่างก็มีข้อดีของมันเอง

ส่วนตัวคิดว่า จะใช้แบบไหนก็ใช้ไปเถอะ ที่สำคัญกว่าก็คือต้องใช้เหมือนกันหมดทุกคน และใช้อย่างสม่ำเสมอ ไม่เช่นนั้นมันก็จะเกิดปัญหาหลายอย่างขึ้นมาเหมือนกัน

แต่ถ้าให้เลือกว่าเอาแบบไหน ผมเลือกใช้ space นะ

คืองี้ครับ ในโปรแกรมแต่ละโปรแกรม จะแสดงผล tab ไม่เหมือนกัน บางโปรแกรมก็ 3 space มั่งล่ะ 4 space มั่งล่ะ (8 space ยังไม่แปลกเลย) แถมยังเปลี่ยนได้ด้วย ดังนั้นเวลาเปิดบนแต่ละโปรแกรมโค๊ดจะแสดงผลไม่เหมือนกัน

และในกรณีที่แย่ที่สุดคือถ้ามีคนเอา space กับ tab ไปใช้ปนกันในโค๊ดเดียว ในทุก ๆ whitespace บนโค๊ด เราจะเห็นโค๊ดที่ไม่ตรงกันในโปรแกรมหนึ่ง และดูสวยในอีกโปรแกรมหนึ่ง

ในทางกลับกัน space นั้นแสดงผลเหมือนกันหมดทุกเครื่อง ไม่ว่ามันจะอยู่ที่ไหน ไม่ว่าจะอยู่บนแพลตฟอร์มไหน ดังนั้น space จึงเป็นคำตอบที่ดีกว่า เพราะว่ามันมีความสม่ำเสมอ (consistency) ที่ดีกว่า tab นั่นเอง

แต่พิมพ์เยอะแล้วอาจจะเมื่อย ก็มีเคล็ดนิดหน่อยครับ คือ ใน Text Editor ส่วนใหญ่สามารถแทนที่ tab ด้วย space ได้ คือเวลากด tab แทนที่จะได้ tab เราก็จะได้ space จำนวนหนึ่งแทน

ไม่ยากกันเกินไปใช่ไหมครับ :)

4

ในเอนจินที่ผมพัฒนาอยู่เนี่ย มันจะมีการทำ 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);

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

1

ทุก ๆ คนที่เคยใช้ C/C++ ย่อมเคยที่จะใช้เจ้า preprocessor #include อยู่แล้ว แต่หลายคนคงไม่เข้าใจว่าไอ้เจ้านี่มันทำอะไร วันนี้จะอธิบายเพิ่มเติมเรื่องของเจ้านี่นะครับ

Syntax

สำหรับวิธีการใช้ #include นั้นมีอยู่สองแบบครับ นั่นก็คือ #include <ชื่อไฟล์> และ #include "ชื่อไฟล์" แต่ละแบบมีความหมายที่แตกต่างกัน ก็คือ แบบที่ใช้ <> นั้นเป็นการอ้างอิงถึงไฟล์ที่อยู่ใน include path ที่เราตั้งค่าให้ compiler ไปหา (พูดกันง่าย ๆ ก็บรรดา library ทั้งหลายที่ไม่ได้เป็นส่วนหนึ่งของโปรเจคครับ ซึ่งรวมถึง standard libary ด้วย) กับแบบ "" นั้นจะเป็นการอ้างถึงไฟล์ที่อยู่ในโปรเจคของเรา

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

การทำงานของ #include

เจ้า preprocessor #include เป็นการสั่งให้ compiler นั้นไปเอาเนื้อหาของไฟล์ที่ระบุไว้มาแทนที่คำสั่งนี้ก่อนที่ไฟล์จะถูกคอมไพล์ครับ ยกตัวอย่างเช่น ถ้าผมมีไฟล์สองไฟล์ดังต่อไปนี้

abc.i

printf("ABC");

usage.cpp

int main(char** args, int argc) {
    #include "include.i"
    return 0;
}

ผมคิดว่าหลาย ๆ คนคงไม่เคยใช้โค้ดลักษณะนี้ แต่น่าจะพอเดาได้ ครับเวลาเราสั่งคอมไพล์ usage.cpp นั้นคอมไพล์เลอร์จะเอาโค้ดจาก abc.i ไปแทนประโยค #include "abc.i" ก่อนที่จะคอมไพล์โค้ดนั้น เราก็จะได้โค้ดหน้าตาแบบนี้ครับ

int main(char** args, int argc) {
printf("ABC");
    return 0;
}

คิดว่าน่าจะเห็นภาพนะครับว่าทำงานอย่างไร

ทั้งนี้การใช้งานลักษณะนี้นี่ไม่ค่อยได้รับความนิยมในระยะหลัง และผมเองก็ไม่แนะนำซะด้วย การใช้ #include ลักษณะนี้ทำให้การไล่โปรแกรมเวลามีปัญหาทำได้ยากมากครับ ผมยืนยันได้ เพราะผมเคยทำงานกับโปรเจคที่ใช้ ไฟล์ include เยอะมากในลักษณะนี้มาก่อน บอกตามตรง มันน่าปวดหัวมากครับ

การใช้งาน #include

การใช้งาน #include ที่ได้รับความนิยมและเป็นที่แนะนำกันก็คือการใช้ ไฟล์ include ในเชิงของการทำ forward declaration ยกตัวอย่างเช่น ถ้าเรามีไฟล์หลายไฟล์ที่ใช้งานฟังก์ชั่น int calculateAge(date birthdate, date currentdate)

ปัญหาของการใช้ Forward Declaration

ถ้าเราไม่ใช้ #include เลยเราก็ต้องประกาศฟังก์ชั่นนี้บนทุกไฟล์ที่อ้างอิงถึง (และกำหนดฟังก์ชั่นนี้บนไฟล์ใดไฟล์หนึ่งเพียงไฟล์เดียว ไม่เช่นนั้นตอน link โปรแกรมจะพังอีก) ทุก ๆ ไฟล์ก็จะมีไอ้การประกาศนี้บนส่วนบนสุดของไฟล์ ก่อนหน้าที่ฟังก์ชั่นนี้จะถูกเรียกใช้งาน

int calculateAge(date birthdate, date currentdate);

นี่แค่ฟังก์ชั่นเดียวนะครับ ถ้ามีหลาย ๆ ฟังก์ชั่นนะ มันจะยาวสุด ๆ ไปเลยครับ อาจจะเป็นแบบนี้

int calculateAge(date birthdate, date currentdate);
int compareDate(date date1, date date2);
...
int hundredthFunctionAboutDate();

ถ้าสมมติว่าผมแก้ signature ของฟังก์ชั่นใดฟังก์ชั่นหนึ่ง ผมต้องไปตามแก้มันหมดทุกไฟล์ ... ลาสุนัขบวชเลยล่ะครับ

แก้ปัญหาโดยการใช้ #include

ผมสามารถที่จะย้ายเอาการประกาศฟังก์ชั่นทั้งหมดมาใส่ในไฟล์เดียว แล้วใช้ #include ในทุก ๆ ไฟล์ที่ใช้งานแทน สะดวกไหมครับ ? ผมอาจจะตั้งชื่อไฟล์นี้ว่า datefunction.h แล้วใช้คำสั่ง #include "datefunction.h" ในทุก ๆ ไฟล์ที่ผมจะใช้ฟังก์ชั่นที่ประกาศในไฟล์นี้ (ส่วนตัวฟังก์ชั่นจะกำหนดอยู่ที่ไหนนั่นอีกเรื่องนึงนะ)

ถ้าเกิดผมมีการแก้ไข signature ของฟังก์ชั่น เช่นเปลี่ยนจาก int calculateAge(date birthdate, date currentdate) เป็น float calculate_age(date birthdate, date currentdate) ก็แค่แก้ในไฟล์ datefunction.h เท่านั้นเอง ยกเว้นถ้าเกิดว่ามีบางไฟล์ที่ใช้ float ไม่ได้จริง ๆ ก็ไปแก้ไฟล์นั้นเอาอีกที อะไรทำนองนี้

อ้อ ใน C/C++ เราจะเรียกไฟล์ include ในลักษณะที่เป็น forward declaration ว่า header file ครับ และมักจะใช้นามสกุล .h หรือ .hpp

Inclusion Guard

สำหรับ include ไฟล์นั้นก็เหมือนกับไฟล์ที่มีโค้ดของ C/C++ ตามปรกตินั่นล่ะครับ คือมีได้ทุกอย่างอย่างที่ .c หรือ .cpp มี แน่นอนว่านั่นรวมถึงการเรียกใช้คำสั่ง #include ด้วย

ปัญหามันก็จะมาเมื่อเราใช้ include ไฟล์ที่มี #include ไฟล์อื่น ๆ เข้ามา และไอ้ไฟล์ที่เรียกใช้ก็ดันไปเรียก #include ไฟล์นั้นเข้ามา ก็คือ #include ไฟล์เดียวกันทั้งบนไฟล์ที่เรียกใช้และไฟล์ที่ถูก include เข้ามา เช่น สมมติว่าผมมี

vector.h

struct {
    float x;
    float y;
    float z;
    float w;
} vector;

matrix.h

#include "vector.h"

struct {
    float x0;
    float y0;
    float z0;
    float w0;
    /*....omitted*/

} matrix;
vector multiply(vector v, matrix m);
vector multiply(matrix m, vector v);

usage.c

#include "vector.h"
#include "matrix.h"

ผมละโค้ดที่เรียกใช้ใน usage.c ไว้เพราะว่ามันไม่สำคัญนะครับ แต่เรามาดูตอนคอมไพล์กันเลยดีกว่า ตัวคอมไพล์เลอร์นั้นจะทำการแทนที่โค้ดใน usage.c ให้มีลักษณะแบบนี้

struct {
    float x;
    float y;
    float z;
    float w;
} vector;
struct {
    float x;
    float y;
    float z;
    float w;
} vector;

struct {
    float x0;
    float y0;
    float z0;
    float w0;
    /*....omitted*/

} matrix;
vector multiply(vector v, matrix m);
vector multiply(matrix m, vector v);

กลายเป็นว่าเรามี struct ที่ชื่อว่า vector สองตัว ... กลายเป็นว่าคอมไพล์ไม่ผ่านครับ declaration สองตัวที่ชื่อเหมือนกันนั้นอยู่บนไฟล์เดียวกันไม่ได้ครับ กลายเป็นการประกาศซ้ำ

หลายคนคงคิดว่า ก็เอา #include "vector.h" ใน usage.c ออกไปสิ ทำอย่างนั้นก็ได้ครับ แต่สำหรับโปรแกรมที่ซับซ้อนมาก ๆ เราทำแบบนี้ไม่ได้ครับ คือถ้าให้ include สัก 5 ไฟล์ ใน 10 ไฟล์นี่ก็ไล่ไปเถอะครับ ขอให้โชคดี

วิธีแก้ก็คือ เราจะใช้ preprocessor สามตัวช่วยครับ ตามตัวอย่างโค้ดข้างล่างนี้

vector.h

#ifndef VECTOR_H
#define VECTOR_H
struct {
    float x;
    float y;
    float z;
    float w;
} vector;
#endif

matrix.h

#ifndef MATRIX_H
#define MATRIX_H
#include "vector.h"

struct {
    float x0;
    float y0;
    float z0;
    float w0;
    /*....omitted*/

} matrix;
vector multiply(vector v, matrix m);
vector multiply(matrix m, vector v);
#endif

ผมขออธิบายเฉพาะ vector.h นะครับ เริ่มจาก #define ก่อน เจ้า #define นี้เป็นตัวประกาศ macro ชื่อ VECTOR_H อยู่ เป็นการบอกให้คอมไพล์เลอร์รู้ครับ คือในกรณีนี้เราไม่กำหนดว่า macro นี้ทำอะไร บอกแค่ว่าให้สร้างขึ้นมาณ.จุดนี้

ส่วนคำสั่ง #ifndef ย่อมาจาก if not define จะตามท้ายด้วยชื่อ macro เดาจากชื่อคงพอบอกได้ ก็คือถ้ามี macro ชื่อ VECTOR_H ประกาศเอาไว้ตัวคอมไพล์เลอร์จะเอาโค้ดตั้งแต่ประโยค #ifndef จนถึง #endif ออกจากโค้ดก่อนคอมไพล์นั่นเอง

พอเราสั่งคอมไพล์ usage.cpp แล้วเนี่ย คอมไพล์เลอร์จะเริ่มจากประมวลผล preprocessor ที่บรรทัดแรก จะได้โค้ดหน้าตาแบบนี้

usage.cpp

#ifndef VECTOR_H
#define VECTOR_H
struct {
    float x;
    float y;
    float z;
    float w;
} vector;
#endif
#include "matrix.h"

จากนั้นมันจะทำ preprocessor บรรทัดบนสุดต่อ ก็คือ #ifndef VECTOR_H จนถึง #endif (มันใช้คู่กันก็เลยทำงานเป็นคู่ครับ) ซึ่งเนื่องจากณ.จุดนี้ยังไม่มีมาโครที่ชื่อ VECTOR_H ประกาศเอาไว้เลย มันก็เลยคงโค้ดส่วนด้านในระหว่าง #ifndef จนถึง #endif เอาไว้ ผลลัพท์ที่ได้ก็จะเป็นแบบนี้

usage.cpp

#define VECTOR_H
struct {
    float x;
    float y;
    float z;
    float w;
} vector;
#include "matrix.h"

ตัว preprocessor ข้างบนสุดที่เหลือก็คือ #define VECTOR_H ตัว compiler เห็นดังนั้น ก็ทำการสร้าง macro เก็บเอาไว้ในหน่วยความจำของตัวเอง แล้วก็เอาโค้ดบรรทัดนี้ออก ได้ผลลัพท์ออกมาเป็นแบบนี้

usage.cpp

struct {
    float x;
    float y;
    float z;
    float w;
} vector;
#include "matrix.h"

ต่อไปก็มี preprocessor ตัวที่อยู่บนสุดก็คือ #include "matrix.h" คอมไพล์เลอร์ก็จะไปเอาเนื้อความของไฟล์matrix.h มาใส่ตรงนี้

usage.c

struct {
    float x;
    float y;
    float z;
    float w;
} vector;

#ifndef MATRIX_H
#define MATRIX_H
#include "vector.h"

struct {
    float x0;
    float y0;
    float z0;
    float w0;
    /*....omitted*/

} matrix;
vector multiply(vector v, matrix m);
vector multiply(matrix m, vector v);
#endif

พอถึงตรงนี้น่าจะเดาได้แล้ว ครับ มันทำงานคู่ #ifndef จนถึง #endif ต่อ เนื่องจาก MATRIX_H ไม่ได้ถูกประกาศไว้ มันก็เลยคงโค้ดเอาไว้เหมือนเดิม กลายเป็นแบบนี้

usage.c

struct {
    float x;
    float y;
    float z;
    float w;
} vector;

#define MATRIX_H
#include "vector.h"

struct {
    float x0;
    float y0;
    float z0;
    float w0;
    /*....omitted*/

} matrix;
vector multiply(vector v, matrix m);
vector multiply(matrix m, vector v);

แล้วก็ทำงานที่ประโยค #define MATRIX_H ก็แค่สร้างมาโครขึ้นมาในหน่วยความจำของคอมไพล์เลอร์ แล้วก็ลบโค้ดประโยคนี้ทิ้ง

usage.c

struct {
    float x;
    float y;
    float z;
    float w;
} vector;

#include "vector.h"

struct {
    float x0;
    float y0;
    float z0;
    float w0;
    /*....omitted*/

} matrix;
vector multiply(vector v, matrix m);
vector multiply(matrix m, vector v);

คำสั่งต่อไปก็คือ #include "vector.h" ก็แทนค่าเข้าไปเลย

usage.c

struct {
    float x;
    float y;
    float z;
    float w;
} vector;

#ifndef VECTOR_H
#define VECTOR_H
struct {
    float x;
    float y;
    float z;
    float w;
} vector;
#endif

struct {
    float x0;
    float y0;
    float z0;
    float w0;
    /*....omitted*/

} matrix;
vector multiply(vector v, matrix m);
vector multiply(matrix m, vector v);

หลังจากนั้นมันก็ทำงานในส่วนของ #ifndef VECTOR_H จนถึง #endif ครับ แต่จำได้ไหมครับว่า เรามีการประกาศ VECTOR_H เอาไว้แล้วข้างบน (ลองย้อนกลับขึ้นไปอ่านดูนะครับ) ดังนั้นโค้ดประโยคนี้จะไม่ทำอะไรเลย โค้ดที่ได้ก็จะกลายเป็นแบบนี้ไป

usage.c

struct {
    float x;
    float y;
    float z;
    float w;
} vector;

struct {
    float x0;
    float y0;
    float z0;
    float w0;
    /*....omitted*/

} matrix;
vector multiply(vector v, matrix m);
vector multiply(matrix m, vector v);

และสุดท้ายพอไม่มี preprocessor เหลืออยู่แล้ว ตัวคอมไพล์เลอร์ถึงจะเริ่มคอมไพล์โค้ดครับ

นี่คือการทำงานของ inclusion guard ซึ่งเป็นส่วนสำคัญที่ header file ควรจะมี เรื่องหนึ่งที่ต้องระวังก็คือเราต้องใช้ชื่อ macro ที่ไม่ซ้ำกันในแต่ละไฟล์ ไม่เช่นนั้นอาจจะมีปัญหาได้ (ใน MSVC มีคำสั่ง #pragma once ที่ทำงานเป็น inclusion guard ได้ ถ้าเราไม่สนใจเรื่อง cross-platform จะใช้ตัวนี้แทนก็ได้ครับ)

ครั้งนี้ผมจะขอจบแค่นี้ก่อน ครึ่งหลังเราจะมาทำความเข้าใจกับปัญหาของ include file และจะมาดูกันว่าเราจะแก้ปัญหานั้นได้อย่างไรครับ

พูดเรื่องพื้นฐานเช่นเดิมครับ ผมคิดว่าโปรแกรมเมอร์ส่วนใหญ่ที่ไม่เคยใช้งานเจ้าภาษานี้มักจะคิดว่าภาษา C++ นั้นเป็นภาษาที่ยาก ซับซ้อน อ่านไม่รู้เรื่อง และอะไรต่อมิอะไรแล้วแต่จะจินตนาการณ์ไปตามเรื่อง ว่ากันง่าย ๆ คือยังไม่ทันลองหัดเลยก็คิดไปกันก่อนแล้วนั่นล่ะ ปัญหานี้ทำให้ประเทศเราขาดประชากรโปรแกรมเมอร์ C++ อย่างหนัก ส่งผลให้ค่าตัวของคนเขียนภาษานี้สูงกว่าภาษาอื่น

จากการสำรวจของ Adecco ในปี 2014 พบว่า ค่า ตัวของโปรแกรมเมอร์ภาษา C++ ในประเทศไทยที่มีประสบการณ์ตั้งแต่ 5 ปีขึ้นไปนั้นเริ่มต้นที่ 50,000 บาทต่อเดือนครับ (ที่จริง ตัวเลขนี้สูงเกินจริงไปนิด แล้วก็ภาษาอื่นในกลุ่มเดียวกันก็ราคานี้แหละ 555 เอาเป็นว่าผมหลอกคุณละกัน) ฟังดูน่าสนใจใช่ไหมครับ

วันนี้จะมาเล่าให้ฟังว่าพื้นฐานของ C++ นั้นเป็นอะไรครับ คืองี้ครับ C++ เป็นภาษาที่รวมเอา programming paradigm ที่ได้รับความนิยมสูง 3 ตัวมารวมกันในภาษาเดียว (ภาษาสมัยใหม่อย่าง Java, C# เองก็เป็นแบบนี้เหมือนกัน) ทั้งสามนั้นก็ได้แก่

  1. Procedural Programming
  2. Object-Oriented Programming
  3. Metaprogramming

Procedural Programming

Procedural Programming เป็นคอนเซพท์ดั้งเดิมของ C++ ซึ่งได้รับการสืบทอดมาจากภาษา C คอนเซ็พท์นี้จริง ๆ ค่อนข้างตรงไปตรงมา ... มันก็คือการเขียนโค๊ดในรูปแบบของฟังก์ชั่นทางคณิตศาสตร์ อย่าง f(x, y) = x^2 + 2xy +y^2 อะไรทำนองนี้นั่นล่ะครับ

ใน C++ ใช้ syntax เดียวกับภาษา C เลย ฟังก์ชั่นข้างบนเลยสามารถเขียนได้แบบนี้

int function(int x, int y) 
{
    return pow(x, 2) + (2*x*y) + pow(y, 2);
}

ง่ายไหม ?

Object-Oriented Programming

คอนเซพท์ของ OOP นั้นค่อนข้างกว้าง โดยหลัก ๆ แล้วมันคือการมองว่าหน่วยย่อยของโปรแกรมเป็นวัตถุ (ในคอนเซพท์อื่นจะมองว่าหน่วยย่อยของโปรแกรมนั้นเป็นแค่ก้อนข้อมูล) และการเขียนโปรแกรมก็คือการปฎิสัมพันธ์กันระหว่างวัตถุ

OOP แบบของ C++ นั้นจะเป็นแบบ Static Type ก็คือวัตถุทุกชนิดจะถูกสร้างโดยจะมี Class เป็นแม่แบบ และประเภทของวัตถุนั้นก็จะถูกนิยามโดย Class เมื่อถูกนิยามไปแล้วก็จะไม่สามารถเปลี่ยนแปลงลักษณะการทำงานภายในได้

เดี๋ยวเรื่อง OOP จะพูดถึงอย่างละเอียดในโอกาสถัดไปครับ รายละเอียดเยอะน่าดู

Metaprogramming

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

Metaprogramming ของ C++ นั้นจะเป็นลักษณะ Template โดย Template นั้นคือโปรแกรม (อาจจะเป็นคลาสหรือฟังก์ชั่นก็ได้) ที่มี Type บางตัวที่ประกาศเอาไว้เป็นแค่ placeholder ขึ้นมา เช่น

template <class T>
T sum(T x, T y) 
{
    return x + y;
}

ตัว Type T เนี่ยตอนที่เขียนเราวางเอาไว้เฉย ๆ ว่าจะแทนที่ด้วย Type อื่นในตอนที่เอาไปใช้ ซึ่งตอนเอาไปใช้จริง ๆ ก็ค่อย ๆ มากำหนดอีกทีว่าเป็น Type ไหนแบบนี้ครับ

int a = sum<int>(5, 10);
float b = sum<float>(0.25f, 0.85f);

ทั้งนี้ถ้า Type ที่เราใส่เข้าไปนั้นไม่มีฟังก์ชั่นหรือโอเปอเรเตอร์ที่ฟังก์ชั่น sum() รองรับ โปรแกรมก็จะคอมไพล์ไม่ผ่าน เท่านั้นเอง :)

อ้อ เราสามารถกำหนด Type หลอก ๆ ไว้กี่ตัวก็ได้ครับ ใส่เอาไว้ใน template< > นั่นแหละ กี่ตัวก็ใส่ไป คั่นด้วย comma

ก็จบเรื่องนี้แต่เพียงเท่านี้ โอกาสหน้าจะพูดถึง OOP ในรายละเอียดนะครับ

Edit: อ.Tapanan Yeophantong(@tapananYph) ติงมาว่า Functional Programming ที่พูดถึงน่าจะหมายถึง Procedural Programming มากกว่า ก็เลยแก้ไขให้ถูกต้อง ขอขอบคุณอ.มาณ.ที่นี้ครับผม

หลาย ๆ คนคงรู้จักคำว่า mock-up คำนี้มีความหมายว่า แบบจำลอง หรือวัตถุปลอม ๆ ที่เอาไว้สำหรับแสดงให้ลูกค้าดูว่าเมื่อผลิตภัณฑ์สร้างเสร็จแล้วหน้าตามันจะเป็นอย่างไร คำนี้มีเป็นคำผสมที่มีพื้นฐานจากคำว่า mock ที่แปลว่าการล้อเลียน

แล้วไอ้การล้อเลียนที่ว่านี้มันใช้ทำอะไรในการพัฒนาโปรแกรมได้ ?

คืองี้ครับ หลาย ๆ ครั้งที่เราต้องเขียนโปรแกรมที่ซับซ้อน และต้องอาศัย parameter จากคลาสที่มีความซับซ้อนมาก ๆ ซึ่งการทดสอบโปรแกรมนั้น ๆ ด้วยการสร้างตัวแปรขึ้นมาเป็น parameter ส่งเข้าไปนั้นอาจจะเป็นเรื่องที่ทำได้ยาก

อย่างเช่นสมมติว่าเราพัฒนาโปรแกรมที่เป็น Servlet โดยเรามีคลาสที่สืบทอดมาจาก HTTPServlet และเรากำลังจะเขียนโปรแกรมทดสอบเจ้าเมธอด doGet() ที่มีหน้าตาแบบนี้

class MyServlet extends HTTPServlet {
    @Override
    protected void doGet(HttpServletRequest req,
                 HttpServletResponse resp) {
    /* Implementations */
    }
}

คำถามคือ แล้วเราจะสร้างตัวแปรที่มีประเภทเป็น HttpServletRequest และ HttpServletResponse ได้อย่างไร ? เพราะเจ้า type ทั้งสองตัวนี้เป็น Interface ครับ

คำตอบน่ะเหรอ ... เขียนคลาสใหม่ขึ้นมา implement เจ้าสองคลาสนี้ไปเลยครับ

class MockHttpServletRequest implments HttpServletRequest{
     /* implements every method here*/
}

class MockHttpServletResponse implements HttpServletResponse {
    /* implements every method here*/
}

เขียน implementation ของแต่ละ method ข้างในให้ง่ายที่สุดครับ อันไหน get ก็คืนค่าคงที่ไปเลย อันไหน set ก็สร้างตัวแปรมารับ อะไรทำนองนี้ เช่น

class MockHttpServletRequest implments HttpServletRequest{
    String getContextPath() {
        return "/";
    }
}

ทั้งนี้ถ้าเป็น method ที่ใช้ในการทดสอบด้วย ก็ต้องเขียนให้สอดคล้องกับ test case ด้วยนะครับ

ทีนี้เราก็เขียนโปรแกรม unit test ได้ง่าย ๆ แบบนี้ครับ

class MyHttpServletTest {
    @Test
    void testDoGetRootPath() {
        HttpServletRequest req = new MockHttpServletRequest();
        HttpServletResponse resp = new MockHttpServletResponse();
        MyHttpServlet servlet = new MyHttpServlet();

        servlet.service(req, resp);
    }
}

อันนี้พอดีว่า doGet() มันเป็น private method ครับ ไปเรียกตรง ๆ ไม่ได้ ผมเลี่ยงไปเรียก service() แทน เพราะผมรู้ว่ามันจะไปเรียก doGet() แต่ถ้าอยากทดสอบเจ้า doGet() ก็มีอีกวิธี ก็คือเขียนชุดโปรแกรมทดสอบเป็น class ที่สืบทอดเจ้า MyHttpServletTest อีกต่อไปเลยโลด

class MyHttpServletTest extends MyHttpServlet{
    @Test
    void testDoGetRootPath() {
        HttpServletRequest req = new MockHttpServletRequest();
        HttpServletResponse resp = new MockHttpServletResponse();
        MyHttpServletTest servlet = new MyHttpServlet();

        servlet.doGet(req, resp);
    }
}

แค่นี้เอง

(อีกวิธีคือเอาตัว test ใส่ลงไปในคลาสที่จะ test ไปเลย ซึ่งไม่แนะนำครับ เพราะว่าตัว test จะติดไปในโค๊ดที่จะเอาไปรันจริงด้วย ทำให้มันกินแรมมากขึ้นเปล่า ๆ)

ปัญหาของการใช้ Mock คือ ... ในกรณีที่แย่ที่สุดเราอาจจะต้องสร้างคลาสขึ้นมาสำหรับแต่ละเทสต์เคสเลย ซึ่งมันเยอะมาก และมันก็อาจจะไม่ชัดเจนพอที่เอาไปใส่ใน unit test (ซึ่งควรจะเรียบง่ายที่สุดไม่งั้นมันจะอ่านยาก) ที่แย่กว่านั้นเราต้อง implement เมธอดที่เราไม่ได้ใช้ทดสอบด้วย ทำให้โค๊ดยาวโดยใช้เหตุ

สำหรับจุดนี้เราสามารถใช้ mock framework เข้ามาช่วยได้ครับ ในภาษา Java นั้นมี Mock Framework ที่ได้รับความนิยมอยู่หลายตัว (ลอง Google ดูนะครับ) ผมจะลองใช้ Mockito ดู เจ้าโปรแกรมทดสอบมันก็จะเหลือแค่นี้

import static org.mockito.Mockito.*;

class MyHttpServletTest extends MyHttpServlet{
    @Test
    void testDoGetRootPath() {
        HttpServletRequest req = mock(HttpServletRequest.class);
        when(req.getContextPath()).thenReturn("/");
        HttpServletResponse resp = mock(HttpServletResponse.class);
        MyHttpServletTest servlet = new MyHttpServlet();

        servlet.doGet(req, resp);
    }
}

แค่นี้เอง ผมไม่ต้องสร้างคลาสใหม่ขึ้นมาเพื่อใช้ทดสอบด้วย ยอดไปเลย :)

สุดท้ายนี้ถ้าถามว่าทำไมถึงเรียกว่า mock ผมคิดว่ามันเกิดจากการล้อเลียนเจ้า class จริง ๆ น่ะครับ เราสร้าง class ใหม่ขึ้นมาล้อเลียนตัวเดิมที่ใช้งานได้ กลายเป็นคลาสง่อย ๆ ตัวนึง อะไรทำนองนี้ ที่จริงการสร้าง mock-up ก็คือการสร้างวัตถุชิ้นหนึ่งที่มีหน้าตาเหมือนของจริงแต่ทำอะไรไม่ได้ ใช่ไหมล่ะครับ 😉 เจ้า mock object นี่ก็เหมือนกันแหละ

ปล.โค๊ดข้างบนยังไม่ได้เทสต์ครับ อาจจะมีจุดผิดได้ 555 ลองดูก็แล้วกัน

หลังจากห่างหายกับเรื่องการวาดตัวอักษรไปนาน วันนี้จะมาอัพเดตเรื่องนี้สักเล็กน้อย

ในตัวอย่างเก่า ๆ ผมใช้การสร้าง SDL_Surface ขึ้นมาแล้วเรียก SDL_CreateTextureFromSurface() ในการสร้าง Texture ก่อนที่จะวาดลงไป วิธีนี้มีข้อเสียเพราะว่าเราจะมี object ใช้แล้วทิ้งเพิ่มมาหนึ่งตัว ซึ่งถ้าเราสามารถลดตรงนี้ไปได้โค๊ดเราก็จะทำงานได้อย่างมีประสิทธิภาพมากขึ้นครับ

โค๊ดก็ไม่ได้มีอะไรซับซ้อนเลย

SDL_Texture* CreateTextureFromFT_Bitmap(SDL_Renderer* renderer,
                                        const FT_Bitmap& bitmap,
                                        const SDL_Color& color)
{
    SDL_Texture* output = SDL_CreateTexture(renderer,
            SDL_PIXELFORMAT_RGBA8888,
            SDL_TEXTUREACCESS_STREAMING,
            bitmap.width,
            bitmap.rows);

    void *buffer;
    int pitch;
    SDL_LockTexture(output, NULL, &buffer, &pitch);

    unsigned char *src_pixels = bitmap.buffer;
    unsigned int *target_pixels = reinterpret_cast<unsigned int*>(buffer);

    SDL_PixelFormat* pixel_format = SDL_AllocFormat(SDL_PIXELFORMAT_RGBA8888);

    for (int y = 0; y < bitmap.rows; y++)
    {
        for (int x = 0; x < bitmap.width; x++)
        {
            int index = (y * bitmap.width) + x;
            unsigned int alpha = src_pixels[index];
            unsigned int pixel_value =
                    SDL_MapRGBA(pixel_format, color.r, color.g, color.b, alpha);

            target_pixels[index] = pixel_value;
        }
    }

    SDL_FreeFormat(pixel_format);
    SDL_UnlockTexture(output);

    return output;
}

ผมเปลี่ยนไปใช้ return statement แทนการใช้ pass-by-reference นะครับ โค๊ดจะอ่านง่ายขึ้นพอสมควร ส่วนการเอาไปใช้ก็เหมือนเดิมครับ อาจจะต้องปรับเปลี่ยนโค๊ดเดิมนิดหน่อยแต่ก็ไม่เกินความสามารถกันอยู่แล้ว

โค๊ดชุดนี้มีการลดการใช้ hardcode ในส่วนของการคำนวนค่าในแต่ละ pixel ลง จากของเก่าจะเป็นการยึด format มาเลย ก็เปลี่ยนมาใช้ SDL_MapRGBA() แทน แต่โค๊ดนี้ก็ยังอยู่ภายใต้ข้อจำกัดที่ว่าแต่ละ pixel นั้นมีขนาด 4 byte อันนี้อาจจะมีการแก้ไขในอนาคตครับ

อ่านแล้วก็ไปลองเล่นกันดูนะครับ :-)

ไม่รู้ว่ามีใครยังติดตาม series นี้อยู่หรือเปล่า เขียนนานเหลือเกิน คนเขียนช่วงนี้สุขภาพหัวใจไม่ค่อยดีครับก็เลยหักโหมมากไม่ได้น่ะ 😛

ช่วงการวาดแบบ Raster นี่ ผมจะขออนุญาตแบ่งเป็นตอน ๆ นะครับ เพราะว่าตรงนี้จะเริ่มลงไปรายละเอียดแล้ว คงจะยาวทีเดียว

ก่อนอื่นขอออกตัวก่อนว่า ผมจะเขียนการวาดตัวหนังสือแบบ Vector รวมกับแบบ Raster ไปเลย  เหตุผลก็คือระบบกราฟิคในปัจจุบันนั้นการวาดในระดับล่างจะเป็นแบบ Raster ทั้งหมด การวาดกราฟิคแบบ Vector นั้นตัว Output ที่ได้จะถูกแปลงกลับมาเป็น Raster ก่อนที่จะแสดงผลออกทางหน้าจอ เป็นขั้นตอนที่เรียกกว่า Rasterization (สำหรับ การวาดภาพบนหน้าจอแบบ Vector คือการวาดที่ใช้จอแบบ CRT แบบโบราณที่ตัวปืนยิงรังสีจะขยับตาม Input ซึ่งถ้าอยากเห็นของจริงก็คงต้องไปพิพิธภัณฑ์ล่ะครับ) ...continue reading เรื่องวุ่น ๆ กับตัวหนังสือ – ตอนที่ 6 – วาดตัวหนังสือแบบ Raster ส่วนที่ 1

1

โดยปรกติแล้ว เวลาที่จะ debug โปรแกรมของ Android เราจะมีสองทางเลือก ระหว่าง

  1. Deploy บนเครื่องจริง
  2. กับ ใช้ Android Virtual Device

ซึ่งทั้งสองทางเลือกนั้นต่างก็มีข้อเสีย ในขณะที่เครื่องจริงนั้นเราอาจจะไม่ได้อยากพกไปด้วย (เกะกะ) แถมต้องต่อสายมากมาย ส่วน AVD นั้นก็อืดอภิมหาอืด

หลังจากที่หาวิธี Debug ที่สะดวกกว่าการใช้อุปกรณ์จริง แต่ก็ทำได้งานได้เร็วพอที่จะใช้งานได้จริง (เพราะโน๊ตบุ๊คผมนั้นมันอืดเป็นปรกติอยู่แล้ว พอรัน AVD เข้าไปก็จะยิ่งอืดเข้าไปอีก) ก็พบว่ามีอีกวิธีนึงก็คือการรัน Android บน x86-based Virtual Machine โดยอาจจะเป็น VMWare หรือ Oracle VirtualBox หรืออื่น ๆ ก็ได้

...continue reading Debug โปรแกรมของ Android บน x86 VM (VirtualBox)