ทำไม C ++ ไม่อนุญาตให้คุณรับที่อยู่ของนวกรรมิก?


14

มีเหตุผลที่เฉพาะเจาะจงหรือไม่ที่จะทำให้ภาษาแตกต่างจากแนวคิดหรือเป็นเหตุผลเฉพาะที่ทำให้ไม่สามารถใช้ภาษาได้ในบางกรณี?

การใช้งานจะเป็นกับผู้ประกอบการใหม่

แก้ไข:ฉันจะให้ความหวังในการรับ "โอเปอเรเตอร์ใหม่" และ "โอเปอเรเตอร์ใหม่" ตรงไปตรงมา

ประเด็นของคำถามคือ: ทำไมคอนสตรัคเตอร์ถึงพิเศษ ? โปรดจำไว้ว่าข้อกำหนดทางภาษาบอกเราว่าอะไรถูกกฎหมาย แต่ไม่จำเป็นต้องมีคุณธรรม โดยทั่วไปแล้วสิ่งที่ถูกกฎหมายจะได้รับแจ้งจากสิ่งที่มีเหตุผลสอดคล้องกับส่วนที่เหลือของภาษาสิ่งที่ง่ายและรัดกุมและสิ่งที่เป็นไปได้สำหรับคอมไพเลอร์ที่จะใช้ เหตุผลที่เป็นไปได้ของคณะกรรมการมาตรฐานในการชั่งน้ำหนักปัจจัยเหล่านี้มีเจตนาและน่าสนใจ - ดังนั้นคำถาม


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

ถ้าคุณมีเท็มเพลตฟังก์ชั่นที่คุณต้องการสร้างวัตถุโดยใช้คอนสตรัคเตอร์ที่จะระบุว่าเป็นอาร์กิวเมนต์ของฟังก์ชัน
Praxeolitic

1
จะมีทางเลือกสำหรับตัวอย่างใด ๆ ที่ฉันสามารถคิดได้ แต่ก็ยังทำไมผู้สร้างควรจะพิเศษ มีหลายสิ่งหลายอย่างที่คุณอาจไม่ได้ใช้ในภาษาการเขียนโปรแกรมส่วนใหญ่ แต่กรณีพิเศษเช่นนี้มักมาพร้อมกับเหตุผล
Praxeolitic

1
@ RobertHarvey คำถามที่เกิดขึ้นกับฉันเมื่อฉันกำลังจะพิมพ์ชั้นโรงงาน
Praxeolitic

1
ฉันสงสัยว่า C ++ 11 std::make_uniqueและstd::make_sharedสามารถแก้ไขแรงจูงใจในทางปฏิบัติสำหรับคำถามนี้ได้อย่างเพียงพอหรือไม่ นี่คือวิธีการของเทมเพลตซึ่งหมายความว่าเราต้องจับอาร์กิวเมนต์ที่ป้อนเข้ากับตัวสร้างแล้วส่งต่อไปยังตัวสร้างจริง
rwong

คำตอบ:


10

ฟังก์ชั่น Pointers-to-member เข้าใจได้เฉพาะถ้าคุณมีสมาชิกมากกว่าหนึ่งฟังก์ชั่นที่มีลายเซ็นเหมือนกัน - มิฉะนั้นจะมีเพียงค่าเดียวที่เป็นไปได้สำหรับตัวชี้ของคุณ แต่นั่นเป็นไปไม่ได้สำหรับสิ่งก่อสร้างเนื่องจากใน C ++ ตัวสร้างที่แตกต่างกันของคลาสเดียวกันจะต้องมีลายเซ็นที่แตกต่างกัน

ทางเลือกสำหรับ Stroustrup คือการเลือกไวยากรณ์สำหรับ C ++ โดยที่ตัวสร้างอาจมีชื่อที่แตกต่างจากชื่อคลาส - แต่นั่นจะป้องกันบางแง่มุมที่งดงามของไวยากรณ์ ctor ที่มีอยู่และทำให้ภาษาซับซ้อนขึ้น สำหรับฉันที่ดูเหมือนว่าราคาสูงเพียงเพื่อให้มีคุณสมบัติที่ต้องการซึ่งสามารถจำลองได้ง่าย ๆ โดย "จ้าง" การเริ่มต้นของวัตถุจาก ctor ไปยังinitฟังก์ชั่นที่แตกต่างกัน(ฟังก์ชั่นสมาชิกปกติที่ตัวชี้ไปยังสมาชิกสามารถ สร้าง)


2
ยังทำไมป้องกันmemcpy(buffer, (&std::string)(int, char), size)? (อาจมากยกเลิกเพียว ๆ แต่นี่คือ C ++ หลังจากทั้งหมด.)
โทมัส Eding

3
ขออภัย แต่สิ่งที่คุณเขียนไม่สมเหตุสมผล ฉันเห็นอะไรผิดปกติกับการมีตัวชี้ไปยังสมาชิกที่ชี้ไปที่คอนสตรัค มันฟังดูเหมือนว่าคุณจะพูดอะไรบางอย่างโดยไม่มีลิงก์ไปยังแหล่งที่มา
BЈовић

1
@ThomasEding: คุณคาดหวังสิ่งที่คำสั่งที่จะทำอย่างไร คัดลอกรหัสประกอบของสตริง ctor ที่อื่น? จะกำหนด "ขนาด" อย่างไร (แม้ว่าคุณจะลองอะไรที่เทียบเท่ากับฟังก์ชั่นสมาชิกมาตรฐาน)
Doc Brown

memcpy(buffer, strlen, size)ผมคาดว่ามันทำสิ่งเดียวกันก็จะทำตามที่ได้รับอยู่ของตัวชี้ฟังก์ชันฟรี สันนิษฐานว่ามันจะคัดลอกสมัชชา แต่ใครจะรู้ ไม่ว่ารหัสนั้นจะถูกเรียกใช้โดยไม่ขัดข้องจะต้องมีความรู้เกี่ยวกับคอมไพเลอร์ที่คุณใช้ กันไปสำหรับการกำหนดขนาด มันจะขึ้นอยู่กับแพลตฟอร์มสูง แต่โครงสร้าง C ++ ที่ไม่สามารถพกพาได้ถูกใช้ในรหัสการผลิต ฉันไม่เห็นเหตุผลที่จะผิดกฎหมาย
Thomas Eding

@ThomasEding: คอมไพเลอร์ที่สอดคล้องกับ C ++ คาดว่าจะให้การวินิจฉัยเมื่อพยายามเข้าถึงตัวชี้ฟังก์ชั่นราวกับว่ามันเป็นตัวชี้ข้อมูล คอมไพเลอร์ที่ไม่เป็นไปตามมาตรฐาน C ++ สามารถทำสิ่งใดก็ได้ แต่พวกเขายังสามารถจัดเตรียมวิธีการที่ไม่ใช่ c ++ ในการเข้าถึงตัวสร้างเช่นกัน นั่นไม่ใช่เหตุผลที่จะเพิ่มคุณสมบัติให้กับ C ++ ที่ไม่มีประโยชน์ในการใช้รหัสที่สอดคล้องกัน
Bart van Ingen Schenau

5

ตัวสร้างเป็นฟังก์ชันที่คุณเรียกใช้เมื่อวัตถุยังไม่มีอยู่ดังนั้นจึงไม่สามารถเป็นฟังก์ชันสมาชิกได้ มันอาจจะคงที่

คอนสตรัคได้รับจริงด้วยตัวชี้นี้หลังจากหน่วยความจำได้รับการจัดสรร แต่ก่อนที่จะได้รับการเริ่มต้นอย่างสมบูรณ์ ดังนั้นคอนสตรัคเตอร์จึงมีคุณสมบัติพิเศษมากมาย

หากคุณมีตัวชี้ไปยังตัวสร้างมันอาจจะต้องเป็นตัวชี้คงที่บางสิ่งบางอย่างเช่นฟังก์ชั่นจากโรงงานหรือตัวชี้พิเศษกับสิ่งที่จะเรียกทันทีหลังจากการจัดสรรหน่วยความจำ มันไม่สามารถเป็นฟังก์ชันสมาชิกสามัญและยังคงทำงานเป็นตัวสร้าง

