สมาชิกคลาส C ++ จะเริ่มต้นอย่างไรถ้าฉันไม่ทำอย่างชัดเจน?


158

สมมติว่าฉันได้เรียนกับ memebers ส่วนตัวptr, name, pname, rname, และcrname ageจะเกิดอะไรขึ้นถ้าฉันไม่เริ่มต้นด้วยตนเอง นี่คือตัวอย่าง:

class Example {
    private:
        int *ptr;
        string name;
        string *pname;
        string &rname;
        const string &crname;
        int age;

    public:
        Example() {}
};

จากนั้นฉันก็:

int main() {
    Example ex;
}

สมาชิกจะเริ่มต้นอย่างไรในอดีต? เกิดอะไรขึ้นกับพอยน์เตอร์? ทำstringและintรับ 0-intialized กับตัวสร้างเริ่มต้นstring()และint()? สมาชิกอ้างอิงล่ะ? ยังเกี่ยวกับการอ้างอิง const?

ฉันควรรู้เรื่องอะไรอีก

ไม่มีใครรู้กวดวิชาที่ครอบคลุมกรณีเหล่านี้หรือไม่? บางทีในหนังสือบางเล่ม? ฉันสามารถเข้าถึงห้องสมุดของมหาวิทยาลัยเพื่อรับหนังสือ C ++ มากมาย

ฉันต้องการเรียนรู้เพื่อที่ฉันจะสามารถเขียนโปรแกรมได้ดีขึ้น (ไม่มีข้อผิดพลาด) ข้อเสนอแนะใด ๆ ที่จะช่วย!


3
สำหรับคำแนะนำหนังสือดูstackoverflow.com/questions/388242/…
Mike Seymour

ไมค์โอ๊ยฉันหมายถึงบทจากหนังสือบางเล่มที่อธิบายได้ ไม่ใช่ทั้งเล่ม! :)
bodacydo

มันอาจเป็นความคิดที่ดีที่จะอ่านหนังสือทั้งเล่มเกี่ยวกับภาษาที่คุณตั้งใจจะเขียนโปรแกรม และถ้าคุณอ่านแล้วและไม่ได้อธิบายสิ่งนี้มันก็ไม่ได้เป็นหนังสือที่ดีมาก
Tyler McHenry

2
สกอตต์เมเยอร์ส (ปรมาจารย์แนะนำ C ++ อดีตยอดนิยม) ระบุในC ++ ที่มีประสิทธิภาพ "กฎมีความซับซ้อน - ซับซ้อนเกินไปที่จะคุ้มค่าในการท่องจำในความคิดของฉัน .... ตรวจสอบให้แน่ใจว่า ดังนั้นในความเห็นของเขาวิธีที่ง่ายที่สุดในการเขียนรหัส "บั๊กฟรี" ไม่ใช่การพยายามจดจำกฎ (และในความเป็นจริงเขาไม่ได้วางมันลงในหนังสือ) แต่จะเริ่มต้นทุกอย่างอย่างชัดเจน อย่างไรก็ตามโปรดทราบว่าแม้ว่าคุณจะใช้วิธีการนี้ในรหัสของคุณเองคุณอาจทำงานในโครงการที่เขียนโดยคนที่ไม่ทำดังนั้นกฎอาจยังมีค่า
Kyle Strand

2
@TylerMcHenry คุณมีหนังสืออะไรใน C ++ ที่คิดว่า "ดี" ฉันอ่านหนังสือหลายเล่มใน C ++ แล้ว แต่ก็ไม่มีใครอธิบายเรื่องนี้ได้เลย ตามที่ระบุไว้ในความคิดเห็นก่อนหน้าของฉัน, สกอตต์เมเยอร์สอย่างชัดเจนปฏิเสธที่จะให้กฎระเบียบทั้งหมดในที่มีประสิทธิภาพ C ++ ฉันได้อ่านยังเมเยอร์สโมเดิร์นที่มีประสิทธิภาพ C ++ , Dewhurst ของความรู้ทั่วไปภาษา C ++และ Stroustrup ของทัวร์ของ C ในความทรงจำของฉันไม่มีใครอธิบายกฎที่สมบูรณ์ได้ เห็นได้ชัดว่าฉันสามารถอ่านมาตรฐาน แต่ฉันแทบจะไม่คิดว่าหนังสือดี ๆ ! : D และผมคาดว่า Stroustrup อาจอธิบายได้ในc ++ เขียนโปรแกรมภาษา
ไคล์สาระ

คำตอบ:


207

แทนการเริ่มต้นที่ชัดเจนการเริ่มต้นของสมาชิกในชั้นเรียนทำงานเหมือนกับการเริ่มต้นของตัวแปรท้องถิ่นในการทำงาน

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

