ข้ามไปที่เนื้อหา

Ch21: STL & Ranges

TL;DR: STL Containers & Adaptors: C++ เตรียมโครงสร้างข้อมูลพื้นฐานหลากหลายรูปแบบ ทั้งจัดลำดับติดกันในหน่วยความจำ (vector, array), รายการเชื่อมโยง (list, forward_list), โครงสร้างแบบต้นไม้และตารางแฮช (set, map) รวมถึงอแดปเตอร์แบบเข้าหลังออกก่อน LIFO/FIFO (stack, queue, priority_queue) | แตกต่างจากคอนเทนเนอร์แบบลำดับ (Sequence Containers เช่น vector, list) ที่จัดเก็บข้อมูลตามลำดับตำแหน่ง คอนเทนเนอร์แบบจับคู่เชื่อมโยง (Associative Containers เช่น map, set รวมถึงกลุ่มที่ขึ้นต้นด้วย unordered_) จะจัดเก็บข้อมูลโดยใช้คีย์ (Key) ช่วยให้ค้นหาข้อมูลได้อย่างรวดเร็ว แลกกับการไม่มีการรับประกันลำดับการป้อนข้อมูล (สำหรับกลุ่ม unordered_) หรือการเรียงลำดับข้อมูลเสมอ (สำหรับกลุ่มปกติที่เรียงลำดับ)

⚡ Quick Reference

#include <iostream>
#include <vector>
#include <array>
#include <deque>
#include <list>
#include <forward_list>
#include <set>
#include <map>
#include <unordered_set>
#include <unordered_map>
#include <stack>
#include <queue>
#include <algorithm>
#include <ranges>

int main() {
    std::vector<int> v = {10, 20, 30};
    std::array<int, 3> arr = {1, 2, 3};

    auto start = v.begin();
    auto stop = v.end();

    std::deque<int> d = {1, 2};
    std::list<int> lst = {5, 6};
    std::forward_list<int> fl = {7, 8};

    std::set<int> s = {3, 1, 2};
    std::map<int, std::string> m = {{1, "one"}};

    std::ranges::sort(v);

    auto lazy_view = v | std::views::filter([](int x){ return x > 10; })
                       | std::views::transform([](int x){ return x * 2; });

    return 0;
}

คอนเทนเนอร์แบบจับคู่เชื่อมโยง (Associative Containers)

#include <iostream>
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <string>

int main() {
    // std::map<K, V> - เรียงตามคีย์ (โครงสร้างแบบ Red-Black Tree) ประสิทธิภาพการทำงานระดับ O(log n)
    std::map<std::string, int> ages;
    ages["Alice"] = 30;
    ages.insert({"Bob", 25});

    // .find() - ส่งคืนตัวอ้างอิงตำแหน่ง (Iterator) หรือ .end() หากหาไม่พบ (ไม่มีการแจ้งเตือน Exception)
    if (auto it = ages.find("Alice"); it != ages.end()) {
        std::cout << it->first << " is " << it->second << '\n';
    }

    // .at() - โยนข้อผิดพลาด std::out_of_range หากไม่พบคีย์ (ตรงข้ามกับ operator[] ที่จะป้อนค่าเริ่มต้นให้โดยอัตโนมัติ)
    try {
        std::cout << ages.at("Charlie") << '\n';
    } catch (const std::out_of_range&) {
        std::cout << "Charlie not found\n";
    }

    // std::set<T> - คอนเทนเนอร์เก็บข้อมูลเรียงลำดับและมีค่าไม่ซ้ำกันเท่านั้น
    std::set<int> uniqueNums{5, 3, 5, 1, 3};   // ข้อมูลที่ซ้ำกันจะถูกละเลยโดยไม่แจ้งเตือน -> {1, 3, 5}

    // std::unordered_map<K, V> - ตารางแฮช (Hash Table) ความเร็วการค้นหาเฉลี่ยระดับ O(1) ไม่รับประกันลำดับข้อมูล
    std::unordered_map<std::string, int> fastLookup;
    fastLookup["x"] = 1;

    // std::unordered_set<T> - คอนเทนเนอร์แบบเซ็ตในรูปแบบตารางแฮช
    std::unordered_set<int> fastUnique{5, 3, 5, 1};

    // การวนลูปผ่าน map ด้วย Range-based for จะได้ข้อมูลเป็นคู่ Key-Value
    for (const auto& [name, age] : ages) {
        std::cout << name << ": " << age << '\n';
    }

    return 0;
}

