เมื่อใดที่ฉันจะใช้การประกาศล่วงหน้า


602

ฉันกำลังมองหาคำจำกัดความของเมื่อฉันได้รับอนุญาตให้ทำการประกาศไปข้างหน้าของคลาสในไฟล์ส่วนหัวของคลาสอื่น:

ฉันอนุญาตให้ทำสำหรับคลาสพื้นฐานสำหรับคลาสที่จัดเป็นสมาชิกสำหรับคลาสที่ส่งไปยังฟังก์ชันสมาชิกโดยอ้างอิงหรือไม่?


14
ฉันหมดความต้องการนี้จะเปลี่ยนชื่อเป็น "เมื่อควรฉัน" และคำตอบที่ได้รับการปรับปรุงให้เหมาะสม ...
deworde

12
@deworde เมื่อคุณพูดเมื่อ "ควร" คุณจะถามความคิดเห็น
AturSams

@deworde ฉันเข้าใจว่าคุณต้องการใช้การประกาศล่วงหน้าทุกครั้งที่ทำได้เพื่อปรับปรุงเวลาสร้างและหลีกเลี่ยงการอ้างอิงแบบวงกลม ข้อยกเว้นเพียงอย่างเดียวที่ฉันนึกได้คือเมื่อไฟล์รวมมี typedefs ซึ่งในกรณีนี้จะมีการแลกเปลี่ยนระหว่างการกำหนด typedef ใหม่ (และเสี่ยงต่อการเปลี่ยนแปลง) และรวมไฟล์ทั้งหมด (รวมถึง recursive)
Ohad Schneider

@OhadSchneider จากมุมมองในทางปฏิบัติฉันไม่ได้เป็นแฟนตัวยงของส่วนหัวที่ฉัน ÷
deworde

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

คำตอบ:


962

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

สมมติว่าการประกาศไปข้างหน้าต่อไปนี้

class X;

นี่คือสิ่งที่คุณสามารถทำได้และไม่สามารถทำได้

คุณสามารถทำอะไรกับประเภทที่ไม่สมบูรณ์:

  • ประกาศให้สมาชิกเป็นตัวชี้หรือการอ้างอิงถึงชนิดที่ไม่สมบูรณ์:

    class Foo {
        X *p;
        X &r;
    };
    
  • ประกาศฟังก์ชั่นหรือวิธีการที่รับ / คืนประเภทที่ไม่สมบูรณ์:

    void f1(X);
    X    f2();
    
  • กำหนดฟังก์ชั่นหรือวิธีการที่รับ / ส่งคืนพอยน์เตอร์ / การอ้างอิงถึงประเภทที่ไม่สมบูรณ์ (แต่ไม่ใช้สมาชิก):

    void f3(X*, X&) {}
    X&   f4()       {}
    X*   f5()       {}
    

สิ่งที่คุณไม่สามารถทำได้กับประเภทที่ไม่สมบูรณ์:

  • ใช้เป็นคลาสพื้นฐาน

    class Foo : X {} // compiler error!
  • ใช้มันเพื่อประกาศสมาชิก:

    class Foo {
        X m; // compiler error!
    };
    
  • กำหนดฟังก์ชั่นหรือวิธีการใช้ประเภทนี้

    void f1(X x) {} // compiler error!
    X    f2()    {} // compiler error!
    
  • ใช้วิธีการหรือเขตข้อมูลในความเป็นจริงพยายามที่จะตรวจสอบตัวแปรที่มีประเภทไม่สมบูรณ์

    class Foo {
        X *m;            
        void method()            
        {
            m->someMethod();      // compiler error!
            int i = m->someField; // compiler error!
        }
    };
    

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

ตัวอย่างเช่นstd::vector<T>ต้องการพารามิเตอร์เป็นชนิดที่สมบูรณ์ขณะที่boost::container::vector<T>ไม่มี บางครั้งจำเป็นต้องใช้ชนิดที่สมบูรณ์ถ้าคุณใช้ฟังก์ชันสมาชิกบางอย่างเท่านั้น เป็นกรณีนี้สำหรับstd::unique_ptr<T>ตัวอย่างเช่น

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


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

