แนะนำ C++11 - มีอะไรใหม่ มีอะไรดี

ก่อนอื่นหลายคนอาจจะมีคำถามที่ว่าแล้ว C++11 คืออะไร? ต่างจาก C++ ธรรมดายังไง? คำตอบก็คือว่า C++11 เป็นมาตรฐานของ C++ ตัวใหม่ครับ ซึ่งเป็นมาตรฐานที่ออกมาในปี 2011 คือหมายถึงเถียงกันเสร็จเรียบร้อยแล้วว่าจะให้มีหรือไม่มีอะไรบ้าง อย่างไร ซึ่งถ้าย้อนกลับไปตอนแรกเหมือนมาตรฐานนี้จะใช้ชื่อแบบลำลองว่า C++0x นั่นไม่แน่ใจว่าจะเสร็จในปีไหนแต่คงมี 200x นี่แหละ ซึ่งพอเอาเข้าจริงก็ลากยากมาถึงปี 2011 เลยทีเดียว ถึงขนาดมีคนเล่นมุกว่า 0x ที่ว่าคือ 0B (เลขฐาน 16) นั่นเอง (ฮา)

ตอนที่กำลังเขียนอยู่นี่ก็ต้นปี 2016 เข้าไปแล้ว จริงๆแล้วมันก็มีมาตรฐานใหม่ออกมาเพิ่มอีกแหละครับคือ C++14 กับ C++17 แต่ว่าคราวนี้เรามาดูกันก่อนว่าไอ้เจ้า C++11 นี่มันมีอะไรต่างจากเดิมมั่ง มันเจ๋งขึ้นจริงๆ รึเปล่า?

ก่อนที่จะใช้ C++11 เนี่ยก็จำเป็นที่จะต้องใช้ตัวคอมไพเลอร์ที่ Implement ฟีเจอร์ต่างๆของ C++11 ได้ทั้งหมดแล้ว ซึ่งหาไม่ยากเลยครับ g++, clang ตัวใหม่ หรือจะเป็นคอมไพเลอร์ของ Intel และ Microsoft ก็ใช้ได้หมดแล้ว สามารถดูรายละเอียดว่ามีตัวไหนใช้อะไรได้บ้างที่ C++ compiler support เลยครับ

ทีนี้เรามาดูกันเลยดีกว่าว่ามันมีอะไรที่ทำให้ชีวิตดีขึ้นบ้าง

 

1. auto

มันคือการที่เราสามารถกำหนดชนิดตัวแปรเป็น auto แล้วให้คอมไพเลอร์ไปหาเองว่ามันควรจะเป็นชนิดอะไรครับ พูดแล้วเห็นภาพยาก ลองดูตัวอย่างดีกว่า

auto x = 20; 
auto y = 4.5; 
auto z = 'A';

จากตัวอย่างบรรทัดแรกคือ เราไม่ต้องประกาศตัวแปร x เป็นชนิด int ให้เห็นแต่ว่า ใช้ auto ไปเลย คอมไพลเอร์จะเดาชนิดให้เองจาก ชนิดของด้านขวามือ ซึ่งก็คือ int เพราะมันเห็นว่า 20 เป็น int ครับ ตัวแปร y ก็จะหลายเป็น double และ z ก็จะเป็น char แน่นอนว่าคีย์เวิร์ด auto ก็มีข้อจำกัดอยู่ เช่นถ้าเราไม่มีการกำหนดค่ามันก็จะเดาชนิดให้ไม่ออกครับ เช่นประกาศ auto s; แบบนี้ไม่ได้แน่นอนเพราะไม่มีชนิดในมันอ้างอิง (ยังไงตัวแปรใน C++ ก็ต้องกำหนดชนิดข้อมูล เหมือนเดิมครับ เพียงแต่ว่าเราแค่ปล่อยให้มันไปหาเองได้ว่าควรจะเป็นชนิดอะไร) 

ตัวอย่างเมื่อกี้เป็นแค่ตัวอย่างให้เห็นภาพครับ ยังไม่ใช่ประโยชน์ที่แท้จริงเท่าไหร่ เพราะมันดูไม่ช่วยอะไรเท่าไหร่ มันจะช่วยได้มากในกรณีต่อไปนี้ครับ

vector<int> v;
/* คำสั่งอื่นๆ */
vector<int>::iterator it = v.begin();

vector< map<int,string> > mapv;
/* คำสั่งอื่นๆ */
map<int,string> m = mapv[5];