สำหรับประเภทดึกดำบรรพ์ (พอยน์เตอร์, ints, ฯลฯ ) พวกมันไม่ได้ถูกกำหนดค่าเริ่มต้น - พวกมันมีสิ่งที่ขยะโดยพลการเกิดขึ้นที่ตำแหน่งหน่วยความจำนั้นก่อนหน้านี้

สำหรับการอ้างอิง (เช่นstd::string&) มันผิดกฎหมายที่จะไม่เริ่มต้นและผู้รวบรวมของคุณจะบ่นและปฏิเสธที่จะรวบรวมรหัสดังกล่าว การอ้างอิงจะต้องเริ่มต้นเสมอ

ดังนั้นในกรณีเฉพาะของคุณหากยังไม่ได้กำหนดค่าเริ่มต้นอย่างชัดเจน:

    int *ptr;  // Contains junk
    string name;  // Empty string
    string *pname;  // Contains junk
    string &rname;  // Compile error
    const string &crname;  // Compile error
    int age;  // Contains junk

4
+1 มันเป็นที่น่าสังเกตว่าโดยการกำหนดมาตรฐานที่เข้มงวดกรณีของชนิดดั้งเดิมพร้อมกับความหลากหลายของสิ่งอื่น ๆ (ภูมิภาคของการจัดเก็บใด ๆ ) จะมีการพิจารณาทั้งหมดวัตถุ
stinky472

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

19
@wiz ฉันคิดว่าเขาหมายถึงแท้จริง 'ถ้าวัตถุไม่มีคอนสตรัคเตอร์เริ่มต้น' ในขณะที่ไม่มีการสร้างเช่นกันซึ่งจะเป็นกรณีหากคลาสกำหนดคอนสตรัคเตอร์ใด ๆ นอกเหนือจากค่าเริ่มต้นอย่างชัดเจน หากเราได้รับการอึกทึกมากเกินไปเราอาจจะสับสนมากกว่าความช่วยเหลือและไทเลอร์พูดถึงเรื่องนี้ในจุดที่เขาตอบผมมาก่อน
stinky472

8
@ wiz-loz ฉันอยากจะบอกว่าfooมันมีคอนสตรัคเตอร์มันแค่บอกเป็นนัย แต่นั่นเป็นข้อโต้แย้งของความหมาย
Tyler McHenry

4
ฉันตีความ "ตัวสร้างเริ่มต้น" เป็นตัวสร้างซึ่งสามารถเรียกได้โดยไม่มีข้อโต้แย้ง นี่อาจเป็นสิ่งที่คุณกำหนดเองหรือคอมไพเลอร์ที่สร้างขึ้นโดยปริยาย ดังนั้นการขาดมันหมายถึงไม่ได้กำหนดโดยตัวคุณเองหรือสร้าง หรือว่าฉันเห็นมัน
5

28

ก่อนอื่นให้ฉันอธิบายว่าmem-initializer-listคืออะไร ข่าว-initializer รายการเป็นรายการที่คั่นด้วยเครื่องหมายจุลภาคข่าว-initializer s ซึ่งแต่ละข่าว-initializerเป็นชื่อสมาชิกตามด้วย(ตามด้วยการแสดงออกของรายการ)ตามด้วย รายการนิพจน์คือวิธีสร้างสมาชิก ตัวอย่างเช่นใน

static const char s_str[] = "bodacydo";
class Example
{
private:
    int *ptr;
    string name;
    string *pname;
    string &rname;
    const string &crname;
    int age;

public:
    Example()
        : name(s_str, s_str + 8), rname(name), crname(name), age(-4)
    {
    }
};

ข่าว-initializer รายการname(s_str, s_str + 8), rname(name), crname(name), age(-4)ของตัวสร้างผู้ใช้จัดไม่มีข้อโต้แย้งคือ นี้ข่าว-initializer รายการหมายถึงว่าnameสมาชิกจะเริ่มต้นโดยคอนสตรัคที่ใช้ iterators ใส่สองที่สมาชิกเริ่มต้นได้ด้วยการอ้างอิงถึงการที่สมาชิกจะเริ่มต้นด้วย const-อ้างอิงถึงและสมาชิกจะเริ่มต้นด้วยค่าstd::stringrnamenamecrnamenameage-4

คอนสตรัคเตอร์แต่ละตัวมีmem-initializer-listของตัวเองและสมาชิกสามารถเริ่มต้นได้ในลำดับที่กำหนด (โดยพื้นฐานแล้วลำดับที่สมาชิกจะประกาศในคลาส) ดังนั้นสมาชิกของExampleเท่านั้นที่สามารถเริ่มต้นได้ในการสั่งซื้อ: ptr, name, pname, rname, และcrnameage

