`is_base_of` ทำงานอย่างไร


118

รหัสต่อไปนี้ทำงานอย่างไร

typedef char (&yes)[1];
typedef char (&no)[2];

template <typename B, typename D>
struct Host
{
  operator B*() const;
  operator D*();
};

template <typename B, typename D>
struct is_base_of
{
  template <typename T> 
  static yes check(D*, T);
  static no check(B*, int);

  static const bool value = sizeof(check(Host<B,D>(), int())) == sizeof(yes);
};

//Test sample
class Base {};
class Derived : private Base {};

//Expression is true.
int test[is_base_of<Base,Derived>::value && !is_base_of<Derived,Base>::value];
  1. โปรดทราบว่าBเป็นฐานส่วนตัว วิธีนี้ทำงานอย่างไร?

  2. โปรดทราบว่าoperator B*()เป็น const ทำไมมันถึงสำคัญ?

  3. ทำไมถึงtemplate<typename T> static yes check(D*, T);ดีกว่าstatic yes check(B*, int);?

หมายเหตุ : มันจะลดลงรุ่น (แมโครจะถูกลบออก) boost::is_base_ofของ และใช้งานได้กับคอมไพเลอร์ที่หลากหลาย


4
คุณสับสนมากที่ใช้ตัวระบุเดียวกันสำหรับพารามิเตอร์เทมเพลตและชื่อคลาสที่แท้จริง ...
Matthieu M.

1
@Matthieu M. ฉันเอามันไปแก้ไขเอง :)
Kirill V. Lyadvinsky

2
เมื่อไม่นานมานี้ฉันได้เขียนการใช้งานทางเลือกของis_base_of: ideone.com/T0C1Vมันใช้ไม่ได้กับ GCC เวอร์ชันเก่า (GCC4.3 ทำงานได้ดี)
Johannes Schaub - litb

3
โอเคฉันจะไปเดินเล่น
jokoon

2
การใช้งานนี้ไม่ถูกต้อง is_base_of<Base,Base>::valueควรจะtrue; ผลตอบแทนfalseนี้
chengiz

คำตอบ:


109

หากมีความเกี่ยวข้องกัน

Let 's สำหรับช่วงเวลาที่คิดว่าเป็นจริงฐานของB Dแล้วสำหรับการเรียกร้องให้checkทั้งสองรุ่นจะทำงานได้เพราะHostสามารถแปลงและD* B*เป็นลำดับการแปลงที่ผู้ใช้กำหนดตามที่อธิบายโดย13.3.3.1.2จากHost<B, D>ถึงD*และB*ตามลำดับ สำหรับการค้นหาฟังก์ชันการแปลงที่สามารถแปลงคลาสฟังก์ชันตัวเลือกต่อไปนี้จะถูกสังเคราะห์สำหรับcheckฟังก์ชันแรกตาม13.3.1.5/1

D* (Host<B, D>&)

ฟังก์ชั่นแปลงแรกไม่ได้เป็นผู้สมัครเพราะไม่สามารถแปลงเป็นB*D*

สำหรับฟังก์ชันที่สองมีตัวเลือกดังต่อไปนี้:

B* (Host<B, D> const&)
D* (Host<B, D>&)

นี่คือตัวเลือกฟังก์ชันการแปลงสองตัวที่ใช้วัตถุโฮสต์ ครั้งแรกใช้โดยการอ้างอิง const และครั้งที่สองไม่ได้ ดังนั้นประการที่สองจึงเป็นการจับคู่ที่ดีกว่าสำหรับ*thisวัตถุที่ไม่ใช่ const ( อาร์กิวเมนต์ของวัตถุโดยนัย ) โดย13.3.3.2/3b1sb4และใช้เพื่อแปลงB*เป็นcheckฟังก์ชันที่สอง

หากคุณจะลบ const เราจะมีตัวเลือกดังต่อไปนี้

B* (Host<B, D>&)
D* (Host<B, D>&)