การประกาศชนิดของ iterator หรือว่าชนิดที่เป็น template ทั้งหลายจะสูญเสียพลังงานในการพิมพ์มาก เราก็จะเปลี่ยนเป็นใช้ auto แทน ซึ่งเสียเวลาพิมพ์น้อยกว่า โค้ดก็อ่านง่ายสบายตากว่าเดิมเยอะ เช่นแบบนี้ครับ

vector<int> v;
/* คำสั่งอื่นๆ */
auto = v.begin();

vector< map<int,string> > mapv;
/* คำสั่งอื่นๆ */
auto m = mapv[5];

 

2. แก้ไขการ parse <>

อันนี้นึกไม่ออกว่าจะเรียกมันว่าอะไร แต่ C++ มีข้อจำกัดที่โคตรจะน่ารำคาญอย่างนึงคือเวลาเขียนชนิดที่เป็น template ซ้อนกันจะต้องเว้นวรรคระหว่าง > อย่างน้อย 1 ทีครับ เช่น ถ้าเราอยากจะสร้าง matrix ขึ้นมาอันนึงเราอาจจะใช้ vector<vector<int>> mat; แต่ว่าเขียนแบบนี้ถือว่าผิด เพราะคอมไพเลอร์จะเข้าใจว่าไอ้ >> ติดกันนี่คือโอเปอเรเตอร์ >> ซึ่งถ้าจะให้ถูกจะต้องเขียนว่า vector< vector<int> > mat; เพื่อกันมันสับสน

แต่ว่า C++11 ไม่โง่แล้วครับ เขียนไปได้เลย vector<vector<int>> มันไม่มึนแล้ว ชีวิตสดใสขึ้นเยอะเลย

 

3. ลูป for แบบ range

ภาษาสมัยใหม่มันก็มีกันหมดแล้วล่ะ ก็เลยเป็นอีก 1 ฟีเจอร์ที่ขาดไปไม่ได้ จากที่แต่ก่อนเราอาจจะต้องวนลูปตัวอาร์เรย์หรือ containner ต่างๆ แล้วค่อย get ค่ามาจากการใช้ index แบบนี้

vector<int> v;
for (int i = 0; i < v.size(); ++i) {
    int x = v[i];
    /* เอาค่า x ไปทำอะไรซักอย่าง */
}

for (vector<int>::iterator it = v.begin(); 
     it != v.end(); ++it) {
    int x = *it;
    /* เอาค่า x ไปทำอะไรซักอย่าง */
}

พอเห็นแล้วก็จะรู้สึกว่ายาวเหลือเกิน จริงๆมันมันเขียนแบบสั้นๆ แต่ได้ความหมายเหมือนเดิม ด้วยการใช้ลูปแบบ range ตามนี้เลยครับ

for (int x : v) {
    /* เอาค่า x ไปทำอะไรซักอย่าง */
}

หรือจะเอา auto มาใช้ก็ได้ เกิดถ้าชนิดของ element ใน container มันยาว

vector< map<int,string> > mapv;
for (auto m : mapv) {
    /* เอาค่า m ไปทำอะไรซักอย่าง */
}

 

4. List initialization

อีกตัวที่ผมรู้สึกว่ามันควรจะมีมานานมากแล้ว เพราะเขียน C++ แล้วอึดอัดเหลือเกินกับปัญหาการกำหนดค่าให้ container ลองดูตัวอย่างต่อไปนี้นะครับ

int a[5] = {1,2,3,4,5}; /* ทำได้ */
vector<int> v = {1,2,3,4,5}; /* ทำไม่ได้ */

/* ถ้าจะได้ต้องเอาอาร์เรย์มากำหนดให้มันอีกต่อนึงแบบนี้ */
vector<int> v(a,a+5);

ปัญหาก็ประมาณนี้แหละครับ คือมันกำหนดค่าเริ่มต้นยากมาก ดังนั้นใน C++11 ก็เลยยอมให้ทำได้เรียบร้อยแล้ว

vector<int> v = {1,2,3,4,5}; /* ทำได้ใน C++11 */
vector<int> v2{1,2,3,4,5}; /* แบบนี้ก็ได้ (C++11) */

/* กำหนดค่าไปแล้วอยากเปลี่ยนกลางอากาศก็ทำได้ สุดยอด!! */
v = {100,200,300}; 

ผมก็สุดยอดไปงั้นแหละครับ พวกภาษาสมัยใหม่มันเขียนได้อยู่แล้ว แต่ C++ มันทำไม่ได้มาอย่างนาน พอได้แล้วมันก็ต้องดีใจบ้าง (ฮา) นอกจากนี้ยังกำหนดค่าแบบ nested ได้ด้วยนะ อย่างเช่นพวก map