2
@AndyDent: จริง แต่ผู้บริโภคของส่วนหัวจะต้องรวมการอ้างอิงที่เขาใช้จริงเท่านั้นดังนั้นสิ่งนี้จะเป็นไปตามหลักการ C ++ ของ "คุณจ่ายเฉพาะสิ่งที่คุณใช้" แต่แน่นอนมันอาจไม่สะดวกสำหรับผู้ใช้ที่คาดว่าส่วนหัวจะเป็นแบบสแตนด์อโลน
Luc Touraille

8
ชุดของกฎนี้ละเว้นกรณีที่สำคัญมากหนึ่งกรณี: คุณต้องมีชนิดที่สมบูรณ์เพื่อสร้างอินสแตนซ์ของเทมเพลตส่วนใหญ่ในไลบรารีมาตรฐาน ต้องให้ความสนใจเป็นพิเศษกับสิ่งนี้เนื่องจากการละเมิดกฎทำให้เกิดพฤติกรรมที่ไม่ได้กำหนดและอาจไม่ทำให้เกิดข้อผิดพลาดของคอมไพเลอร์
James Kanze

12
+1 สำหรับ "ทำให้ตัวเองอยู่ในตำแหน่งของคอมไพเลอร์" ฉันจินตนาการว่า "ผู้เรียบเรียง" เป็นคนมีหนวด
PascalVKooten

3
@JesusChrist: ตรง: เมื่อคุณส่งวัตถุตามค่าคอมไพเลอร์จำเป็นต้องรู้ขนาดของมันเพื่อที่จะทำการจัดการสแต็กที่เหมาะสม เมื่อผ่านตัวชี้หรือการอ้างอิงคอมไพเลอร์ไม่จำเป็นต้องมีขนาดหรือรูปแบบของวัตถุเพียงขนาดของที่อยู่ (เช่นขนาดของตัวชี้) ซึ่งไม่ขึ้นอยู่กับประเภทที่ชี้ไป
Luc Touraille

45

กฎหลักคือคุณสามารถส่งต่อคลาสที่มีเลย์เอาต์ของหน่วยความจำ (และฟังก์ชันสมาชิกและสมาชิกข้อมูล) ไม่จำเป็นต้องรู้จักในไฟล์ที่คุณส่งต่อประกาศ

สิ่งนี้จะแยกคลาสพื้นฐานและอะไรก็ได้ยกเว้นคลาสที่ใช้ผ่านการอ้างอิงและพอยน์เตอร์


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

แล้วคลาสที่ฉันต้องการใช้เป็นสมาชิกของคลาสที่ฉันกำหนดในไฟล์ส่วนหัวล่ะ ฉันจะส่งต่อประกาศพวกเขาได้ไหม
Igor Oks

1
ใช่ แต่ในกรณีนั้นคุณสามารถใช้การอ้างอิงหรือตัวชี้ไปยังคลาสที่ประกาศไปข้างหน้าเท่านั้น แต่มันทำให้คุณมีสมาชิกได้อย่างไรก็ตาม
Reunanen

32

Lakosแยกความแตกต่างระหว่างการใช้คลาส

  1. ในชื่อเท่านั้น (ซึ่งมีการประกาศล่วงหน้าเพียงพอ) และ
  2. ขนาด (ซึ่งต้องการคำจำกัดความของคลาส)

ฉันไม่เคยเห็นมันชัดเจนมากขึ้น :)


2
ในชื่อเท่านั้นหมายถึงอะไร
Boon

4
@Boon: ฉันกล้าพูดเหรอ ... ? ถ้าคุณใช้เพียงระดับฯชื่อ ?
Marc Mutz - mmutz

1
บวกหนึ่งสำหรับ Lakos, Marc
mlvljr

28

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

ตัวอย่าง:

struct X;              // Forward declaration of X

void f1(X* px) {}      // Legal: can always use a pointer
void f2(X&  x) {}      // Legal: can always use a reference
X f3(int);             // Legal: return value in function prototype
void f4(X);            // Legal: parameter in function prototype
void f5(X) {}          // ILLEGAL: *definitions* require complete types

19

ไม่มีคำตอบใดที่อธิบายถึงเมื่อสามารถใช้การประกาศไปข้างหน้าของแม่แบบคลาส ดังนั้นนี่มันไป

เทมเพลตชั้นเรียนสามารถถูกส่งต่อเป็นประกาศ:

template <typename> struct X;

ตามโครงสร้างของคำตอบที่ยอมรับแล้ว

นี่คือสิ่งที่คุณสามารถทำได้และไม่สามารถทำได้

คุณสามารถทำอะไรกับประเภทที่ไม่สมบูรณ์:

  • ประกาศให้สมาชิกเป็นตัวชี้หรือการอ้างอิงถึงชนิดที่ไม่สมบูรณ์ในเทมเพลตคลาสอื่น:

    template <typename T>
    class Foo {
        X<T>* ptr;
        X<T>& ref;
    };
    
  • ประกาศให้สมาชิกเป็นตัวชี้หรือการอ้างอิงถึงหนึ่งในอินสแตนซ์ที่ไม่สมบูรณ์:

    class Foo {
        X<int>* ptr;
        X<int>& ref;
    };
    
  • ประกาศแม่แบบฟังก์ชันหรือแม่แบบฟังก์ชันสมาชิกที่ยอมรับ / ส่งคืนชนิดที่ไม่สมบูรณ์:

    template <typename T>
       void      f1(X<T>);
    template <typename T>
       X<T>    f2();
    
  • ประกาศฟังก์ชันหรือฟังก์ชันสมาชิกที่ยอมรับ / ส่งคืนหนึ่งในอินสแตนซ์ที่ไม่สมบูรณ์ของมัน:

    void      f1(X<int>);
    X<int>    f2();
    
  • กำหนดฟังก์ชั่นเทมเพลตหรือฟังก์ชั่นเทมเพลตสมาชิกที่ยอมรับ / ส่งคืนพอยน์เตอร์ / การอ้างอิงถึงประเภทที่ไม่สมบูรณ์ (แต่ไม่ได้ใช้สมาชิก):

    template <typename T>
       void      f3(X<T>*, X<T>&) {}
    template <typename T>
       X<T>&   f4(X<T>& in) { return in; }
    template <typename T>
       X<T>*   f5(X<T>* in) { return in; }
    
  • กำหนดฟังก์ชั่นหรือวิธีการที่รับ / ส่งคืนพอยน์เตอร์ / การอ้างอิงถึงหนึ่งในอินสแตนซ์ที่ไม่สมบูรณ์ (แต่ไม่ได้ใช้สมาชิก):

    void      f3(X<int>*, X<int>&) {}
    X<int>&   f4(X<int>& in) { return in; }
    X<int>*   f5(X<int>* in) { return in; }
    
  • ใช้เป็นคลาสพื้นฐานของคลาสเทมเพลตอื่น

    template <typename T>
    class Foo : X<T> {} // OK as long as X is defined before
                        // Foo is instantiated.
    
    Foo<int> a1; // Compiler error.
    
    template <typename T> struct X {};
    Foo<int> a2; // OK since X is now defined.
    
  • ใช้เพื่อประกาศสมาชิกของเทมเพลตคลาสอื่น:

    template <typename T>
    class Foo {
        X<T> m; // OK as long as X is defined before
                // Foo is instantiated. 
    };
    
    Foo<int> a1; // Compiler error.
    
    template <typename T> struct X {};
    Foo<int> a2; // OK since X is now defined.
    
  • กำหนดแม่แบบฟังก์ชันหรือวิธีการที่ใช้ประเภทนี้

    template <typename T>
      void    f1(X<T> x) {}    // OK if X is defined before calling f1
    template <typename T>
      X<T>    f2(){return X<T>(); }  // OK if X is defined before calling f2
    
    void test1()
    {
       f1(X<int>());  // Compiler error
       f2<int>();     // Compiler error
    }
    
    template <typename T> struct X {};
    
    void test2()
    {
       f1(X<int>());  // OK since X is defined now
       f2<int>();     // OK since X is defined now
    }
    

สิ่งที่คุณไม่สามารถทำได้กับประเภทที่ไม่สมบูรณ์:

  • ใช้หนึ่งในอินสแตนซ์ของมันเป็นคลาสพื้นฐาน

    class Foo : X<int> {} // compiler error!
  • ใช้หนึ่งในอินสแตนซ์ของมันเพื่อประกาศสมาชิก:

    class Foo {
        X<int> m; // compiler error!
    };
    
  • กำหนดฟังก์ชันหรือเมธอดโดยใช้หนึ่งในอินสแตนซ์

    void      f1(X<int> x) {}            // compiler error!
    X<int>    f2() {return X<int>(); }   // compiler error!
    
  • ใช้เมธอดหรือฟิลด์ของอินสแตนซ์ของหนึ่งในความเป็นจริงพยายามตรวจสอบตัวแปรที่มีชนิดไม่สมบูรณ์

    class Foo {
        X<int>* m;            
        void method()            
        {
            m->someMethod();      // compiler error!
            int i = m->someField; // compiler error!
        }
    };
    
  • สร้างอินสแตนซ์ของแม่แบบคลาสที่ชัดเจน

    template struct X<int>;

2
"ยังไม่มีคำตอบใดที่อธิบายได้ว่าเมื่อใดที่มีการประกาศไปข้างหน้าของแม่แบบคลาส" นั่นไม่ใช่เพียงเพราะความหมายของXและX<int>เหมือนกันเท่านั้นและมีเพียงไวยากรณ์การประกาศไปข้างหน้าแตกต่างกันในวิธีการที่สำคัญใด ๆ แต่ทั้งหมด 1 บรรทัดของคำตอบของคุณเทียบเท่ากับการเอาลุคและs/X/X<int>/g? มันจำเป็นจริงๆเหรอ? หรือฉันพลาดรายละเอียดเล็ก ๆ มันเป็นไปได้ แต่ผมเคยเทียบสายตาไม่กี่ครั้งและไม่สามารถมองเห็นใด ๆ ...
underscore_d

ขอบคุณ! การแก้ไขนั้นเพิ่มข้อมูลที่มีค่ามากมาย ฉันจะต้องอ่านมันหลาย ๆ ครั้งเพื่อที่จะเข้าใจอย่างถ่องแท้ ... หรืออาจจะใช้วิธีการที่ดีกว่ารอจนกว่าฉันจะสับสนในโค้ดจริงและกลับมาที่นี่! ฉันสงสัยว่าฉันจะสามารถใช้สิ่งนี้เพื่อลดการพึ่งพาในที่ต่างๆ
underscore_d

4

ในไฟล์ที่คุณใช้เพียงตัวชี้หรือการอ้างอิงถึงคลาสและไม่ควรเรียกใช้ฟังก์ชันสมาชิก / สมาชิกคิดว่าตัวชี้ / การอ้างอิงเหล่านั้น

ด้วยclass Foo;// ประกาศไปข้างหน้า

เราสามารถประกาศสมาชิกข้อมูลประเภท Foo * หรือ Foo &

เราสามารถประกาศ (แต่ไม่นิยาม) ฟังก์ชันที่มีอาร์กิวเมนต์และ / หรือค่าส่งคืนประเภท Foo

เราสามารถประกาศสมาชิกข้อมูลคงที่ประเภท Foo นี่เป็นเพราะสมาชิกข้อมูลคงที่มีการกำหนดไว้นอกคำจำกัดความของชั้นเรียน


4

ฉันเขียนสิ่งนี้เป็นคำตอบที่แยกต่างหากแทนที่จะแสดงความคิดเห็นเพราะฉันไม่เห็นด้วยกับคำตอบของ Luc Touraille ไม่ใช่เพราะเหตุผลทางกฎหมาย แต่สำหรับซอฟต์แวร์ที่มีประสิทธิภาพและอันตรายจากการตีความที่ผิด

โดยเฉพาะฉันมีปัญหากับสัญญาโดยนัยของสิ่งที่คุณคาดหวังว่าผู้ใช้อินเทอร์เฟซของคุณต้องรู้

หากคุณส่งคืนหรือยอมรับประเภทการอ้างอิงคุณเพียงแค่บอกว่าพวกเขาสามารถผ่านตัวชี้หรือการอ้างอิงที่พวกเขาอาจรู้จักเท่านั้นผ่านการประกาศไปข้างหน้า

เมื่อคุณส่งคืนชนิดที่ไม่สมบูรณ์X f2();จากนั้นคุณกำลังบอกว่าผู้เรียกของคุณต้องมีข้อมูลจำเพาะชนิดเต็มของ X พวกเขาต้องการเพื่อสร้าง LHS หรือวัตถุชั่วคราวที่ไซต์การโทร

หากคุณยอมรับประเภทที่ไม่สมบูรณ์ผู้เรียกจะต้องสร้างวัตถุซึ่งเป็นพารามิเตอร์ แม้ว่าวัตถุนั้นจะถูกส่งกลับเป็นประเภทที่ไม่สมบูรณ์อื่นจากฟังก์ชั่นไซต์การโทรต้องมีการประกาศแบบเต็ม เช่น:

class X;  // forward for two legal declarations 
X returnsX();
void XAcceptor(X);

XAcepptor( returnsX() );  // X declaration needs to be known here

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

ยกเว้น

  1. หากการพึ่งพาภายนอกนี้เป็นพฤติกรรมที่ต้องการ แทนที่จะใช้การคอมไพล์แบบมีเงื่อนไขคุณสามารถมีข้อกำหนดที่มีเอกสารที่ดีสำหรับพวกเขาในการจัดหาส่วนหัวของตนเองที่ประกาศ X ซึ่งเป็นทางเลือกแทนการใช้ #ifdefs และอาจเป็นวิธีที่มีประโยชน์ในการแนะนำ mocks หรือตัวแปรอื่น ๆ

  2. ความแตกต่างที่สำคัญคือเทคนิคเทมเพลตบางอย่างที่คุณไม่คาดหวังว่าจะยกตัวอย่างพวกเขาอย่างชัดเจนกล่าวถึงเพียงบางคนไม่ได้พูดจากับฉัน


"ฉันคิดว่ามีหลักการสำคัญที่ส่วนหัวควรให้ข้อมูลเพียงพอที่จะใช้โดยไม่ต้องพึ่งพาส่วนหัวอื่น ๆ " - ปัญหาอื่นถูกกล่าวถึงในความคิดเห็นโดย Adrian McCarthy ตามคำตอบของ Naveen นี่เป็นเหตุผลที่ไม่ควรทำตาม "ควรให้ข้อมูลที่เพียงพอต่อการใช้" แม้ในขณะนี้ยังไม่ได้ใช้เทมเพลต
Tony Delroy

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

1
I disagree with Luc Touraille's answerดังนั้นเขียนความคิดเห็นรวมถึงลิงค์ไปยังโพสต์บล็อกถ้าคุณต้องการความยาว สิ่งนี้ไม่ตอบคำถามที่ถาม หากทุกคนคิดว่าคำถามเกี่ยวกับวิธีการทำงานของ X คำตอบที่ไม่เหมาะสมกับ X ทำเช่นนั้นหรือ จำกัด ขอบเขตการโต้วาทีซึ่งเราควร จำกัด เสรีภาพในการใช้ X - เราไม่มีคำตอบที่แท้จริง
underscore_d

3

กฎทั่วไปที่ฉันติดตามจะไม่รวมไฟล์ส่วนหัวใด ๆ เว้นแต่ฉันจะต้องทำ ดังนั้นถ้าฉันกำลังเก็บวัตถุของคลาสเป็นตัวแปรสมาชิกของคลาสของฉันฉันจะไม่รวมมันฉันจะใช้การประกาศไปข้างหน้า


2
สิ่งนี้จะทำลาย encapsulation และทำให้โค้ดเปราะ ในการทำเช่นนี้คุณต้องทราบว่าประเภทนั้นเป็น typedef หรือคลาสสำหรับเทมเพลตคลาสที่มีพารามิเตอร์เท็มเพลตเริ่มต้นและหากการใช้งานมีการเปลี่ยนแปลงคุณจะต้องอัปเดตตำแหน่งที่คุณเคยใช้การประกาศไปข้างหน้า
Adrian McCarthy

@AdrianMcCarthy ถูกต้องและวิธีแก้ไขที่เหมาะสมคือมีส่วนหัวของการประกาศไปข้างหน้าซึ่งรวมอยู่ในส่วนหัวซึ่งเนื้อหาจะถูกประกาศไปข้างหน้าซึ่งควรจะเป็นเจ้าของ / ดูแล / จัดส่งโดยผู้ใดก็ตามที่เป็นเจ้าของส่วนหัวนั้น ตัวอย่างเช่น: ส่วนหัวของไลบรารี iosfwd Standard ซึ่งมีการประกาศไปข้างหน้าของเนื้อหา iostream
Tony Delroy

