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

Ch12: Getting Things out of Functions

TL;DR: Output Parameters: ฟังก์ชันสามารถส่งข้อมูลออกผ่านตัวแปรระดับ Caller โดยการระบุพารามิเตอร์แบบ non-const reference (T&) หรือ pointer (T*) เพื่อเขียนข้อมูลลงในหน่วยความจำโดยตรง

⚡ Quick Reference

#include <iostream>
#include <string>

// รูปแบบการป้อนพารามิเตอร์ Input, Output และ In-Out
void process(const int& input, int* output, int& in_out) {
    if (output) {
        *output = input * 2; // Output parameter เขียนผลลัพธ์ลงพอยน์เตอร์
    }
    in_out += input; // In-Out parameter อ่านข้อมูลและแก้ไขในเรฟเฟอเรนซ์
}

// Returning by value: คืนค่าแบบส่งค่ากลับ (ส่งผลลัพธ์ผ่านการก๊อปปี้หรือ RVO)
int get_value() { return 42; }

// Returning by pointer: คืนค่าเป็นแอดเดรสของทรัพยากรที่ถาวรหรือบน heap
int* get_ptr() {
    static int val = 10;
    return &val;
}

// Returning by reference: คืนค่าเป็นเรฟเฟอเรนซ์ของตัวแปรถาวรเพื่อใช้อ้างอิงต่อ
int& get_ref() {
    static int val = 20;
    return val;
}

// auto - ให้ compiler ช่วยคาดเดาชนิดข้อมูลขาส่งกลับของฟังก์ชัน
auto deduce() { return 3.14; }

int main() {
    int in = 5, out = 0, io = 10;
    process(in, &out, io);
    return 0;
}

🧠 Core Concepts

  • Output Parameters: ฟังก์ชันสามารถส่งข้อมูลออกผ่านตัวแปรระดับ Caller โดยการระบุพารามิเตอร์แบบ non-const reference (T&) หรือ pointer (T*) เพื่อเขียนข้อมูลลงในหน่วยความจำโดยตรง
  • Returning by Reference or Pointer: ฟังก์ชันสามารถส่งคืนค่ากลับเป็น Reference หรือ Pointer ไปยังวัตถุที่มีตัวตนอยู่จริงได้ แต่การส่งคืนตำแหน่งของตัวแปร Local ที่กำลังจะโดนทำลายเมื่อจบฟังก์ชันจะทำให้เกิด Undefined behavior
  • Optional Results (std::optional): std::optional<T> ทำหน้าที่เป็น Wrapper เก็บข้อมูลประเภท T ที่อาจจะมีหรือไม่มีค่าอยู่ภายใน เพื่อเป็นทางเลือกทดแทนการใช้ค่ารหัสผิดพลาด (Sentinel value) แบบดั้งเดิม

⚠️ Pitfalls (Quick Scan)

ข้อผิดพลาด วิธีแก้
การระบุพารามิเตอร์ขาออก (Output Parameter) เป็น const ถอด Keyword const ออกจากพารามิเตอร์ที่เป็นช่องทางส่งค่าออก
เข้าใจผิดว่าการส่งคืนแบบ Return-by-value จะต้องคัดลอกข้อมูลเสมอ ห้ามเขียนโปรแกรมที่พึ่งพาผลของการคัดลอกทางอ้อมจากการ Return
การส่งคืนค่าแบบอ้างอิง Reference (T&) ไปยังตัวแปร Local ส่งคืน Reference เฉพาะตัวแปรที่มีอายุขัยยาวนานกว่าขอบเขตฟังก์ชันเท่านั้น
การส่งคืนค่าแบบ Pointer (T) ชี้หาตำแหน่งตัวแปร Local ส่งคืน Pointer ไปยังหน่วยความจำบน Heap หรือตัวแปรที่เป็นพารามิเตอร์
ใช้ Keyword auto เดี่ยวๆ ในการรับค่าอ้างอิง ระบุตัวแปรเป็น auto& เพื่อรักษาตรรกะการอ้างอิง
พึ่งพาการคาดเดา Return type ของ auto เพื่อส่งคืน Reference ระบุประเภทการส่งคืนเป็น T& ชัดเจน หรือใช้คำสั่ง decltype(auto)
เรียกใช้เมธอด .value() บนตัวแปร optional ที่เป็นค่าว่าง ตรวจสอบตัวแปรผ่าน has_value() ก่อนเรียกใช้งาน หรือดึงค่าด้วย value_or()
ส่งคืนข้อมูลชนิดต่างกันจากคนละสาขา (Branch) ในฟังก์ชันประเภท auto ทำการแปลงประเภทข้อมูล (Casting) ในแต่ละสาขาให้เป็นประเภทเดียวกันก่อนส่งกลับ
ลืมใส่เครื่องหมาย & เมื่อส่งตัวแปรไปยังพารามิเตอร์ประเภทพอยน์เตอร์ ระบุเครื่องหมาย & หน้าตัวแปรเสมอเพื่อส่งแอดเดรสไปให้ฟังก์ชัน
ใช้ค่าเฉพาะบางตัว (Sentinel value) เช่น -1 เพื่อแสดงความล้มเหลว ใช้ std::optional เพื่อแยกแยะความจริงของการไม่มีข้อมูลได้อย่างชัดเจนและปลอดภัย