🧠 Core Concepts

  • STL Containers & Adaptors: C++ เตรียมโครงสร้างข้อมูลพื้นฐานหลากหลายรูปแบบ ทั้งจัดลำดับติดกันในหน่วยความจำ (vector, array), รายการเชื่อมโยง (list, forward_list), โครงสร้างแบบต้นไม้และตารางแฮช (set, map) รวมถึงอแดปเตอร์แบบเข้าหลังออกก่อน LIFO/FIFO (stack, queue, priority_queue)
  • Ranges & Adaptor Views: คลัง Ranges ของ C++20 ช่วยอำนวยความสะดวกในการจัดระเบียบตรรกะประมวลผลแทนการระบุอ้างอิง iterator คู่ โดยใช้กลไกของ Views (filter, transform, take, iota) เพื่อดึงผลลัพธ์แบบ Lazy (ดึงค่าเมื่อใช้) ผ่านตัวดำเนินการ Pipe (|)
  • Custom Iterator Protocols: ออบเจกต์ Iterator สำหรับคลาสของเราจะต้องประกาศสิทธิการเข้าถึง 5 แบบที่ระบุตัวแปรประเภทข้อมูล และทำโอเปอเรเตอร์อ้างอิง ดึงค่า เปลี่ยนตำแหน่ง เพื่อรองรับการนำไปใช้ร่วมกับฟังก์ชันมาตรฐานของ STL
  • Ordered vs Unordered: คอนเทนเนอร์ std::map/std::set จะเรียงลำดับข้อมูลสมาชิกตามคีย์ (มักใช้โครงสร้างแบบ Red-Black Tree) มอบประสิทธิภาพการทำงานที่ O(log n) และการวนรอบข้อมูลตามลำดับคีย์ ส่วน std::unordered_map/std::unordered_set จะใช้ตารางแฮช (Hash Table) เพื่อประสิทธิภาพการทำงานเฉลี่ยที่ O(1) แต่ไม่รับประกันลำดับข้อมูลสมาชิก
  • Key Uniqueness: คอนเทนเนอร์กลุ่ม map/set/unordered_map/unordered_set บังคับให้คีย์ต้องไม่ซ้ำกันเท่านั้น — การเขียนคีย์ซ้ำจะถูกละเลยการทำงาน (ค่าเดิมยังคงอยู่) หากต้องการคีย์ซ้ำกันได้ให้เลือกใช้งานคลาสประเภท multimap/multiset แทน
  • operator[] vs .at(): การใช้ operator[] บน map หากไม่มีคีย์นั้นอยู่ระบบจะแอบสร้าง Default Constructor และแทรกคีย์ใหม่ให้ทันที — แม้จะสะดวกแต่มักเป็นต้นตอที่ซ่อนบั๊กเอาไว้ ส่วนคำสั่ง .at() จะโยนข้อผิดพลาด std::out_of_range ออกมาแทน ทำให้สามารถดักจับจุดบกพร่องเรื่องคีย์สูญหายได้ง่ายขึ้น
  • Structured Bindings with Maps: การวิ่งวนลูปใน map จะส่งคืนสมาชิกประเภท std::pair<const Key, Value> ซึ่งการนำ Structured Bindings (ตั้งแต่ C++17 ขึ้นไป) มาใช้ เช่น for (const auto& [key, value] : myMap) จะช่วยแกะคู่ข้อมูลออกมาใช้งานได้อย่างสวยงาม

⚠️ Pitfalls (Quick Scan)