3

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


0

โดยปกติคุณจะต้องการใช้การประกาศไปข้างหน้าในไฟล์ส่วนหัวของคลาสเมื่อคุณต้องการใช้ชนิดอื่น ๆ (คลาส) เป็นสมาชิกของคลาส คุณไม่สามารถใช้วิธีการเรียนที่ประกาศไปข้างหน้าในไฟล์ส่วนหัวได้เนื่องจาก C ++ ไม่ทราบคำจำกัดความของคลาสนั้น ณ จุดนั้น นั่นคือตรรกะที่คุณต้องย้ายเข้าไปในไฟล์. cpp แต่ถ้าคุณใช้ฟังก์ชั่นเทมเพลตคุณควรลดมันลงไปในส่วนที่ใช้เทมเพลตและย้ายฟังก์ชั่นนั้นไปไว้ที่ส่วนหัว


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

0

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


2
ทำไม 2 คนไม่ลงคะแนนในเรื่องนี้? คุณไม่ได้พูดถึงสิ่งที่คำถามกำลังพูดถึง คุณหมายถึงปกติ - ไม่ไปข้างหน้า - การประกาศของฟังก์ชั่น คำถามคือเกี่ยวกับการคาดการณ์ล่วงหน้าประกาศของชั้นเรียน ในขณะที่คุณพูดว่า "การประกาศล่วงหน้าจะทำให้โค้ดของคุณรวบรวม" โปรดให้ฉัน: คอมไพล์class A; class B { A a; }; int main(){}และแจ้งให้เราทราบว่ามันจะไปได้อย่างไร แน่นอนมันจะไม่รวบรวม คำตอบที่เหมาะสมทั้งหมดที่นี่อธิบายว่าทำไมและบริบทที่ จำกัด และแม่นยำซึ่งการประกาศล่วงหน้านั้นถูกต้อง คุณแทนที่จะเขียนสิ่งนี้เกี่ยวกับบางสิ่งที่แตกต่างออกไปโดยสิ้นเชิง
underscore_d

0

ฉันต้องการเพิ่มสิ่งสำคัญที่คุณสามารถทำได้กับคลาสที่ส่งต่อซึ่งไม่ได้กล่าวถึงในคำตอบของ Luc Touraille

คุณสามารถทำอะไรกับประเภทที่ไม่สมบูรณ์:

กำหนดฟังก์ชั่นหรือวิธีการที่รับ / ส่งคืนพอยน์เตอร์ / การอ้างอิงถึงประเภทที่ไม่สมบูรณ์และส่งต่อที่ตัวชี้ / การอ้างอิงไปยังฟังก์ชั่นอื่น

void  f6(X*)       {}
void  f7(X&)       {}
void  f8(X* x_ptr, X& x_ref) { f6(x_ptr); f7(x_ref); }

โมดูลสามารถส่งผ่านวัตถุของคลาสที่ประกาศไปข้างหน้าไปยังโมดูลอื่น


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

0

ในฐานะที่เป็น Luc Touraille ได้อธิบายแล้วมันเป็นอย่างดีที่จะใช้และไม่ได้ใช้การประกาศไปข้างหน้าของชั้นเรียน

ฉันจะเพิ่มไปที่ทำไมเราต้องใช้มัน

เราควรใช้การประกาศไปข้างหน้าทุกที่ที่เป็นไปได้เพื่อหลีกเลี่ยงการฉีดที่ต้องพึ่งพา

เนื่องจาก#includeไฟล์ส่วนหัวถูกเพิ่มในหลายไฟล์ดังนั้นหากเราเพิ่มส่วนหัวในไฟล์ส่วนหัวอื่นมันจะเพิ่มการเพิ่มการพึ่งพาที่ไม่พึงประสงค์ในส่วนต่าง ๆ ของซอร์สโค้ดซึ่งสามารถหลีกเลี่ยงได้โดยการเพิ่ม#includeส่วนหัวลงใน.cppไฟล์ทุกที่ที่เป็นไปได้ ใช้การประกาศไปข้างหน้าของคลาสที่เป็นไปได้ใน.hไฟล์ส่วนหัว

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