กฎของสาม (Rule of Three) เป็นหนึ่งในกฎของ C++ ภายใต้กฎทั่วไปสำหรับการสร้างโค้ดที่ปลอดภัยจากข้อยกเว้น (exception safe) กฎเหล่านี้ระบุว่าสมาชิกเริ่มต้น (default members) ของคลาสควรถูกใช้อย่างไรเพื่อปฏิบัติที่ปลอดภัยจากข้อยกเว้น
กฎของสามยังเรียกว่า Law of Big Three หรือ The Big Three และระบุสำหรับคลาสว่า หากคลาสกำหนดฟังก์ชันใดฟังก์ชันหนึ่งในสามฟังก์ชันต่อไปนี้ ควรจะกำหนดทั้งสามอย่างอย่างชัดเจน (explicitly):
- Destructor (ตัวทำลาย)
- Copy Constructor (คอนสตรัคเตอร์คัดลอก)
- Copy Assignment Constructor (คอนสตรัคเตอร์กำหนดค่าคัดลอก)
ทั้งสามนี้เป็นฟังก์ชันสมาชิกพิเศษของคลาส หากไม่มีฟังก์ชันใดถูกกำหนดอย่างชัดเจนโดยโปรแกรมเมอร์ คอมไพเลอร์จะให้เวอร์ชันโดยนัย (implicit versions) หากมีการกำหนดอย่างชัดเจนเพียงหนึ่งในสาม เวอร์ชันโดยนัยสำหรับอีกสองตัวจะไม่เหมาะสมและต้องกำหนดใหม่
สิ่งนี้เกิดขึ้นเพราะคอนสตรัคเตอร์และออเปอเรเตอร์กำหนดค่าที่สร้างโดยนัยจะทำการ shallow copy (คัดลอกตื้น) สมาชิกข้อมูล เราต้องการ deep copy (คัดลอกลึก) เมื่อคลาสมี pointer ชี้ไปยังทรัพยากรที่จัดสรรแบบไดนามิก
Destructor เริ่มต้นจะลบอ็อบเจกต์ที่ไม่ได้ใช้ เมื่อไม่มี copy constructor กำหนดไว้ Destructor จะทำงานสองครั้ง ครั้งหนึ่งสำหรับอ็อบเจกต์ที่มีการคัดลอก และอีกครั้งสำหรับอ็อบเจกต์ที่ข้อมูลถูกคัดลอกมาจากนั้น เพื่อหลีกเลี่ยงเรื่องนี้ การกำหนดอย่างชัดเจนจึงเป็นสิ่งจำเป็น
มาดูตัวอย่างที่ไม่มี copy constructor และ copy assignment operator แต่มี destructor:
ตัวอย่าง
#include <stdio.h>
class Numbers {
private:
int num;
int* ptr;
public:
Numbers(int n, int* p) // copy constructor {
num = n;
ptr = new int[num];
}
~Numbers() // destructor {
delete ptr;
ptr = NULL;
}
};
int main() {
int arr[4] = { 11, 22, 33, 44 };
Numbers Num1(4, arr);
// นี่สร้างปัญหา
Numbers Num2(Num1);
return 0;
}
ผลลัพธ์
*** Error in `./a.out': double free or corruption (fasttop): 0x0000000001f46c20 *** Aborted
สิ่งนี้เกิดขึ้นเพราะ destructor ถูกเรียกสองครั้งเมื่อโปรแกรมออกจากขอบเขต (scope) ครั้งแรกลบ Num1 แล้วครั้งที่สองสำหรับ Num2 Default copy constructor สร้างคัดลอกของ pointer ptr แต่ไม่ได้จัดสรรหน่วยความจำสำหรับมัน ดังนั้นเมื่อ Num1 ถูกลบ pointer ptr ต่อไปทำให้โปรแกรมล้มเหลว (crash)