📖 Full Details

Cause → Effect → Fix พร้อม timestamp (คลิกเพื่อดู) * **การระบุพารามิเตอร์ขาออก (Output Parameter) เป็น `const`** -> **เกิด Compile-time error เมื่อพยายามเขียนแก้ไขค่าตัวแปรในฟังก์ชัน** -> **ถอด Keyword `const` ออกจากพารามิเตอร์ที่เป็นช่องทางส่งค่าออก (00:40)** * **เข้าใจผิดว่าการส่งคืนแบบ Return-by-value จะต้องคัดลอกข้อมูลเสมอ** -> **Compiler ทำการยกเว้นการคัดลอก (Copy Elision) ทำให้ตัวแปรสองฝั่งใช้แอดเดรสเดียวกัน** -> **ห้ามเขียนโปรแกรมที่พึ่งพาผลของการคัดลอกทางอ้อมจากการ Return (07:30)** * **การส่งคืนค่าแบบอ้างอิง Reference (`T&`) ไปยังตัวแปร Local** -> **เกิด Undefined behavior เนื่องจากตัวแปรท้องถิ่นถูกทำลายทันทีหลังจบฟังก์ชันจนเกิด Dangling Reference** -> **ส่งคืน Reference เฉพาะตัวแปรที่มีอายุขัยยาวนานกว่าขอบเขตฟังก์ชันเท่านั้น (09:15)** * **การส่งคืนค่าแบบ Pointer (`T*`) ชี้หาตำแหน่งตัวแปร Local** -> **เกิด Undefined behavior เนื่องจาก Pointer จะกลายเป็นพอยน์เตอร์ชี้หาตำแหน่งขยะเมื่อจบฟังก์ชัน** -> **ส่งคืน Pointer ไปยังหน่วยความจำบน Heap หรือตัวแปรที่เป็นพารามิเตอร์ (10:45)** * **ใช้ Keyword `auto` เดี่ยวๆ ในการรับค่าอ้างอิง** -> **ตัวแปรจะโคลนข้อมูลออกมาแทนที่จะอ้างอิง ทำให้การแก้ไขไม่มีผลกระทบต่อตัวแปรต้นทาง** -> **ระบุตัวแปรเป็น `auto&` เพื่อรักษาตรรกะการอ้างอิง (13:15)** * **พึ่งพาการคาดเดา Return type ของ `auto` เพื่อส่งคืน Reference** -> **Compiler จะถอด Reference ออกและคาดเดาเป็นประเภทแบบ Copy ค่าส่งกลับแทน** -> **ระบุประเภทการส่งคืนเป็น `T&` ชัดเจน หรือใช้คำสั่ง `decltype(auto)` (16:10)** * **เรียกใช้เมธอด `.value()` บนตัวแปร optional ที่เป็นค่าว่าง** -> **โปรแกรมโยน exception `std::bad_optional_access` และจะแครชทันที** -> **ตรวจสอบตัวแปรผ่าน `has_value()` ก่อนเรียกใช้งาน หรือดึงค่าด้วย `value_or()` (19:10)** * **ส่งคืนข้อมูลชนิดต่างกันจากคนละสาขา (Branch) ในฟังก์ชันประเภท `auto`** -> **เกิด Compile-time error เนื่องจาก Compiler ไม่สามารถประเมินผลลัพธ์เป็นประเภทเดียวได้** -> **ทำการแปลงประเภทข้อมูล (Casting) ในแต่ละสาขาให้เป็นประเภทเดียวกันก่อนส่งกลับ (15:15)** * **ลืมใส่เครื่องหมาย `&` เมื่อส่งตัวแปรไปยังพารามิเตอร์ประเภทพอยน์เตอร์** -> **เกิด Compile-time error เนื่องจากฟังก์ชันต้องการพอยน์เตอร์แต่ได้รับตัวแปรธรรมดา** -> **ระบุเครื่องหมาย `&` หน้าตัวแปรเสมอเพื่อส่งแอดเดรสไปให้ฟังก์ชัน (01:50)** * **ใช้ค่าเฉพาะบางตัว (Sentinel value) เช่น `-1` เพื่อแสดงความล้มเหลว** -> **สร้างความสับสนและเสี่ยงต่อการที่ผู้เขียนลืมตรวจสอบค่าผิดพลาด** -> **ใช้ `std::optional` เพื่อแยกแยะความจริงของการไม่มีข้อมูลได้อย่างชัดเจนและปลอดภัย (17:00)**

📎 Repo Files

  • 19.2InputAndOutputParameters/main.cpp
  • 19.3ReturningFromFunctionsByValue/main.cpp
  • 19.4ReturningByReference/main.cpp
  • 19.5ReturningByPointer/main.cpp
  • 19.6ReturningArrayElementIndexByPointer/main.cpp
  • 19.7BareAutoTypeDeduction/main.cpp
  • 19.8FunctionReturnTypeDeduction/main.cpp
  • 19.9ReturnTypeDeductionWithReferences/main.cpp
  • 19.10FunctionDefinitionsWithReturnTypeDeduction/main.cpp
  • 19.11OptionalOutputFromFunctions/main.cpp
  • 19.12IntroducingStdOptional/main.cpp
  • 19.13OptionalOutputWithStdOptional/main.cpp