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.cpp19.3ReturningFromFunctionsByValue/main.cpp19.4ReturningByReference/main.cpp19.5ReturningByPointer/main.cpp19.6ReturningArrayElementIndexByPointer/main.cpp19.7BareAutoTypeDeduction/main.cpp19.8FunctionReturnTypeDeduction/main.cpp19.9ReturnTypeDeductionWithReferences/main.cpp19.10FunctionDefinitionsWithReturnTypeDeduction/main.cpp19.11OptionalOutputFromFunctions/main.cpp19.12IntroducingStdOptional/main.cpp19.13OptionalOutputWithStdOptional/main.cpp