map<int,string> m = {
    {1, "one"},
    {2, "two"},
    {3, "three"}
};

 

5. Lambda functions

Labmda functions ถ้ามองผ่านก็คือฟังก์ชันที่ไม่มีชื่ออันนึง แต่จริงๆแล้วมันมีอะไรมากกว่าฟังก์ชันธรรมดาครับ มันมีส่วนที่เรียกว่า capture ซึ่งสามารถนำ context ภายนอก เช่นตัวแปรต่างๆเข้าไปใช้ในฟังก์ชันที่สร้างขึ้นได้ ซึ่งขอไม่ลงลึกนะครับ ไปดูตัวอย่างการใช้แบบง่ายๆกันก่อนดีกว่า

bool moreThan10(int x) { return x>10; }

vector<int> v = {6,5,10,13,25,1,25,23,13,5,8,90};
int c = count_if(v.begin(), v.end(), moreThan10);

ฟังก์ชัน count_if ใน <algorithm> ใช้สำหรับนับว่าใน container มีข้อมูลกี่ตัวที่ตรงกับเงื่อนไขที่กำหนดให้ ซึ่งเราจะต้องส่งฟังก์ชันสำหรับกำหนดเงื่อนไขเข้าไปตัวนึง ทีนี้ Lambdas จะมีประโยชน์ตรงนี้แหละครับ คือมันสามารถสร้างฟังก์ชันที่ไม่มีชื่อแล้วส่งเข้าไปตรงนั้นได้เลย

vector<int> v = {6,5,10,13,25,1,25,23,13,5,8,90};
int c = count_if(v.begin(), v.end(), [](int x){return x>10;});

ซึ่งมันจะเหมาะมากกับฟังก์ชันแบบที่ใช้ครั้งเดียวแล้วก็เลิกลากันไป ไม่ต้องมาประกาศให้กวนใจกันอีกครับ

ตรง [] คือส่วนของ capture ที่บอกว่าจะเอาตัวแปรอะไรเข้าไปใช้ในฟังก์ชันบ้าง ซึ่งในที่นี้เป็นว่างเพราะเราไม่เอาอะไรเข้าไปเลย (int x) อันนี้ก็พารามิเตอร์ของฟังก์ชันเฉยๆ

สมมติว่าอยากให้เช็คว่ามีกี่ตัวบ้างที่มีค่ามากกว่า q

vector<int> v = {6,5,10,13,25,1,25,23,13,5,8,90};
int q = 8;
int c = count_if(v.begin(), v.end(), [q](int x){return x>q;});

ตรง capture ก็ใส่ตัวแปร q เข้าไป เพื่อบอกว่าให้เอาตัวแปร q ไปใช้ในฟังก์ชันด้วย ซึ่งความหมายของคำสั่งดังกล่าวคือนับจำนวนของข้อมูลใน vector v ที่มากกว่า q (ซึ่งมีค่าเท่ากับ 8)

จาก concept ของ lambdas ทำให้ประกาศตัวแปรฟังก์ชันใน C++11 ได้ง่ายขึ้นมากครับ (เมื่อก่อนมันก็ทำได้แต่ลำบาก) เช่นจากตัวอย่างแรกสุด เราก็จะได้โปรแกรมใหม่หน้าตาแบบนี้ ใช้ชนิดเป็น auto ไปได้เลยครับ

auto moreThan10 = [](int x){return x>10;};
int c = count_if(v.begin(), v.end(), moreThan10);

 

อื่นๆ

  • เพิ่มฟังก์ชันที่เกี่ยวกับ Regular Expression ใน <regex>
  • เพิ่มฟังก์ชันที่เกี่ยวกับการสุ่มใน <random>
  • Raw string literals ที่เขียน string literal แบบหลายบรรทัดได้ง่ายขึ้น
  • คีย์เวิร์ด override ที่เอาไว้บอกว่าฟังก์ชันนี้เป็นการ overriding และคีย์เวิร์ด final ที่ทำให้ฟังก์ชันไม่สามารถ overriding ต่อได้ (หรือกำหนดให้ class ไม่สามารถ inherite ต่อได้)  
  • มีฟังก์ชันใหม่ๆอีกมากมาย, smart pointer, enum แบบใหม่, tuple, การจัดการ thread, atomic operator และอื่นๆอีกมากมายครับ
สรุปง่ายๆคือถ้าใช้ C++ อยู่ ลองมาใช้ C++11 กันดูครับ มันสะดวกขึ้นเยอะเลย (สรุปโคตรสั้น 55)

Taxonomy upgrade extras: