กฎนิยามข้อเดียว
กฎการนิยามเดียว ( One Definition RuleหรือODR ) เป็นกฎสำคัญของภาษาโปรแกรมC++ ที่กำหนดว่า คลาส/โครงสร้าง และฟังก์ชันที่ไม่ใช่แบบอินไลน์ ไม่สามารถมีการนิยามมากกว่าหนึ่งครั้งในโปรแกรมทั้งหมด และเทมเพลตและชนิดข้อมูล ไม่สามารถมีการนิยามมากกว่าหนึ่งครั้งต่อหน่วยการแปล กฎนี้ถูกกำหนดไว้ในมาตรฐาน ISO C++ ( ISO/IEC 14882 ) ปี 2003 ในส่วนที่ 3.2 ภาษาโปรแกรมอื่นๆ บางภาษาก็มีกฎที่คล้ายกัน แต่มีการกำหนดที่แตกต่างกันออกไปเพื่อจุดประสงค์เดียวกัน
สรุป
โดยสรุป ODR ระบุว่า:
- ในหน่วยการแปลใดๆเทมเพลตประเภทฟังก์ชันหรืออ็อบเจ็กต์จะมีคำจำกัดความได้ไม่เกินหนึ่งรายการ บางส่วนของคำจำกัดความเหล่านี้สามารถมีการประกาศได้ไม่จำกัดจำนวน คำจำกัดความจะให้ตัวอย่างหนึ่งรายการ
- ใน โปรแกรมทั้งหมดอ็อบเจ็กต์หรือฟังก์ชันที่ไม่ใช่แบบอินไลน์ไม่สามารถมีนิยามได้มากกว่าหนึ่งครั้ง หากมีการใช้งานอ็อบเจ็กต์หรือฟังก์ชัน จะต้องมีนิยามเพียงหนึ่งเดียวเท่านั้น คุณสามารถประกาศอ็อบเจ็กต์หรือฟังก์ชันที่ไม่เคยใช้งานได้ ในกรณีนี้คุณไม่จำเป็นต้องให้นิยาม ในทุกกรณีจะต้องมีนิยามได้ไม่เกินหนึ่งครั้ง
- บางสิ่ง เช่น ประเภท เทมเพลต และ ฟังก์ชันอินไลน์ ภายนอกสามารถกำหนดได้ในหน่วยการแปลมากกว่าหนึ่งหน่วย สำหรับเอนทิตีที่กำหนด การกำหนดแต่ละครั้งจะต้องมีลำดับโทเค็น เดียวกัน วัตถุและฟังก์ชันที่ไม่ใช่ภายนอกในหน่วยการแปลที่แตกต่างกันถือเป็นเอนทิตีที่แตกต่างกัน แม้ว่าชื่อและประเภทของพวกมันจะเหมือนกันก็ตาม
การละเมิด ODR บางอย่างจะต้องได้รับการวินิจฉัยโดยคอมไพเลอร์การละเมิดอื่นๆ โดยเฉพาะอย่างยิ่งการละเมิดที่ครอบคลุมหน่วยการแปล ไม่จำเป็นต้องได้รับการวินิจฉัย[ 1 ]
ตัวอย่าง
โดยทั่วไป หน่วยการแปลควรมีคำจำกัดความของคลาสประเภทใดประเภทหนึ่งไม่เกินหนึ่งรายการ ในตัวอย่างนี้ มีคำจำกัดความของคลาสประเภทเดียวกันสองรายการMyClassอยู่ในหน่วยการแปล เดียวกัน ซึ่งมักเกิดขึ้นหาก มีการรวม ไฟล์ส่วนหัวสองครั้งโดยไฟล์ต้นฉบับเดียวกันโดยไม่มีการป้องกันส่วนหัว ที่เหมาะสม
class MyClass {}; // การนิยาม MyClass ครั้งแรกclass MyClass {}; // เกิดข้อผิดพลาด การนิยาม MyClass ครั้งที่สองต่อไปนี้ การสร้างตัวชี้ไปยังMyStructหรือการกำหนดฟังก์ชันที่รับการอ้างอิงไปยังMyStructเป็นตัวอย่างของโครงสร้างที่ถูกต้องตามกฎหมาย เนื่องจากไม่จำเป็นต้องให้ประเภทของMyStructสมบูรณ์ดังนั้นจึงไม่จำเป็นต้องมีการกำหนด[ 2 ]
การกำหนดวัตถุประเภทMyStructฟังก์ชันที่รับอาร์กิวเมนต์ประเภทMyStructหรือการใช้MyStructใน นิพจน์ sizeofเป็นตัวอย่างของบริบทที่ S ต้องสมบูรณ์ และดังนั้นจึงต้องมีการกำหนด[ 2 ]
struct MyStruct ; // การประกาศ MyStruct MyStruct * p ; // ถูกต้อง ไม่จำเป็นต้องกำหนดนิยามvoid f ( MyStruct & ); // ถูกต้อง ไม่จำเป็นต้องกำหนดนิยามvoid f ( MyStruct * ); // ถูกต้อง ไม่จำเป็นต้องกำหนดนิยามMyStruct f (); // ถูกต้อง ไม่จำเป็นต้องกำหนดนิยาม - นี่เป็นการประกาศฟังก์ชันเท่านั้น!MyStruct s ; // เกิดข้อผิดพลาด ต้องกำหนดsizeof ( MyStruct ); // เกิดข้อผิดพลาด ต้องกำหนด sizeof ( MyStruct );มากกว่าหนึ่งคำจำกัดความ
ในบางกรณี อาจมีคำจำกัดความของประเภทหรือแม่แบบมากกว่าหนึ่งรายการ โปรแกรมที่ประกอบด้วยไฟล์ส่วนหัวและไฟล์ต้นฉบับหลายไฟล์ โดยทั่วไปจะมีคำจำกัดความของประเภทมากกว่าหนึ่งรายการ แต่ไม่เกินหนึ่งคำจำกัดความต่อหน่วยการแปล
หากโปรแกรมมีคำจำกัดความของประเภทมากกว่าหนึ่งรายการ คำจำกัดความแต่ละรายการจะต้องเทียบเท่ากัน[ 3 ]
นิยามของสมาชิกข้อมูลคงที่แบบ static
ในภาษา C++ ก่อนการกำหนดมาตรฐาน สมาชิกข้อมูลแบบ static ทั้งหมดจำเป็นต้องมีการนิยามไว้นอกคลาสของมัน อย่างไรก็ตาม ในระหว่างกระบวนการกำหนดมาตรฐานของ C++ ได้มีการตัดสินใจยกเลิกข้อกำหนดนี้สำหรับสมาชิกจำนวนเต็มแบบ static const โดยมีจุดประสงค์เพื่อให้สามารถใช้งานได้ในลักษณะต่างๆ เช่น:
struct MyClass { static const int N = 10 ; }; char data [ MyClass :: N ]; // N "ถูกใช้งาน" โดยไม่ต้องกำหนดนิยามภายนอกคลาสโดยไม่มี การกำหนดขอบเขต เนมสเปซสำหรับN.
อย่างไรก็ตาม ถ้อยคำของมาตรฐาน C++ ปี 1998 ยังคงกำหนดให้ต้องมีการกำหนดสมาชิกหากมีการใช้สมาชิกนั้นในโปรแกรม[ 4 ]ซึ่งรวมถึงสมาชิกที่ปรากฏที่ใดก็ได้ยกเว้นในฐานะตัวดำเนินการของsizeofหรือtypeidซึ่งทำให้รูปแบบข้างต้นไม่ถูกต้อง[ 5 ]
สิ่งนี้ถูกระบุว่าเป็นข้อบกพร่อง และถ้อยคำได้รับการปรับเปลี่ยนเพื่อให้สมาชิกดังกล่าวปรากฏได้ทุกที่ที่ ต้องการ นิพจน์คง ที่ โดยไม่ต้องมีการกำหนดนอกคลาส ซึ่งรวมถึงขอบเขตของอาร์เรย์นิพจน์เคสตัวเริ่มต้นสมาชิกแบบคงที่ และอาร์กิวเมนต์เทมเพลตที่ไม่ใช่ประเภท[ 6 ]
struct MyClass1 { static const int N = 10 ; static const int U = N ; // ถูกต้องตามกฎ C++03 };char data [ Class1 :: N ]; // ถูกต้องตาม C++03template < int > struct MyClass2 ;template <> struct MyClass2 < Class1 :: N > {}; // ถูกต้องตามหลัก C++03อย่างไรก็ตาม การใช้สมาชิกอินทิกรัลคงที่คงที่ทุกที่ยกเว้นในกรณีที่ต้องใช้นิพจน์ค่าคงที่อินทิกรัล ต้องมีการกำหนด: [ 7 ]
struct MyClass { static const int N = 10 ; };int main () { int i = MyClass :: N ; // รูปแบบไม่ถูกต้องใน C++03 ต้องมีการกำหนด MyClass::N }ตัวอย่างที่แสดงผลข้างเคียงที่ไม่คาดคิด
เราต้องการไฟล์ 4 ไฟล์ ได้แก่ " Base.cppm ", " Dummy1.cppm ", " Dummy2.cppm " และ " Main.cpp "
Base.cppm :
โมดูล;ส่งออกโมดูลวิกิพีเดียตัวอย่างฐาน;ส่งออกเนมสเปซวิกิพีเดีย:: examples {// คลาสพื้นฐานนามธรรม class Base { public : virtual void myFunc ( ) = 0 ; virtual ~ Base () = default ; };}ดัมมี่1.ซีพีพี
โมดูล;ส่งออกโมดูลวิกิพีเดียตัวอย่างดัมมี่2 ;import std ;import wikipedia.examples.Base ;ส่งออกเนมสเปซวิกิพีเดีย:: examples {คลาสDummy : public Base { public : void myFunc () override { std :: println ( "odr ONE dummy: Hello" ); } };Base * odr1Create () { return new Dummy (); }}Dummy2.cppm :
โมดูล;ส่งออกโมดูลวิกิพีเดียตัวอย่างดัมมี่2 ;import std ;import wikipedia.examples.Base ;ส่งออกวิกิพีเดีย:: ตัวอย่าง{class Dummy : public Base { public : void myFunc () override { std :: println ( "odr TWO dummy: World" ); } };Base * odr2Create () { return new Dummy (); }}Main.cpp :
import wikipedia.examples.Base ; import wikipedia.examples.Dummy1 ; import wikipedia.examples.Dummy2 ;using wikipedia :: examples :: Base ; using wikipedia :: examples :: Dummy1 ; using wikipedia :: examples :: Dummy2 ;int main ( int argc , char * argv []) { Base * o1 = odr1Create (); Base * o2 = odr2Create (); o1 -> myFunc (); o2 -> myFunc ();ลบo1 ; ลบo2 ; }เมื่อดำเนินการแล้ว ผลลัพธ์ ที่คาดหวังคือ:
odr ONE หุ่นจำลอง: สวัสดี odr TWO dummy: World
แต่ผลลัพธ์ที่น่าจะเป็นคือ:
odr ONE หุ่นจำลอง: สวัสดี odr ONE หุ่นจำลอง: สวัสดี
ปัญหาคือ ตัวเชื่อมโยงของ C++ ต้องหาวิธีสร้างตารางเมธอดเสมือนสำหรับDummyคลาส (สองคลาสที่แตกต่างกัน) และวิธีนี้จะใช้ได้ผลก็ต่อเมื่อชื่อคลาสแตกต่างกันเท่านั้น