เมื่อคุณไม่ได้ระบุmem-initializerของสมาชิกมาตรฐาน C ++ จะพูดว่า:

หากเอนทิตีนั้นเป็นสมาชิกข้อมูลที่ไม่คงที่ ... ของประเภทคลาส ... แสดงว่าเอนทิตีนั้นเริ่มต้นได้ (8.5) ... มิฉะนั้นจะไม่มีการเริ่มต้นเอนทิตี

ที่นี่เพราะnameเป็นข้อมูลสมาชิก nonstatic ประเภทชั้นมันเป็นค่าเริ่มต้นเริ่มต้นได้หากไม่มีการเริ่มต้นสำหรับการnameได้รับการระบุไว้ในข่าว-initializer รายการ สมาชิกคนอื่น ๆ ทั้งหมดExampleไม่มีประเภทของชั้นเรียนดังนั้นจึงไม่ได้เริ่มต้น

เมื่อมาตรฐานบอกว่าพวกเขาไม่ได้เริ่มต้นซึ่งหมายความว่าพวกเขาสามารถมีค่าใด ๆ ดังนั้นเนื่องจากรหัสข้างต้นไม่ได้เริ่มต้นpnameก็อาจเป็นอะไรก็ได้

โปรดทราบว่าคุณยังต้องปฏิบัติตามกฎอื่น ๆ เช่นกฎที่จะต้องเริ่มต้นการอ้างอิงเสมอ มันเป็นข้อผิดพลาดของคอมไพเลอร์ที่จะไม่เริ่มต้นการอ้างอิง


นี่เป็นวิธีที่ดีที่สุดในการเริ่มต้นสมาชิกเมื่อคุณต้องการแยกการประกาศ (ใน.h) และคำจำกัดความ (ใน.cpp) โดยไม่แสดง internals มากเกินไป
Matthieu

12

คุณยังสามารถเริ่มสมาชิกข้อมูล ณ จุดที่คุณประกาศได้:

class another_example{
public:
    another_example();
    ~another_example();
private:
    int m_iInteger=10;
    double m_dDouble=10.765;
};

ฉันใช้แบบฟอร์มนี้ค่อนข้างพิเศษแม้ว่าฉันจะได้อ่านบางคนคิดว่ามันเป็น 'รูปแบบที่ไม่ดี' อาจเป็นเพราะมันเพิ่งเปิดตัวเมื่อเร็ว ๆ นี้ - ฉันคิดว่าใน C ++ 11 สำหรับฉันมันเป็นตรรกะมากขึ้น

อีกแง่มุมที่มีประโยชน์สำหรับกฎใหม่คือวิธีการเตรียมใช้งานข้อมูลสมาชิกที่เป็นคลาสตัวเอง ตัวอย่างเช่นสมมติว่าCDynamicStringเป็นคลาสที่ห่อหุ้มการจัดการสตริง CDynamicString(wchat_t* pstrInitialString)มันมีคอนสตรัคที่ช่วยให้คุณระบุค่าเริ่มต้นของ คุณอาจใช้คลาสนี้เป็นสมาชิกข้อมูลภายในคลาสอื่นได้ดี - พูดคลาสที่สรุปค่ารีจิสทรี windows ซึ่งในกรณีนี้เก็บที่อยู่ทางไปรษณีย์ หากต้องการ 'hard code' ชื่อคีย์รีจิสตรีที่คุณใช้วงเล็บปีกกา:

class Registry_Entry{
public:
    Registry_Entry();
    ~Registry_Entry();
    Commit();//Writes data to registry.
    Retrieve();//Reads data from registry;
private:
    CDynamicString m_cKeyName{L"Postal Address"};
    CDynamicString m_cAddress;
};

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


9

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

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

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

  • int *ptr; // ตัวชี้เริ่มต้น (หรือเป็นศูนย์ถ้าเป็นโกลบอล)
  • string name; // นวกรรมิกเรียกว่าเริ่มต้นด้วยสตริงที่ว่างเปล่า
  • string *pname; // ตัวชี้เริ่มต้น (หรือเป็นศูนย์ถ้าเป็นโกลบอล)
  • string &rname; // ข้อผิดพลาดการคอมไพล์หากคุณไม่สามารถเริ่มต้นนี้
  • const string &crname; // ข้อผิดพลาดการคอมไพล์หากคุณไม่สามารถเริ่มต้นนี้
  • int age; // ค่าสเกลาร์ไม่กำหนดค่าเริ่มต้นและสุ่ม (หรือเป็นศูนย์ถ้าเป็นโกลบอล)