ข้อผิดพลาด วิธีแก้
การประกาศขยายขนาดของ Vector ด้วยการใช้วงเล็บปีกกา (เช่น vector<int>{20, 55}) เปลี่ยนไปใช้วงเล็บธรรมดาในการป้อนคำสั่ง vector<int>(20, 55) เพื่อกำหนดขนาดช่อง
ดึงข้อมูลหรือเลื่อนตำแหน่งของ Iterator ให้เลยช่วงขอบเขตสิ้นสุด end() ตรวจสอบเงื่อนไข it != container.end() ให้มั่นใจก่อนดึงค่าใช้งาน
แก้ไขหรือลบสมาชิกใน Vector ระหว่างที่มีลูปดึงข้อมูลจาก Iterator จัดระบบพิกัดใหม่หรือดึงตำแหน่งขาส่งกลับมาจากฟังก์ชันลบแก้ไขเพื่อปรับค่า
อ่านค่าจากคีย์ที่ไม่มีอยู่ใน std::map ผ่านตัวดำเนินการ operator[] ใช้เมธอด .find() หรือ .at() ในการอ่านค่า และสงวน operator[] สำหรับการอัปเดตข้อมูลเท่านั้น
เรียกใช้คำสั่งคัดลอกข้อมูล std::copy หรือแปลงค่าไปยัง Container เป้าหมายที่ไม่มีการจองพื้นที่ ตั้งขนาดจองพื้นที่ของออบเจกต์ปลายทางล่วงหน้า หรือประยุกต์ใช้ std::back_inserter เพื่อช่วยขยายขนาดยามเขียน
ตั้งเงื่อนไขเปรียบเทียบที่ไม่เข้มงวดพอ (เช่น ใช้เครื่องหมาย <=) ในฟังก์ชันคัสตอมสำหรับ std::sort เขียนตรรกะเปรียบเทียบโดยใช้เฉพาะเครื่องหมายน้อยกว่า < เสมอ
ไม่ได้จำกัดขนาดความยาวของลำดับที่เจเนอเรตมาจาก std::views::iota ดักควบคุมขนาดข้อมูลโดยพ่วงด้วยฟังก์ชันควบคุม เช่น std::views::take
ลืมสร้างและส่งคืนค่าสำเนาออบเจกต์ในคำสั่ง Post-increment สำหรับ Custom Iterator ทำการสำเนาออบเจกต์เดิมไว้ก่อน ขยับตำแหน่งปัจจุบันขึ้น แล้วส่งคืนตัวสำเนาตัวเดิมกลับไป
ไม่ได้แยกประเภทระหว่างตัวแปรอ่านแก้ไขได้ทั่วไปและตัวแปรแบบ const สำหรับ Custom Iterator ออกแบบคลาส ConstIterator แยกออกมาต่างหาก หรือประยุกต์ใช้ raw pointer const T* เป็นตัวช่วยสืบค้น
ใช้ operator[] เพื่อเช็คว่ามีคีย์อยู่หรือไม่ คำสั่งนี้จะส่งผลข้างเคียงโดยการสร้างและแทรกค่าเริ่มต้นให้ทันที — ให้ใช้ .find() หรือ .contains() แทน
คาดหวังให้ลำดับการวนลูปของ unordered_map คงที่หรือเรียงตามลำดับที่ใส่เข้าไป ไม่มีการรับประกันลำดับข้อมูลใดๆ — ให้ใช้ std::map แทนหากลำดับมีความสำคัญ
คิดว่าการแทรกข้อมูลคีย์ซ้ำจะเขียนทับค่าเดิมที่มีอยู่ คำสั่ง insert() จะไม่ทำงานเลยเมื่อคีย์ซ้ำ — ให้ใช้ operator[] หรือ insert_or_assign() เพื่อเขียนทับข้อมูลแทน
ใช้ Struct ที่ปรับเปลี่ยนค่าได้เป็นสมาชิกใน std::set และแก้ไขค่าสมาชิกโดยตรง การแก้ไขข้อมูลตรงๆ จะทำลายระเบียบการเรียงลำดับของต้นไม้ในโครงสร้างข้อมูล — ให้ลบสมาชิกเดิมออกแล้วค่อยป้อนค่าใหม่เข้าไปแทน

📖 Full Details