วัตถุประสงค์ที่เป็นประโยชน์เพียงอย่างเดียวที่อยู่ในใจคือตัวชี้ชนิดพิเศษที่สามารถส่งผ่านไปยังผู้ประกอบการใหม่เพื่อให้สามารถใช้ตัวสร้างทางอ้อมได้ ฉันเดาว่ามันอาจจะมีประโยชน์ แต่มันจะต้องมีไวยากรณ์ใหม่ที่สำคัญและน่าจะเป็นคำตอบคือพวกเขาคิดเกี่ยวกับมันและมันก็ไม่คุ้มค่ากับความพยายาม

หากคุณเพียงต้องการ refactor รหัสเริ่มต้นทั่วไปแล้วฟังก์ชั่นหน่วยความจำปกติมักจะเป็นคำตอบที่เพียงพอและคุณสามารถรับตัวชี้ไปยังหนึ่งในนั้น


ดูเหมือนว่าคำตอบที่ถูกต้องที่สุด ฉันจำบทความจากหลาย ๆ ปีที่ผ่านมาเกี่ยวกับผู้ปฏิบัติงานใหม่และการทำงานภายในของ“ ผู้ดำเนินการใหม่” ผู้ประกอบการใหม่ () จัดสรรพื้นที่ ผู้ประกอบการใหม่เรียกตัวสร้างด้วยพื้นที่ที่จัดสรรนั้น การระบุที่อยู่ของคอนสตรัคเตอร์นั้น“ พิเศษ” เพราะการเรียกคอนสตรัคเตอร์นั้นต้องการพื้นที่ การเข้าถึงการเรียกคอนสตรัคเตอร์เช่นนี้ขึ้นอยู่กับตำแหน่งใหม่
Bill Door

1
คำว่า "มีอยู่" ปิดบังรายละเอียดที่วัตถุสามารถมีที่อยู่และได้จัดสรรหน่วยความจำ แต่ไม่สามารถเริ่มต้นได้ ในฟังก์ชั่นสมาชิกหรือไม่ฉันคิดว่าการรับตัวชี้นี้ทำให้ฟังก์ชั่นสมาชิกเป็นฟังก์ชั่นเพราะมันเกี่ยวข้องกับวัตถุอย่างชัดเจน (แม้ว่าจะไม่เตรียมการ) ที่กล่าวว่าคำตอบเพิ่มจุดที่ดี: นวกรรมิกเป็นฟังก์ชั่นสมาชิกเท่านั้นที่สามารถเรียกบนวัตถุที่ไม่ได้เตรียมการ
Praxeolitic

ไม่เป็นไรเห็นได้ชัดว่าพวกเขามีการกำหนด "ฟังก์ชั่นสมาชิกพิเศษ" ข้อ 12 ของมาตรฐาน C ++ 11: "ตัวสร้างค่าเริ่มต้น (12.1), ตัวสร้างตัวคัดลอกและตัวดำเนินการกำหนดค่า (12.8), ตัวสร้างตัวย้ายและตัวดำเนินการกำหนดค่าย้าย (12.8) และตัวทำลาย (12.4) เป็นฟังก์ชันสมาชิกพิเศษ "
Praxeolitic

และ 12.1: "ตัวสร้างจะต้องไม่เป็นเสมือน (10.3) หรือคงที่ (9.4)" (ความสำคัญของฉัน)
Praxeolitic

1
ความจริงก็คือถ้าคุณคอมไพล์ด้วยสัญลักษณ์การดีบักและค้นหาการติดตามสแต็กจะมีตัวชี้ไปยังตัวสร้าง สิ่งที่ฉันไม่สามารถทำได้คือการหาไวยากรณ์เพื่อรับตัวชี้นี้ ( &A::Aไม่ทำงานในคอมไพเลอร์ใด ๆ ที่ฉันพยายาม)
alfC

-3

นี่เป็นเพราะพวกมันไม่ใช่ประเภทผลตอบแทนของตัวสร้างและคุณไม่ได้สำรองพื้นที่ใด ๆ สำหรับตัวสร้างในหน่วยความจำ เช่นเดียวกับคุณในกรณีของตัวแปรในระหว่างการประกาศ ตัวอย่างเช่น: หากคุณเขียนตัวแปรอย่างง่าย X ดังนั้นคอมไพเลอร์จะสร้างข้อผิดพลาดเนื่องจากคอมไพเลอร์จะไม่เข้าใจความหมายของสิ่งนี้ แต่เมื่อคุณเขียน Int x; จากนั้นคอมไพเลอร์รู้ว่ามันเป็นตัวแปรข้อมูลประเภท int ดังนั้นมันจะสงวนพื้นที่ไว้สำหรับตัวแปร