ฉันทดลองและดูเหมือนว่าstring nameจะว่างเปล่าหลังจากเริ่มต้นชั้นเรียนในกอง คุณแน่ใจเกี่ยวกับคำตอบของคุณหรือไม่?
bodacydo

1
สตริงจะมีตัวสร้างที่ให้สตริงว่างโดยค่าเริ่มต้น - ฉันจะชี้แจงคำตอบของฉัน
Paul Dixon

@bodacydo: Paul ถูกต้อง แต่ถ้าคุณสนใจเกี่ยวกับพฤติกรรมนี้มันไม่เคยเจ็บที่จะชัดเจน ทิ้งไว้ในรายการ initializer
สตีเฟ่น

ขอบคุณสำหรับการชี้แจงและอธิบาย!
bodacydo

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

5

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

แน่นอนสำหรับพารามิเตอร์วัตถุ (เช่นstring) คอนสตรัคของวัตถุสามารถทำการเริ่มต้นเริ่มต้น

ในตัวอย่างของคุณ:

int *ptr; // will point to a random memory location
string name; // empty string (due to string's default costructor)
string *pname; // will point to a random memory location
string &rname; // it would't compile
const string &crname; // it would't compile
int age; // random value

2

สมาชิกที่มีคอนสตรัคเตอร์จะมีคอนสตรัคเตอร์เริ่มต้นของพวกเขาสำหรับการเริ่มต้น

คุณไม่สามารถพึ่งพาเนื้อหาประเภทอื่นได้


0

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

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


0

ขึ้นอยู่กับวิธีการสร้างชั้นเรียน

การตอบคำถามนี้มาพร้อมกับความเข้าใจถึงคำสั่ง switch case ขนาดใหญ่ในมาตรฐานภาษา C ++ และสิ่งที่ยากสำหรับมนุษย์ทั่วไปที่จะได้รับสัญชาตญาณ

เป็นตัวอย่างง่าย ๆ ของสิ่งที่ยาก:

main.cpp

#include <cassert>

int main() {
    struct C { int i; };

    // This syntax is called "default initialization"
    C a;
    // i undefined

    // This syntax is called "value initialization"
    C b{};
    assert(b.i == 0);
}

ในการเริ่มต้นเริ่มต้นคุณจะเริ่มต้นจาก: https://en.cppreference.com/w/cpp/language/default_initializationเราไปที่ส่วน "ผลกระทบของการเริ่มต้นเริ่มต้นคือ" และเริ่มคำสั่งกรณี:

  • "ถ้า T เป็นnon-POD ": ไม่ (คำจำกัดความของ POD อยู่ในคำสั่งสวิตช์ขนาดใหญ่ในตัวเอง)
  • "ถ้า T เป็นประเภทอาเรย์": ไม่
  • "ไม่เช่นนั้นจะไม่มีสิ่งใดทำ": ดังนั้นจึงมีค่าที่ไม่ได้กำหนด

จากนั้นถ้ามีคนตัดสินใจที่จะให้ค่าเริ่มต้นเราจะไปที่https://en.cppreference.com/w/cpp/language/value_initialization "ผลกระทบของการเริ่มต้นค่าคือ" และเริ่มต้นคำสั่ง case:

  • "ถ้า T เป็นประเภทคลาสที่ไม่มีตัวสร้างเริ่มต้นหรือด้วยตัวสร้างเริ่มต้นที่ผู้ใช้ให้หรือลบ": ไม่ใช่ตัวพิมพ์ ตอนนี้คุณจะใช้เวลา 20 นาที Googling คำเหล่านี้:
    • เรามีคอนสตรัคเตอร์เริ่มต้นที่กำหนดโดยปริยาย (โดยเฉพาะอย่างยิ่งเนื่องจากไม่มีการกำหนดคอนสตรัคอื่น)
    • มันไม่ได้ให้ผู้ใช้ (กำหนดโดยนัย)
    • มันไม่ถูกลบ ( = delete)
  • "ถ้า T เป็นประเภทคลาสที่มีตัวสร้างเริ่มต้นที่ไม่ได้ระบุหรือลบโดยผู้ใช้": ใช่
    • "วัตถุนั้นมีค่าเริ่มต้นเป็นศูนย์และจากนั้นมันจะเป็นค่าเริ่มต้นถ้ามันมีตัวสร้างปริยายที่ไม่ใช่เรื่องเล็กน้อย": ไม่มีตัวสร้างที่ไม่ใช่เรื่องไร้สาระเพียงแค่เริ่มต้นเป็นศูนย์ คำจำกัดความของ "zero-initialize" อย่างน้อยนั้นง่ายและทำในสิ่งที่คุณคาดหวัง: https://en.cppreference.com/w/cpp/language/zero_initialization

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

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