Cause → Effect → Fix พร้อม timestamp (คลิกเพื่อดู) * **การประกาศขยายขนาดของ Vector ด้วยการใช้วงเล็บปีกกา (เช่น `vector{20, 55}`)** -> **ได้อาร์เรย์ที่มีสมาชิกสองตัวคือ 20 และ 55 แทนที่จะสร้างอาร์เรย์ขนาด 20 ช่องที่เก็บเลข 55** -> **เปลี่ยนไปใช้วงเล็บธรรมดาในการป้อนคำสั่ง `vector(20, 55)` เพื่อกำหนดขนาดช่อง (115:00)** * **ดึงข้อมูลหรือเลื่อนตำแหน่งของ Iterator ให้เลยช่วงขอบเขตสิ้นสุด `end()`** -> **เกิดปัญหา Undefined behavior, โปรแกรมแครช หรืออ่านขยะจากแรม** -> **ตรวจสอบเงื่อนไข `it != container.end()` ให้มั่นใจก่อนดึงค่าใช้งาน (116:00)** * **แก้ไขหรือลบสมาชิกใน Vector ระหว่างที่มีลูปดึงข้อมูลจาก Iterator** -> **ทำให้ Iterator ตัวที่ชี้ไปยังตำแหน่งหลังจากตัวที่ถูกลบกลายเป็นตำแหน่งขยะ (Invalidated)** -> **จัดระบบพิกัดใหม่หรือดึงตำแหน่งขาส่งกลับมาจากฟังก์ชันลบแก้ไขเพื่อปรับค่า (120:00)** * **อ่านค่าจากคีย์ที่ไม่มีอยู่ใน `std::map` ผ่านตัวดำเนินการ `operator[]`** -> **Compiler จะลอบสร้างค่าขึ้นมาให้เงียบๆ โดยใช้ค่าเริ่มต้น (Default constructor) ส่งผลให้โครงสร้าง Map บวมโดยใช่เหตุ** -> **ใช้เมธอด `.find()` หรือ `.at()` ในการอ่านค่า และสงวน `operator[]` สำหรับการอัปเดตข้อมูลเท่านั้น (126:00)** * **เรียกใช้คำสั่งคัดลอกข้อมูล `std::copy` หรือแปลงค่าไปยัง Container เป้าหมายที่ไม่มีการจองพื้นที่** -> **เกิดการเขียนทับพิกัดภายนอกออบเจกต์ นำไปสู่การเข้าแรมผิดพลาดและโปรแกรมแครช** -> **ตั้งขนาดจองพื้นที่ของออบเจกต์ปลายทางล่วงหน้า หรือประยุกต์ใช้ `std::back_inserter` เพื่อช่วยขยายขนาดยามเขียน (134:00)** * **ตั้งเงื่อนไขเปรียบเทียบที่ไม่เข้มงวดพอ (เช่น ใช้เครื่องหมาย `<=`) ในฟังก์ชันคัสตอมสำหรับ `std::sort`** -> **เกิดการวนลูปไม่รู้จบ โปรแกรมค้าง หรือส่งผลให้การเรียงลำดับผิดเพี้ยน** -> **เขียนตรรกะเปรียบเทียบโดยใช้เฉพาะเครื่องหมายน้อยกว่า `<` เสมอ (137:00)** * **ไม่ได้จำกัดขนาดความยาวของลำดับที่เจเนอเรตมาจาก `std::views::iota`** -> **ส่งผลให้เกิดการประมวลผลวนซ้ำไปเรื่อยๆ จนโปรแกรมค้าง** -> **ดักควบคุมขนาดข้อมูลโดยพ่วงด้วยฟังก์ชันควบคุม เช่น `std::views::take` (145:00)** * **ลืมสร้างและส่งคืนค่าสำเนาออบเจกต์ในคำสั่ง Post-increment สำหรับ Custom Iterator** -> **จะส่งผลให้ตำแหน่งขยับขึ้นเลยแต่โปรแกรมไม่ได้ค่าออบเจกต์เดิมมาอ้างอิงตามหลักการทั่วไป** -> **ทำการสำเนาออบเจกต์เดิมไว้ก่อน ขยับตำแหน่งปัจจุบันขึ้น แล้วส่งคืนตัวสำเนาตัวเดิมกลับไป (192:00)** * **ไม่ได้แยกประเภทระหว่างตัวแปรอ่านแก้ไขได้ทั่วไปและตัวแปรแบบ const สำหรับ Custom Iterator** -> **เกิด Compile-time error เมื่อนำคลาสไปใช้งานร่วมกับออบเจกต์ประเภท const** -> **ออกแบบคลาส `ConstIterator` แยกออกมาต่างหาก หรือประยุกต์ใช้ raw pointer `const T*` เป็นตัวช่วยสืบค้น (194:00)** * **ใช้ `operator[]` เพียงเพื่อตรวจสอบว่ามีคีย์อยู่หรือไม่** -> **ระบบจะสร้างออบเจกต์ค่าเริ่มต้นและเพิ่มข้อมูลสมาชิกใหม่เข้าไปในคอนเทนเนอร์เงียบๆ ซึ่งเป็นผลข้างเคียงของการค้นหา ทำให้คอนเทนเนอร์ขยายขนาดโดยไม่ตั้งใจและทำลายตรรกะที่อิงตามขนาดข้อมูล** -> **ใช้คำสั่ง `.find(key) != container.end()` หรือ `.contains(key)` (ตั้งแต่ C++20 ขึ้นไป) เพื่อตรวจสอบการมีอยู่โดยไม่สร้างความเปลี่ยนแปลงแก่ข้อมูล** * **คาดหวังให้ `std::unordered_map` เก็บข้อมูลเรียงลำดับตามที่ใส่เข้าไป หรือให้ลำดับการวนลูปเหมือนเดิมทุกครั้งที่รันโปรแกรม** -> **การจัดกลุ่มบัคเก็ตของตารางแฮชอาจแตกต่างกันในการทำงานแต่ละครั้ง ขึ้นกับตัวคอมไพเลอร์ หรือหลังจากทำกระบวนการจัดแฮชใหม่ (Rehashing) ลำดับการทำงานจึงไม่แน่นอน** -> **ใช้ `std::map` (ที่จัดเรียงตามคีย์) แทน หากคุณต้องการลำดับข้อมูลที่ทำนายคาดเดาได้** * **คิดว่าคำสั่ง `.insert()` จะเขียนทับข้อมูลของคีย์ที่มีอยู่เดิม** -> **คำสั่ง `insert()` จะไม่ทำงานใดๆ เลยหากพบคีย์ซ้ำกันในระบบ — ส่งผลให้ข้อมูลเดิมยังคงอยู่โดยไม่ได้รับการอัปเดต** -> **ใช้ `operator[]` ในการเขียนทับค่าใหม่ หรือใช้งานคำสั่ง `.insert_or_assign()` (ตั้งแต่ C++17 ขึ้นไป) เพื่อระบุเจตนาที่ชัดเจน** * **แก้ไขค่าของออบเจกต์ที่จัดเก็บใน `std::set` โดยตรง (เช่น ผ่านการแฮกตัววนซ้ำที่ไม่ใช่ const)** -> **ทำลายกฎระเบียบการเรียงลำดับสมาชิกภายในระบบโครงสร้างต้นไม้ เนื่องจากตัวโครงสร้างคาดหวังว่าคีย์จะไม่มีวันเปลี่ยนแปลงหลังจากแทรกแล้ว ส่งผลให้เกิดพฤติกรรมที่ไม่คาดคิดในการค้นหา (Undefined Behavior)** -> **ลบข้อมูลตัวเก่าทิ้งออกไปก่อน แล้วแทรกออบเจกต์ใหม่เข้าไปแทนที่การแก้ไขโดยตรง** * **ใช้ Struct หรือ Class ที่แต่งขึ้นมาเองเป็นคีย์ใน `unordered_map`/`unordered_set` โดยไม่ได้เขียนฟังก์ชันแฮช** -> **การคอมไพล์ล้มเหลวเนื่องจากตัว Standard Library ไม่มีรูปแบบฟังก์ชัน `std::hash` เริ่มต้นสำหรับข้อมูลที่ผู้ใช้นิยามเอง** -> **กำหนดฟังก์ชันเฉพาะสำหรับเทมเพลต `std::hash` หรือส่ง Hasher ที่เขียนขึ้นเองเป็นพารามิเตอร์เทมเพลตตัวที่สองให้กับตัวคอนเทนเนอร์**