สรุป: - ดังนั้นข้อสรุปคือเนื่องจากการยกเว้นประเภท return มันจะไม่ได้รับที่อยู่ในหน่วยความจำ


1
รหัสในตัวสร้างจะต้องมีที่อยู่ในหน่วยความจำเพราะมันจะต้องมีที่ไหนสักแห่ง ไม่จำเป็นต้องจองพื้นที่บนสแต็กแต่จะต้องอยู่ในหน่วยความจำ คุณสามารถใช้ที่อยู่ของฟังก์ชั่นที่ไม่ส่งคืนค่า (void)(*fptr)()ประกาศตัวชี้ไปยังฟังก์ชั่นโดยไม่มีค่าตอบแทน
Praxeolitic

2
คุณพลาดจุดของคำถาม - โพสต์ต้นฉบับถามเกี่ยวกับการใช้ที่อยู่ของรหัสสำหรับตัวสร้างไม่ใช่ผลลัพธ์ที่ตัวสร้างให้ นอกจากนี้ในบอร์ดนี้โปรดใช้คำเต็ม: "u" ไม่ใช่สิ่งทดแทนที่ยอมรับได้สำหรับ "คุณ"
BobDalgleish

นาย praxeolitic ฉันคิดว่าถ้าเราไม่พูดถึงประเภทการส่งคืนใด ๆ แล้วคอมไพเลอร์จะไม่ตั้งค่าตำแหน่งหน่วยความจำเฉพาะสำหรับ ctor และมันตั้งอยู่ภายใน ... เราสามารถดึงที่อยู่ของสิ่งใด ๆ ใน c ++ ซึ่งไม่ได้รับจาก คอมไพเลอร์? หากผิดโปรดแก้ไขให้ฉันด้วยคำตอบที่ถูกต้อง
Goyal

และบอกฉันเกี่ยวกับตัวแปรอ้างอิงด้วย เราสามารถดึงข้อมูลที่อยู่ของตัวแปรอ้างอิงได้หรือไม่? ถ้าไม่เช่นนั้นที่อยู่ใด printf ("% u", & (& (j))); กำลังพิมพ์ถ้า & j = x โดยที่ x = 10 เพราะที่อยู่ที่พิมพ์โดย printf และที่อยู่ของ x ไม่เหมือนกัน
Goyal

-4

ฉันจะเดายาก:

C ++ constructor และ destructor ไม่ทำงานเลย: มันเป็นมาโคร พวกมันจะถูกแทรกเข้าไปในขอบเขตที่วัตถุถูกสร้างขึ้นและขอบเขตที่วัตถุถูกทำลาย ในทางกลับกันไม่มีคอนสตรัคเตอร์หรือตัวทำลายระบบวัตถุเพียงแค่เป็น

ที่จริงแล้วฉันคิดว่าฟังก์ชั่นอื่น ๆ ในชั้นเรียนไม่ใช่ฟังก์ชั่นใด ๆ แต่ฟังก์ชั่นอินไลน์ที่ไม่ได้รับการ inline เพราะคุณใช้ที่อยู่ของพวกเขา (คอมไพเลอร์ตระหนักว่าคุณลงบนมันและไม่ inline หรือ inline ปรับฟังก์ชั่นนั้นให้เหมาะสม) และในทางกลับกันฟังก์ชั่นดูเหมือนจะ "ยังอยู่ที่นั่น" แม้ว่ามันจะไม่เกิดขึ้นหากคุณไม่ได้ระบุที่อยู่

