เหตุใดฉันจึงควรใช้รายการเริ่มต้นสมาชิก


228

ฉันมีบางส่วนที่จะใช้รายการเริ่มต้นสมาชิกกับตัวสร้างของฉัน ... แต่ฉันนานลืมเหตุผลที่อยู่เบื้องหลังนี้ ...

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


3
เหตุผลที่แสดงไว้ที่นี่ ... https://www.geeksforgeeks.org/when-do-we-use-initializer-list-in-c/
u8it

คำตอบ:


278

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

class A
{
public:
    A() { x = 0; }
    A(int x_) { x = x_; }
    int x;
};

class B
{
public:
    B()
    {
        a.x = 3;
    }
private:
    A a;
};

ในกรณีนี้ตัวสร้างสำหรับBจะเรียกตัวสร้างเริ่มต้นสำหรับAแล้วเริ่มต้นa.xที่ 3 วิธีที่ดีกว่าสำหรับตัวBสร้างของการเรียกตัวสร้างโดยตรงAในรายการเริ่มต้น:

B()
  : a(3)
{
}

นี้เท่านั้นที่จะเรียกA's A(int)คอนสตรัคและไม่ได้สร้างการเริ่มต้นของ ในตัวอย่างนี้ความแตกต่างนั้นเล็กน้อย แต่ลองคิดดูว่าคุณจะAเป็นผู้สร้างเริ่มต้นที่ทำได้มากกว่าเช่นการจัดสรรหน่วยความจำหรือเปิดไฟล์ คุณไม่ต้องการที่จะทำเช่นนั้นโดยไม่จำเป็น

นอกจากนี้หากคลาสไม่มีคอนสตรัคเตอร์เริ่มต้นหรือคุณมีconstตัวแปรสมาชิกคุณต้องใช้รายการ initializer:

class A
{
public:
    A(int x_) { x = x_; }
    int x;
};

class B
{
public:
    B() : a(3), y(2)  // 'a' and 'y' MUST be initialized in an initializer list;
    {                 // it is an error not to do so
    }
private:
    A a;
    const int y;
};

5
ต้องเป็นกรณีที่สำคัญสำหรับการอ้างอิงด้วย
4pie0

5
ทำไมไม่ใช้ "a (3);" หรือ "a = A (3);" ในเนื้อความของตัวสร้างเริ่มต้นของ B หรือไม่
Sergey

1
คุณช่วยอธิบายได้ไหมว่าคุณมีความหมายอย่างไรกับ POD
Jonas Stein

2
@JonasStein POD เป็นชุดของกฎที่กำหนดไว้อย่างดีเกี่ยวกับโครงสร้างข้อมูลอย่างง่าย (แทนที่จะเป็นคลาสที่สมบูรณ์) อ่านคำถามที่พบบ่อยเพิ่มเติมได้ที่: stackoverflow.com/questions/146452/what-are-pod-types-in-c
monkey0506

2
@Sergey ตัวสร้างปริยายของ A จะยังคงถูกเรียกใช้
Vassilis

44

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


7
กันไปสำหรับสมาชิก const ฉันเชื่อว่า
Richard Corden

ใช่ไม่สามารถใช้การกำหนดค่าเพื่อแก้ไขตัวแปร const ดังนั้นจึงต้องเริ่มต้นใหม่
Hareen Laks

23
  1. การเริ่มต้นของคลาสฐาน

เหตุผลหนึ่งที่สำคัญสำหรับการใช้รายการตัวสร้างเริ่มต้นซึ่งไม่ได้กล่าวถึงในคำตอบที่นี่คือการเริ่มต้นของชั้นฐาน

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

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

  1. การเริ่มต้นของ Subobjects ซึ่งมีคอนสตรัคเตอร์ที่กำหนดพารามิเตอร์เท่านั้น

  2. อย่างมีประสิทธิภาพ

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

  1. การเริ่มต้นสมาชิกข้อมูล const ที่ไม่คงที่

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

  1. การเริ่มต้นของสมาชิกข้อมูลอ้างอิง

สมาชิกข้อมูลอ้างอิงจะต้องเป็น intialized เมื่อคอมไพเลอร์เข้าสู่ Constructor เนื่องจากการอ้างอิงไม่สามารถประกาศและเริ่มต้นได้ในภายหลัง นี่เป็นไปได้เฉพาะกับรายการตัวสร้างเริ่มต้น


10

ถัดจากปัญหาด้านประสิทธิภาพมีอีกสิ่งหนึ่งที่สำคัญมากซึ่งฉันต้องการเรียกรหัสการบำรุงรักษาและการขยาย

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

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

เช่นเดียวกับสมาชิก const หรือสมาชิกอ้างอิงสมมติว่าเริ่มแรก T ถูกกำหนดดังนี้:

struct T
{
    T() { a = 5; }
private:
    int a;
};

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

struct T
{
    T() : a(5) {} // 2. that requires changes here too
private:
    const int a; // 1. one line change
};

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


5

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

หากคุณมีการอ้างอิงหรือเขตข้อมูล const หรือหากหนึ่งในคลาสที่ใช้ไม่มีคอนสตรัคเตอร์เริ่มต้นคุณต้องใช้รายการเริ่มต้น


2
// Without Initializer List
class MyClass {
    Type variable;
public:
    MyClass(Type a) {  // Assume that Type is an already
                     // declared class and it has appropriate 
                     // constructors and operators
        variable = a;
    }
};

คอมไพเลอร์ที่นี่ทำตามขั้นตอนต่อไปนี้เพื่อสร้างวัตถุประเภท MyClass
1 คอนสตรัคเตอร์ประเภทนี้เรียกว่า "a" เป็นอันดับแรก
2. ผู้ดำเนินการที่ได้รับมอบหมายของ“ ประเภท” เรียกว่าภายในร่างกายของ MyClass () คอนสตรัคที่จะกำหนด

variable = a;
  1. จากนั้นในที่สุด destructor ของ“ Type” จะถูกเรียกว่า“ a” เนื่องจากมันอยู่นอกขอบเขต

    ตอนนี้ให้พิจารณาโค้ดเดียวกันกับตัวสร้าง MyClass () พร้อมกับ Initializer List

    // With Initializer List
     class MyClass {
    Type variable;
    public:
    MyClass(Type a):variable(a) {   // Assume that Type is an already
                     // declared class and it has appropriate
                     // constructors and operators
    }
    };

    ด้วยรายการเริ่มต้นทำตามขั้นตอนต่อไปนี้ตามด้วยคอมไพเลอร์:

    1. ตัวสร้างการคัดลอกของคลาส“ ชนิด” ถูกเรียกเพื่อเริ่มต้น: ตัวแปร (a) อาร์กิวเมนต์ในรายการ initializer ใช้เพื่อคัดลอกสร้าง“ ตัวแปร” โดยตรง
    2. Destructor ของ“ Type” ถูกเรียกว่า“ a” เนื่องจากไม่ได้อยู่ในขอบเขต

2
ในขณะที่ข้อมูลโค้ดนี้อาจแก้ไขคำถามรวมถึงคำอธิบายจากโค้ดช่วยในการปรับปรุงคุณภาพของโพสต์ของคุณ จำไว้ว่าคุณกำลังตอบคำถามสำหรับผู้อ่านในอนาคตและคนเหล่านั้นอาจไม่ทราบสาเหตุของการแนะนำรหัสของคุณ โปรดอย่าพยายามทำให้รหัสของคุณแน่นเกินไปด้วยคำอธิบายที่อธิบายซึ่งจะช่วยลดความสามารถในการอ่านของทั้งรหัสและคำอธิบาย! meta.stackexchange.com/q/114762/308249
davejal

2
กรุณาเขียนความเข้าใจของคุณเองหรือแชร์ลิงก์ไปยังแหล่งต้นฉบับ (ที่นี่ geeksforgeeks.com) แทนที่จะคัดลอกมาวางไว้
yuvi

1

เพียงแค่เพิ่มข้อมูลเพิ่มเติมเพื่อแสดงให้เห็นว่ารายการการเริ่มต้นสมาชิกแตกต่างกันมากเพียงใด ใน leetcode 303 Range Sum Query - ไม่เปลี่ยนรูปhttps://leetcode.com/problems/range-sum-query-immutable/ซึ่งคุณต้องสร้างและเริ่มต้นเป็นศูนย์เวกเตอร์ที่มีขนาดที่แน่นอน นี่คือการใช้งานและการเปรียบเทียบความเร็วสองแบบที่แตกต่างกัน

โดยไม่ต้องเริ่มต้นสมาชิกรายการที่จะได้รับ AC ค่าใช้จ่ายฉันเกี่ยวกับ212 มิลลิวินาที