นี่หมายความว่าเราไม่สามารถเลือกตาม constness ได้อีกต่อไป ในสถานการณ์การแก้ปัญหาโอเวอร์โหลดแบบธรรมดาการโทรจะไม่ชัดเจนเพราะโดยปกติประเภทการส่งคืนจะไม่เข้าร่วมในการแก้ปัญหาโอเวอร์โหลด อย่างไรก็ตามสำหรับฟังก์ชันการแปลงมีแบ็คดอร์ 13.3.3/1หากทั้งสองฟังก์ชั่นการแปลงเป็นสิ่งที่ดีอย่างเท่าเทียมกันแล้วประเภทการกลับมาของพวกเขาตัดสินใจที่จะดีที่สุดตาม ดังนั้นถ้าคุณจะเอา const แล้วคนแรกที่จะได้รับเพราะB*แปลงดีกว่าที่จะB*กว่าจะD*B*

ตอนนี้ลำดับการแปลงที่ผู้ใช้กำหนดไว้แบบใดดีกว่า หนึ่งสำหรับฟังก์ชั่นตรวจสอบที่สองหรือครั้งแรก? 13.3.3.2/3b2กฎคือการที่ผู้ใช้กำหนดลำดับการแปลงเท่านั้นที่สามารถเทียบหากพวกเขาใช้ฟังก์ชั่นการแปลงเดียวกันหรือคอนสตรัคตาม นี่เป็นกรณีตรงนี้: ทั้งสองใช้ฟังก์ชันการแปลงที่สอง สังเกตว่าconstมีความสำคัญเนื่องจากบังคับให้คอมไพเลอร์รับฟังก์ชันการแปลงที่สอง

เนื่องจากเราสามารถเปรียบเทียบได้ - อันไหนดีกว่ากัน? กฎคือการแปลงที่ดีกว่าจากประเภทผลตอบแทนของฟังก์ชันการแปลงไปยังประเภทปลายทางจะชนะ (อีกครั้งโดย13.3.3.2/3b2) ในกรณีนี้D*แปลงดีกว่าที่จะไปกว่าการD* B*ดังนั้นฟังก์ชันแรกจึงถูกเลือกและเรารับรู้ถึงการสืบทอด!

สังเกตว่าเนื่องจากเราไม่จำเป็นต้องแปลงเป็นคลาสพื้นฐานจริง ๆเราจึงสามารถรับรู้การสืบทอดส่วนตัวได้เพราะการที่เราจะแปลงจาก a D*เป็น a B*ได้นั้นไม่ได้ขึ้นอยู่กับรูปแบบของการสืบทอดตาม4.10/3

หากไม่เกี่ยวข้องกัน

ตอนนี้สมมติว่าพวกเขาไม่เกี่ยวข้องกับการถ่ายทอดทางพันธุกรรม ดังนั้นสำหรับฟังก์ชันแรกเราจึงมีผู้สมัครดังต่อไปนี้

D* (Host<B, D>&) 

และสำหรับวินาทีนี้เรามีอีกชุดหนึ่ง

B* (Host<B, D> const&)

เนื่องจากเราไม่สามารถแปลงD*เป็นB*ถ้าเราไม่มีความสัมพันธ์ทางมรดกตอนนี้เราจึงไม่มีฟังก์ชันการแปลงร่วมกันระหว่างลำดับการแปลงที่ผู้ใช้กำหนดสองลำดับ! ดังนั้นเราจะคลุมเครือหากไม่ใช่เพราะความจริงที่ว่าฟังก์ชันแรกเป็นเทมเพลต 13.3.3/1แม่แบบทางเลือกที่สองเมื่อมีฟังก์ชั่นที่ไม่ใช่แม่แบบที่เป็นสิ่งที่ดีอย่างเท่าเทียมกันตาม ดังนั้นเราจึงเลือกฟังก์ชันที่ไม่ใช่เทมเพลต (อันที่สอง) และเรารับรู้ว่าไม่มีการสืบทอดระหว่างBและD!


2
อา! Andreas มีย่อหน้าที่ถูกต้อง แต่แย่มากที่เขาไม่ได้ให้คำตอบเช่นนั้น :) ขอบคุณสำหรับเวลาของคุณฉันหวังว่าฉันจะใส่มันได้
Matthieu M.

2
นี่จะเป็นคำตอบที่ฉันชอบที่สุดเลยทีเดียว ... คำถาม: คุณอ่านมาตรฐาน C ++ ทั้งหมดแล้วหรือคุณแค่ทำงานในคณะกรรมการ C ++ ?? ขอแสดงความยินดี!
Marco A.

4
@DavidKernin ที่ทำงานในคอมมิต C ++ ไม่ได้ทำให้คุณรู้โดยอัตโนมัติว่า C ++ ทำงานอย่างไร :) ดังนั้นคุณต้องอ่านส่วนของ Standard ที่จำเป็นเพื่อให้ทราบรายละเอียดซึ่งฉันได้ทำไปแล้ว ยังไม่ได้อ่านทั้งหมดดังนั้นฉันไม่สามารถช่วยอะไรเกี่ยวกับไลบรารีมาตรฐานหรือคำถามที่เกี่ยวข้องกับเธรดส่วนใหญ่ได้ :)
Johannes Schaub - litb

1
@underscore_d เพื่อความเป็นธรรมข้อมูลจำเพาะไม่ได้ห้ามลักษณะ std :: ในการใช้เวทมนตร์ของคอมไพเลอร์เพื่อให้ผู้ใช้ไลบรารีมาตรฐานสามารถใช้พวกเขาได้ พวกเขาจะหลีกเลี่ยงการแสดงผาดโผนแม่แบบซึ่งช่วยเร่งเวลาในการรวบรวมและการใช้หน่วยความจำ นี่คือความจริงแม้ว่าอินเตอร์เฟซที่std::is_base_of<...>ดูเหมือนว่า ทั้งหมดอยู่ภายใต้ประทุน
Johannes Schaub - litb

2
แน่นอนว่าห้องสมุดทั่วไปboost::ต้องการให้แน่ใจว่ามีที่อยู่ภายในเหล่านี้ก่อนใช้งาน และฉันรู้สึกว่ามีความคิด "ยอมรับความท้าทาย" บางอย่างในหมู่พวกเขาที่จะนำสิ่งต่าง ๆ ไปใช้โดยไม่ได้รับความช่วยเหลือจากผู้เรียบเรียง :)
Johannes Schaub - litb

24

มาดูวิธีการทำงานโดยดูขั้นตอน

เริ่มต้นด้วยsizeof(check(Host<B,D>(), int()))ส่วน. คอมไพเลอร์ได้อย่างรวดเร็วสามารถเห็นว่านี้คือการแสดงออกฟังก์ชั่นการโทรดังนั้นจึงต้องการที่จะทำเกินความละเอียดในcheck(...) checkมีผู้สมัครสองคนที่มีโอเวอร์โหลดtemplate <typename T> yes check(D*, T);และno check(B*, int);. หากเลือกอย่างแรกคุณจะได้รับsizeof(yes)อย่างอื่นsizeof(no)

ต่อไปมาดูความละเอียดโอเวอร์โหลด เกินพิกัดแรกคือ instantiation แม่แบบและผู้สมัครที่สองคือcheck<int> (D*, T=int) check(B*, int)ข้อโต้แย้งที่เกิดขึ้นจริงมีให้และHost<B,D> int()พารามิเตอร์ที่สองไม่แยกความแตกต่างอย่างชัดเจน เป็นเพียงการทำหน้าที่เพื่อทำให้เทมเพลตหนึ่งโอเวอร์โหลดครั้งแรก เราจะมาดูกันว่าทำไมส่วนของเทมเพลตจึงมีความเกี่ยวข้อง

ตอนนี้ดูลำดับการแปลงที่จำเป็น สำหรับการโอเวอร์โหลดครั้งแรกเรามีHost<B,D>::operator D*- การแปลงที่ผู้ใช้กำหนดหนึ่งรายการ ประการที่สองการโอเวอร์โหลดนั้นยากกว่า เราต้องการ B * แต่อาจมีลำดับการแปลงสองลำดับ หนึ่งคือผ่านHost<B,D>::operator B*() const. ถ้า (และเฉพาะในกรณีที่) B และ D เกี่ยวข้องกันโดยการสืบทอดจะทำให้ลำดับการแปลงHost<B,D>::operator D*()+ D*->B*มีอยู่ ตอนนี้ถือว่า D แน่นอนสืบทอดจากบีลำดับสองแปลงมีและHost<B,D> -> Host<B,D> const -> operator B* const -> B*Host<B,D> -> operator D* -> D* -> B*

ดังนั้นสำหรับ B และ D ที่เกี่ยวข้องno check(<Host<B,D>(), int())จะคลุมเครือ ด้วยเหตุนี้เทมเพลตyes check<int>(D*, int)จึงถูกเลือก อย่างไรก็ตามหาก D ไม่ได้รับมรดกจาก B ก็no check(<Host<B,D>(), int())ไม่คลุมเครือ ณ จุดนี้ความละเอียดเกินพิกัดไม่สามารถเกิดขึ้นได้ตามลำดับการแปลงที่สั้นที่สุด no check(B*, int)แต่ได้รับการแปลงลำดับเท่ากับความละเอียดเกินชอบฟังก์ชั่นที่ไม่ใช่แม่แบบคือ

ตอนนี้คุณจะเห็นว่าเหตุใดจึงไม่สำคัญว่าการสืบทอดจะเป็นแบบส่วนตัว: ความสัมพันธ์นั้นทำหน้าที่กำจัดออกno check(Host<B,D>(), int())จากความละเอียดเกินพิกัดเท่านั้นก่อนที่การตรวจสอบการเข้าถึงจะเกิดขึ้น และคุณจะเห็นว่าเหตุใดจึงoperator B* constต้องเป็น const: อื่น ๆ ไม่จำเป็นสำหรับHost<B,D> -> Host<B,D> constขั้นตอนไม่มีความคลุมเครือและno check(B*, int)จะถูกเลือกเสมอ


constคำอธิบายของคุณไม่บัญชีสำหรับการปรากฏตัวของ หากคำตอบของคุณเป็นจริงก็ไม่constจำเป็น แต่มันไม่เป็นความจริง ลบconstและหลอกจะไม่ทำงาน
Alexey Malistov

หากไม่มี const ลำดับการแปลงสองรายการno check(B*, int)จะไม่คลุมเครือ
MSalters

หากคุณปล่อยไว้อย่างเดียวแสดงno check(B*, int)ว่าเกี่ยวข้องกันBและDจะไม่คลุมเครือ คอมไพเลอร์จะเลือกที่operator D*()จะทำการแปลงอย่างไม่น่าสงสัยเพราะไม่มี const มันค่อนข้างบิตในทิศทางตรงกันข้าม: ถ้าคุณลบ const คุณแนะนำความรู้สึกของบางคนสงสัย แต่ที่ได้รับการแก้ไขจากข้อเท็จจริงที่ว่าoperator B*()มีผลตอบแทนที่ดีกว่าชนิดซึ่งไม่จำเป็นต้องแปลงตัวชี้ไปB*เหมือนD*ไม่
Johannes Schaub - litb

นั่นคือประเด็นที่แท้จริง: ความคลุมเครืออยู่ระหว่างลำดับการแปลงที่แตกต่างกันสองลำดับเพื่อรับ a B*จาก<Host<B,D>()ชั่วคราว
MSalters

นี่คือคำตอบที่ดีกว่า ขอบคุณ! ดังนั้นตามที่ฉันเข้าใจถ้าฟังก์ชันหนึ่งดีกว่า แต่ไม่ชัดเจนฟังก์ชันอื่นจะถูกเลือก?
user1289

4

privateบิตจะถูกละเว้นสมบูรณ์โดยis_base_ofเพราะความละเอียดเกินเกิดขึ้นก่อนที่จะตรวจสอบการเข้าถึง

คุณสามารถตรวจสอบสิ่งนี้ได้ง่ายๆ:

class Foo
{
public:
  void bar(int);
private:
  void bar(double);
};

int main(int argc, char* argv[])
{
  Foo foo;
  double d = 0.3;
  foo.bar(d);       // Compiler error, cannot access private member function
}

เช่นเดียวกันกับที่นี่ข้อเท็จจริงที่ว่าBเป็นฐานส่วนตัวไม่ได้ป้องกันไม่ให้เกิดการตรวจสอบ แต่จะป้องกันการแปลงเท่านั้น แต่เราไม่เคยขอการแปลงจริง)


เรียงลำดับจาก. ไม่มีการแปลงฐานเลย hostจะถูกแปลงเป็นD*หรือB*ในนิพจน์ที่ไม่ได้ประเมินโดยพลการ ด้วยเหตุผลบางประการD*ควรB*อยู่ภายใต้เงื่อนไขบางประการ
Potatoswatter

ฉันคิดว่าคำตอบอยู่ใน 13.3.1.1.2 แต่ฉันยังไม่ได้เรียงลำดับรายละเอียด :)
Andreas Brinck

คำตอบของฉันอธิบายเฉพาะส่วน "ทำไมถึงทำงานส่วนตัว" แต่คำตอบของ Sellibitze นั้นสมบูรณ์กว่าอย่างแน่นอนแม้ว่าฉันจะรอคำอธิบายที่ชัดเจนเกี่ยวกับกระบวนการแก้ไขปัญหาโดยสมบูรณ์ขึ้นอยู่กับกรณีต่างๆ
Matthieu M.

2

อาจมีบางอย่างที่เกี่ยวข้องกับการสั่งซื้อ WRt overload resolution บางส่วน D * มีความเชี่ยวชาญมากกว่า B * ในกรณีที่ D มาจาก B

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

ฉันไม่จำเป็นต้องค้นหาว่ากฎเหล่านี้โต้ตอบอย่างไร แต่ดูเหมือนว่าการสั่งซื้อบางส่วนจะครอบงำกฎการแก้ปัญหาโอเวอร์โหลดอื่น ๆ เมื่อ D ไม่ได้มาจาก B กฎการสั่งซื้อบางส่วนจะไม่นำไปใช้และเทมเพลตที่ไม่ใช่เทมเพลตนั้นน่าดึงดูดกว่า เมื่อ D มาจาก B การสั่งซื้อบางส่วนจะเข้ามาและทำให้เทมเพลตฟังก์ชันดูน่าสนใจยิ่งขึ้นอย่างที่เห็น

สำหรับการสืบทอดเป็นรางวัล: รหัสไม่เคยขอการแปลงจาก D * เป็น B * ซึ่งจะต้องมีการสืบทอดต่อสาธารณะ


ฉันคิดว่ามันเป็นอย่างนั้นฉันจำได้ว่าเคยเห็นการอภิปรายอย่างกว้างขวางเกี่ยวกับคลังข้อมูลการเพิ่มประสิทธิภาพเกี่ยวกับการใช้งานis_base_ofและลูปที่ผู้ร่วมให้ข้อมูลดำเนินการเพื่อให้แน่ใจว่าสิ่งนี้
Matthieu M.

The exact details are rather complicated- นั่นคือประเด็น กรุณาอธิบาย. ฉันอยากรู้
Alexey Malistov

@ Alexey: ฉันคิดว่าฉันชี้ให้คุณไปในทิศทางที่ถูกต้อง ตรวจสอบว่ากฎการแก้ปัญหาโอเวอร์โหลดต่างๆโต้ตอบกันอย่างไรในกรณีนี้ ความแตกต่างเพียงอย่างเดียวระหว่าง D ที่มาจาก B และ D ที่ไม่ได้มาจาก B ในส่วนที่เกี่ยวกับการแก้ปัญหาของกรณีการโอเวอร์โหลดนี้คือกฎการสั่งซื้อบางส่วน ความละเอียดเกินอธิบายไว้ใน§13ของมาตรฐาน C ++ คุณสามารถรับแบบร่างได้ฟรี: open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1804.pdf
sellibitze

ความละเอียดเกินครอบคลุม 16 หน้าในแบบร่างนั้น ฉันเดาว่าถ้าคุณต้องการเข้าใจกฎและปฏิสัมพันธ์ระหว่างกฎเหล่านี้จริงๆสำหรับกรณีนี้คุณควรอ่านส่วนที่สมบูรณ์§13.3 ฉันจะไม่นับว่าได้รับคำตอบที่ถูกต้อง 100% และตรงตามมาตรฐานของคุณ
sellibitze

โปรดดูคำตอบของฉันสำหรับคำอธิบายหากคุณสนใจ
Johannes Schaub - litb

0

ต่อไปนี้คำถามที่สองของคุณโปรดทราบว่าหากไม่ใช่สำหรับ const โฮสต์จะมีรูปแบบที่ไม่ถูกต้องหากสร้างอินสแตนซ์ด้วย B == D แต่ is_base_of ได้รับการออกแบบมาเพื่อให้แต่ละคลาสเป็นฐานของตัวเองดังนั้นหนึ่งในตัวดำเนินการแปลงจะต้อง เป็น const.

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.