ตารางเสมือนของ "C ++" วัตถุ "ไม่เหมือนกับวัตถุ JavaScript ที่คุณสามารถรับ 'constructor และสร้างวัตถุจากมันที่รันไทม์ผ่านnew XMLHttpRequest.constructorแต่ค่อนข้างชุดของตัวชี้ไปยังฟังก์ชั่นที่ไม่ระบุชื่อที่ทำหน้าที่เป็นวิธีการติดต่อกับวัตถุนี้ ยกเว้นความสามารถในการสร้างวัตถุ และมันก็ไม่สมเหตุสมผลเลยที่จะ "ลบ" วัตถุเพราะมันเหมือนการพยายามลบ struct คุณไม่สามารถ: มันเป็นแค่สแต็กเลเบลเพียงเขียนไปตามที่คุณโปรดใต้ป้ายกำกับอื่น: คุณมีอิสระที่จะ ใช้คลาสเป็นจำนวนเต็ม 4 ตัว:

/* i imagine this string gets compiled into a struct, one of which's members happens to be a const char * which is initialized to exactly your string: no function calls are made during construction. */
std::string a = "hello, world";
int *myInt = (int *)(*((void **)&a));
myInt[0] = 3;
myInt[1] = 9;
myInt[2] = 20;
myInt[3] = 300;

ไม่มีหน่วยความจำรั่วไม่มีปัญหายกเว้นคุณเสียพื้นที่สแต็กจำนวนมากซึ่งสงวนไว้สำหรับการเชื่อมต่อวัตถุและสตริง แต่จะไม่ทำลายโปรแกรมของคุณ (ตราบใดที่คุณไม่พยายามใช้งาน เป็นสตริงอีกครั้ง)

ที่จริงแล้วถ้าสมมุติฐานก่อนหน้าของฉันถูกต้อง: ค่าใช้จ่ายทั้งหมดของสตริงนั้นเป็นเพียงค่าใช้จ่ายในการจัดเก็บ 32 ไบต์และพื้นที่สตริงคงที่: ฟังก์ชั่นจะใช้ในเวลารวบรวมเท่านั้น วัตถุถูกสร้างและใช้งาน (ราวกับว่าคุณกำลังทำงานกับ struct และอ้างถึงโดยตรงโดยไม่มีการเรียกใช้ฟังก์ชันใด ๆ ให้แน่ใจว่ามีการเรียกที่ซ้ำกันแทนการข้ามฟังก์ชัน แต่โดยปกติจะเร็วกว่าและใช้พื้นที่น้อยกว่า) ในสาระสำคัญเมื่อใดก็ตามที่คุณเรียกใช้ฟังก์ชันใด ๆ คอมไพเลอร์ก็แทนที่การเรียกนั้นด้วยคำแนะนำในการทำอย่างแท้จริงโดยมีข้อยกเว้นที่นักออกแบบภาษาได้ตั้งไว้

สรุป: วัตถุ C ++ ไม่รู้ว่ามันคืออะไร เครื่องมือทั้งหมดสำหรับการเชื่อมต่อกับพวกเขาจะ inline แบบคงที่และหายไปตอนรันไทม์ สิ่งนี้ทำให้การทำงานกับคลาสมีประสิทธิภาพเท่ากับการกรอกข้อมูลด้วยโครงสร้างและทำงานโดยตรงกับข้อมูลนั้นโดยไม่ต้องเรียกใช้ฟังก์ชั่นใด ๆ เลย

สิ่งนี้แตกต่างอย่างสิ้นเชิงจากวิธีการของ COM / ObjectiveC เช่นเดียวกับ javascript ซึ่งเก็บข้อมูลประเภทแบบไดนามิกในราคาของค่าใช้จ่ายรันไทม์, การจัดการหน่วยความจำ, การเรียกการก่อสร้างเนื่องจากคอมไพเลอร์ไม่สามารถทิ้งข้อมูลนี้ได้: จำเป็น สำหรับการจัดส่งแบบไดนามิก สิ่งนี้จะช่วยให้เราสามารถ "พูดคุย" กับโปรแกรมของเราที่รันไทม์และพัฒนาในขณะที่มันกำลังทำงานโดยมีองค์ประกอบที่สะท้อนได้


2
ขออภัย แต่บางส่วนของ "คำตอบ" นี้อาจผิดหรือทำให้เข้าใจผิด น่าเศร้าที่พื้นที่ความคิดเห็นมีขนาดเล็กเกินไปที่จะแสดงรายการทั้งหมด (วิธีการส่วนใหญ่จะไม่ได้รับการ inline นี้จะป้องกันเสมือนการจัดส่งและขยายตัวไบนารีไบนารีแม้ว่า inlined อาจมีสำเนาที่สามารถเข้าถึงแอดเดรสที่ไหนสักแห่งตัวอย่างรหัสที่ไม่เกี่ยวข้อง กรณีที่เลวร้ายที่สุดทำให้กองซ้อนของคุณเสียหายและในกรณีที่ดีที่สุดนั้นไม่เหมาะสมกับสมมติฐานของคุณ; ... )
hoffmale