📎 Repo Files

  • 43.StlContainersAndIterators/43.2StdVector/main.cpp
  • 43.StlContainersAndIterators/43.3StdArray/main.cpp
  • 43.StlContainersAndIterators/43.4Iterators/main.cpp
  • 43.StlContainersAndIterators/43.6ReverseIterators/main.cpp
  • 43.StlContainersAndIterators/43.7ContantIterators/main.cpp
  • 43.StlContainersAndIterators/43.9StdBeginStdEnd/main.cpp
  • 44.ZoomingOnSTLContainers/44.2Deque/main.cpp
  • 44.ZoomingOnSTLContainers/44.3ForwardList/main.cpp
  • 44.ZoomingOnSTLContainers/44.4List/main.cpp
  • 44.ZoomingOnSTLContainers/44.5Vector/main.cpp
  • 44.ZoomingOnSTLContainers/44.6Array/main.cpp
  • 44.ZoomingOnSTLContainers/44.8Pair/main.cpp
  • 44.ZoomingOnSTLContainers/44.9Set/main.cpp
  • 44.ZoomingOnSTLContainers/44.10Map/main.cpp
  • 44.ZoomingOnSTLContainers/44.11MultisetMultimap/main.cpp
  • 44.ZoomingOnSTLContainers/44.12UnorderedSetUnorderedMap/main.cpp
  • 44.ZoomingOnSTLContainers/44.14Stack/main.cpp
  • 44.ZoomingOnSTLContainers/44.15Queue/main.cpp
  • 44.ZoomingOnSTLContainers/44.16PriorityQueue/main.cpp
  • 45.StlAlgorithms/45.2AllOf/main.cpp
  • 45.StlAlgorithms/45.3ForEach/main.cpp
  • 45.StlAlgorithms/45.4.MaxEltMinElt/main.cpp
  • 45.StlAlgorithms/45.5FindAndFindIf/main.cpp
  • 45.StlAlgorithms/45.6Copy/main.cpp
  • 45.StlAlgorithms/45.7Sort/main.cpp
  • 45.StlAlgorithms/45.8Transform/main.cpp
  • 46.RangesLibraryInCpp20/46.2RangeAlgorithms/main.cpp
  • 46.RangesLibraryInCpp20/46.4Projections/main.cpp
  • 46.RangesLibraryInCpp20/46.5ViewsAndRangeAdaptors/main.cpp
  • 46.RangesLibraryInCpp20/46.6ViewCompositionAndPipeOperator/main.cpp
  • 46.RangesLibraryInCpp20/46.7RangeFactories/main.cpp
  • 47.BuildingIteratorsForCustomContainers/40.02.IteratorPowers/main.cpp
  • 47.BuildingIteratorsForCustomContainers/40.04CustomInputIterator/main.cpp
  • 47.BuildingIteratorsForCustomContainers/40.05CustomOutputIterator/main.cpp
  • 47.BuildingIteratorsForCustomContainers/40.06CustomForwardIterator/main.cpp
  • 47.BuildingIteratorsForCustomContainers/40.07CustomBidirectionalIteator/main.cpp
  • 47.BuildingIteratorsForCustomContainers/40.08CustomRandomAccessIterator/main.cpp
  • 47.BuildingIteratorsForCustomContainers/40.09CustomIteratorsWithViews/main.cpp
  • 47.BuildingIteratorsForCustomContainers/40.10ConstantIterators/main.cpp
  • 47.BuildingIteratorsForCustomContainers/40.11RawPointersAsIterators/main.cpp
  • 47.BuildingIteratorsForCustomContainers/40.12WrappingIteratorsFromOtherContainers/main.cpp
  • 43.StlContainersAndIterators/main.cpp
  • 44.ZoomingOnSTLContainers/main.cpp