class NumArray {
public:
vector<int> preSum;
NumArray(vector<int> nums) {
    preSum = vector<int>(nums.size()+1, 0);
    int ps = 0;
    for (int i = 0; i < nums.size(); i++)
    {
        ps += nums[i];
        preSum[i+1] = ps;
    }
}

int sumRange(int i, int j) {
    return preSum[j+1] - preSum[i];
}
};

ตอนนี้ใช้รายการเริ่มต้นสมาชิกเวลาที่จะได้รับ AC เป็นเรื่องเกี่ยวกับ108 มิลลิวินาที ด้วยตัวอย่างง่ายๆนี้ก็ค่อนข้างชัดเจนว่ารายการเริ่มต้นสมาชิกเป็นวิธีที่มีประสิทธิภาพมากขึ้น การวัดทั้งหมดมาจากเวลาทำงานจาก LC

class NumArray {
public:
vector<int> preSum;
NumArray(vector<int> nums) : preSum(nums.size()+1, 0) { 
    int ps = 0;
    for (int i = 0; i < nums.size(); i++)
    {
        ps += nums[i];
        preSum[i+1] = ps;
    }
}

int sumRange(int i, int j) {
    return preSum[j+1] - preSum[i];
}
};

0

ไวยากรณ์:

  class Sample
  {
     public:
         int Sam_x;
         int Sam_y;

     Sample(): Sam_x(1), Sam_y(2)     /* Classname: Initialization List */
     {
           // Constructor body
     }
  };

ต้องการเริ่มต้นรายการ:

 class Sample
 {
     public:
         int Sam_x;
         int Sam_y;

     Sample()     */* Object and variables are created - i.e.:declaration of variables */*
     { // Constructor body starts 

         Sam_x = 1;      */* Defining a value to the variable */* 
         Sam_y = 2;

     } // Constructor body ends
  };

ในโปรแกรมด้านบนเมื่อตัวสร้างคลาสดำเนินการSam_xและSam_yจะถูกสร้างขึ้น จากนั้นในคอนสตรัคเตอร์ตัวแปรข้อมูลสมาชิกจะถูกกำหนด

ใช้กรณี:

  1. ตัวแปร Const และ Reference ใน Class

ใน C ต้องกำหนดตัวแปรในระหว่างการสร้าง เช่นเดียวกับใน C ++ เราจะต้องเริ่มต้นตัวแปร Const และ Reference ระหว่างการสร้างวัตถุ ถ้าเราเริ่มต้นหลังจากการสร้างวัตถุ (ภายในตัวสร้าง) เราจะได้รับข้อผิดพลาดในเวลารวบรวม

  1. สมาชิกวัตถุของคลาส Sample1 (ฐาน) ซึ่งไม่มีตัวสร้างเริ่มต้น

     class Sample1 
     {
         int i;
         public:
         Sample1 (int temp)
         {
            i = temp;
         }
     };
    
      // Class Sample2 contains object of Sample1 
     class Sample2
     {
      Sample1  a;
      public:
      Sample2 (int x): a(x)      /* Initializer list must be used */
      {
    
      }
     };

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

 1. Default constructor of Sample1 class
 2. Initialization list in Sample2 class which will call the parametric constructor of Sample1 class (as per above program)
  1. ชื่อพารามิเตอร์ของตัวสร้างคลาสและสมาชิกข้อมูลของคลาสเดียวกัน:

     class Sample3 {
        int i;         /* Member variable name : i */  
        public:
        Sample3 (int i)    /* Local variable name : i */ 
        {
            i = i;
            print(i);   /* Local variable: Prints the correct value which we passed in constructor */
        }
        int getI() const 
        { 
             print(i);    /*global variable: Garbage value is assigned to i. the expected value should be which we passed in constructor*/
             return i; 
        }
     };

อย่างที่เราทุกคนรู้กันว่าตัวแปรท้องถิ่นที่มีความสำคัญสูงสุดและตัวแปรทั่วโลกหากตัวแปรทั้งสองมีชื่อเหมือนกัน ในกรณีนี้โปรแกรมพิจารณาค่า "i" {ตัวแปรด้านซ้ายและด้านขวา เช่น: i = i} เป็นตัวแปรโลคัลใน Sample3 () Constructor และ Class Member variable (i) มีการแทนที่ เพื่อหลีกเลี่ยงเราจะต้องใช้อย่างใดอย่างหนึ่ง

  1. Initialization list 
  2. this operator.

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