คำตอบนั้นไร้เดียงสาฉันแค่อยากจะเดาว่าทำไมตัวสร้าง / ตัวทำลายไม่สามารถอ้างอิงได้ ฉันยอมรับว่าในกรณีของคลาสเสมือน vtable จะต้องคงอยู่และโค้ดที่แอดเดรสได้ต้องอยู่ในหน่วยความจำเพื่อให้ vtable สามารถอ้างอิงได้ อย่างไรก็ตามคลาสที่ไม่ได้ใช้คลาสเสมือนนั้นดูเหมือนจะ inline เช่นเดียวกับในกรณีของ std :: string ไม่ใช่ทุกสิ่งที่ถูกขีดเส้นใต้ แต่สิ่งที่ดูเหมือนจะไม่ถูกนำไปใส่ในบล็อคโค้ด "ไม่ระบุชื่อ" อย่างน้อยหนึ่งที่ในหน่วยความจำ นอกจากนี้รหัสไม่เสียหายกองได้อย่างไร แน่นอนว่าเราทำสายหาย แต่อย่างอื่นที่เราทำคือตีความใหม่
Dmitry

ความเสียหายของหน่วยความจำเกิดขึ้นในโปรแกรมคอมพิวเตอร์เมื่อเนื้อหาของตำแหน่งหน่วยความจำถูกแก้ไขโดยไม่ได้ตั้งใจ โปรแกรมนี้ทำโดยตั้งใจและไม่พยายามใช้สตริงนั้นอีกต่อไปดังนั้นจึงไม่มีความเสียหายเพียงแค่สูญเสียพื้นที่สแต็ก แต่ใช่ค่าคงที่ของสตริงจะไม่คงอยู่อีกต่อไป แต่จะทำให้ขอบเขตยุ่งเหยิง (ในตอนท้ายของการกู้คืนสแต็ก)
Dmitry

ขึ้นอยู่กับการนำสตริงไปใช้คุณอาจเขียนทับไบต์ที่คุณไม่ต้องการ หากสตริงเป็นสิ่งที่ชอบstruct { int size; const char * data; };(เหมือนที่คุณคิด) คุณเขียน 4 * 4 ไบต์ = 16 ไบต์บนที่อยู่หน่วยความจำที่คุณจองไว้เพียง 8 ไบต์บนเครื่อง x86 ดังนั้น 8 ไบต์จะถูกเขียนทับข้อมูลอื่น (ซึ่งอาจทำให้สแต็กของคุณเสียหาย ) โชคดีที่std::stringโดยปกติมีการเพิ่มประสิทธิภาพแบบแทนที่สำหรับสตริงสั้น ๆ ดังนั้นจึงควรมีขนาดใหญ่พอสำหรับตัวอย่างของคุณเมื่อใช้งาน std หลักบางอย่าง
hoffmale

@hoffmale คุณพูดถูกมันอาจเป็น 4 ไบต์อาจเป็น 8 หรือ 1 ไบต์ อย่างไรก็ตามเมื่อคุณทราบขนาดของสตริงแล้วคุณจะรู้ว่าหน่วยความจำนั้นอยู่ในสแต็กในขอบเขตปัจจุบันและคุณสามารถใช้มันได้ตามที่คุณต้องการ ประเด็นของฉันคือว่าถ้าคุณรู้โครงสร้างมันจะถูกบรรจุในลักษณะที่เป็นอิสระจากข้อมูลใด ๆ เกี่ยวกับชั้นซึ่งแตกต่างจากวัตถุ COM ที่มี uuid ระบุระดับของพวกเขาเป็นส่วนหนึ่งของ vtable ของ IUnknown ในทางกลับกันคอมไพเลอร์เข้าถึงข้อมูลนี้โดยตรงผ่านฟังก์ชั่นอินไลน์หรือ mangled คงที่
Dmitry
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.