Tại sao tôi nên sử dụng danh sách khởi tạo thành viên?


228

Tôi là một phần trong việc sử dụng danh sách khởi tạo thành viên với các nhà xây dựng của mình ... nhưng từ lâu tôi đã quên mất những lý do đằng sau việc này ...

Bạn có sử dụng danh sách khởi tạo thành viên trong các nhà xây dựng của bạn? Nếu vậy, tại sao? Nếu không, tai sao không?


3
Các lý do được liệt kê ở đây ... https://www.geekforgeek.org/when-do-we-use-initializer-list-in-c/
u8it

Câu trả lời:


278

Đối với các thành viên lớp POD , điều đó không có gì khác biệt, đó chỉ là vấn đề về phong cách. Đối với các thành viên lớp là các lớp, thì nó sẽ tránh một cuộc gọi không cần thiết đến một hàm tạo mặc định. Xem xét:

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

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

Trong trường hợp này, hàm tạo Bsẽ gọi hàm tạo mặc định A, sau đó khởi tạo a.xthành 3. Cách tốt hơn là hàm tạo của hàm Bgọi trực tiếp Ahàm tạo trong danh sách khởi tạo:

B()
  : a(3)
{
}

Điều này sẽ chỉ gọi A's A(int)constructor và không constructor mặc định của nó. Trong ví dụ này, sự khác biệt là không đáng kể, nhưng hãy tưởng tượng nếu bạn sẽ Axây dựng mặc định đó sẽ làm nhiều hơn, chẳng hạn như phân bổ bộ nhớ hoặc mở tệp. Bạn sẽ không muốn làm điều đó một cách không cần thiết.

Hơn nữa, nếu một lớp không có hàm tạo mặc định hoặc bạn có constbiến thành viên, bạn phải sử dụng danh sách trình khởi tạo:

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
một điều bắt buộc cũng là cho trường hợp quan trọng của một tài liệu tham khảo
4pie0

5
Tại sao không sử dụng "a (3);" hoặc "a = A (3);" trong phần thân của hàm tạo mặc định của B?
Serge

1
Bạn có thể giải thích, ý của bạn với POD không?
Jonas Stein

2
@JonasStein POD là một bộ quy tắc được xác định rõ liên quan đến các cấu trúc dữ liệu đơn giản (thay vì các lớp hoàn chỉnh). Đọc Câu hỏi thường gặp để biết thêm: stackoverflow.com/questions/146452/what-are-pod-types-in-c
khỉ0506 28/03/2016

2
@Sergey, hàm tạo mặc định của A vẫn sẽ được gọi.
Vassilis

44

Ngoài các lý do hiệu suất được đề cập ở trên, nếu lớp của bạn lưu trữ các tham chiếu đến các đối tượng được truyền dưới dạng tham số của hàm tạo hoặc lớp của bạn có các biến const thì bạn không có lựa chọn nào khác ngoại trừ sử dụng danh sách trình khởi tạo.


7
Tôi cũng tin như vậy với các thành viên const.
Richard Corden

có, không thể sử dụng phép gán để sửa đổi các biến const để nó phải được khởi tạo.
Hareen Laks

23
  1. Khởi tạo lớp cơ sở

Một lý do quan trọng để sử dụng danh sách trình khởi tạo hàm tạo không được đề cập trong câu trả lời ở đây là khởi tạo lớp cơ sở.

Theo thứ tự xây dựng, lớp cơ sở nên được xây dựng trước lớp con. Không có danh sách trình khởi tạo hàm tạo, điều này là có thể nếu lớp cơ sở của bạn có hàm tạo mặc định sẽ được gọi ngay trước khi vào hàm tạo của lớp con.

Nhưng, nếu lớp cơ sở của bạn chỉ có hàm tạo được tham số hóa, thì bạn phải sử dụng danh sách trình khởi tạo hàm tạo để đảm bảo rằng lớp cơ sở của bạn được khởi tạo trước lớp con.

  1. Khởi tạo các Subobject chỉ có các hàm tạo tham số

  2. Hiệu quả

Sử dụng danh sách trình khởi tạo hàm tạo, bạn khởi tạo các thành viên dữ liệu của mình theo trạng thái chính xác mà bạn cần trong mã của mình thay vì khởi tạo chúng về trạng thái mặc định của chúng và sau đó thay đổi trạng thái của chúng thành trạng thái bạn cần trong mã.

  1. Khởi tạo thành viên dữ liệu const không tĩnh

Nếu các thành viên dữ liệu const không tĩnh trong lớp của bạn có các hàm tạo mặc định và bạn không sử dụng danh sách trình khởi tạo hàm tạo, bạn sẽ không thể khởi tạo chúng về trạng thái dự định vì chúng sẽ được khởi tạo ở trạng thái mặc định.

  1. Khởi tạo thành viên dữ liệu tham chiếu

Các thành viên dữ liệu tham chiếu phải được xác định khi trình biên dịch vào hàm tạo vì các tham chiếu không thể được khai báo & khởi tạo sau này. Điều này chỉ có thể với danh sách khởi tạo constructor.


10

Bên cạnh các vấn đề về hiệu năng, có một vấn đề khác rất quan trọng mà tôi gọi là khả năng duy trì và mở rộng mã.

Nếu T là POD và bạn bắt đầu thích danh sách khởi tạo, thì nếu một lần T sẽ thay đổi thành loại không phải POD, bạn sẽ không cần thay đổi bất cứ điều gì xung quanh việc khởi tạo để tránh các lệnh gọi của hàm tạo không cần thiết vì nó đã được tối ưu hóa.

Nếu loại T không có hàm tạo mặc định và một hoặc nhiều hàm tạo do người dùng xác định và một lần bạn quyết định xóa hoặc ẩn kiểu mặc định, thì nếu danh sách khởi tạo được sử dụng, bạn không cần cập nhật mã nếu các hàm tạo do người dùng xác định chúng đã được thực hiện chính xác.

Tương tự với các thành viên const hoặc thành viên tham chiếu, giả sử ban đầu T được định nghĩa như sau:

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

Tiếp theo, bạn quyết định đủ điều kiện là const, nếu bạn sẽ sử dụng danh sách khởi tạo từ đầu, thì đây là một thay đổi dòng đơn, nhưng có T được định nghĩa như trên, nó cũng yêu cầu đào định nghĩa hàm tạo để xóa gán:

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

Không có gì bí mật rằng việc bảo trì dễ dàng hơn và ít bị lỗi hơn nếu mã được viết không phải bởi "khỉ mã" mà bởi một kỹ sư đưa ra quyết định dựa trên sự cân nhắc sâu hơn về những gì anh ta đang làm.


5

Trước khi phần thân của hàm tạo được chạy, tất cả các hàm tạo cho lớp cha của nó và sau đó cho các trường của nó được gọi. Theo mặc định, các hàm tạo không có đối số được gọi. Danh sách khởi tạo cho phép bạn chọn hàm tạo nào được gọi và đối số mà hàm tạo nhận được.

Nếu bạn có một tham chiếu hoặc trường const hoặc nếu một trong các lớp được sử dụng không có hàm tạo mặc định, bạn phải sử dụng danh sách khởi tạo.


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;
    }
};

Ở đây trình biên dịch tuân theo các bước sau để tạo một đối tượng kiểu MyClass
1. Trình xây dựng của kiểu được gọi đầu tiên cho một số một.
2. Toán tử gán của Kiểu Kiểu xếp được gọi bên trong phần thân của hàm tạo MyClass () để gán

variable = a;
  1. Và cuối cùng, công cụ phá hủy của Kiểu Loại được gọi là cho một trò chơi vì nó đi ra khỏi phạm vi.

    Bây giờ hãy xem xét cùng mã với hàm tạo MyClass () với Danh sách khởi tạo

    // 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
    }
    };

    Với Danh sách khởi tạo, các bước sau được trình biên dịch theo sau:

    1. Trình xây dựng sao chép của lớp Loại Kiểu được gọi để khởi tạo: biến (a). Các đối số trong danh sách khởi tạo được sử dụng để sao chép trực tiếp cấu trúc biến biến trực tiếp.
    2. Kẻ hủy diệt của Kiểu Loại được gọi là cho một tên lửa vì nó đi ra khỏi phạm vi.

2
Mặc dù đoạn mã này có thể giải quyết câu hỏi, bao gồm giải thích về mã thực sự giúp cải thiện chất lượng bài đăng của bạn. Hãy nhớ rằng bạn đang trả lời câu hỏi cho độc giả trong tương lai và những người đó có thể không biết lý do cho đề xuất mã của bạn. Xin vui lòng cố gắng không làm đông mã của bạn với các bình luận giải thích, điều này làm giảm khả năng đọc của cả mã và các giải thích! meta.stackexchange.com/q/114762/308249
davejal

2
Xin vui lòng, viết sự hiểu biết của riêng bạn hoặc chỉ chia sẻ liên kết đến nguồn ban đầu (ở đây, geekforgeek.com) thay vì chỉ sao chép-dán nó.
yuvi

1

Chỉ cần thêm một số thông tin bổ sung để chứng minh danh sách khởi tạo thành viên có thể khác nhau bao nhiêu . Trong Truy vấn Sum Range leetcode 303 - Không thể thay đổi, https://leetcode.com/probols/range-sum-query-immutable/ , trong đó bạn cần xây dựng và khởi tạo thành một vectơ có kích thước nhất định. Đây là hai thực hiện khác nhau và so sánh tốc độ.

Nếu không có danh sách khởi tạo thành viên , để có được AC, tôi mất khoảng 212 ms .

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];
}
};

Bây giờ sử dụng danh sách khởi tạo thành viên , thời gian để nhận AC là khoảng 108 ms . Với ví dụ đơn giản này, khá rõ ràng rằng, danh sách khởi tạo thành viên là cách hiệu quả hơn . Tất cả các phép đo là từ thời gian chạy từ 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

Cú pháp:

  class Sample
  {
     public:
         int Sam_x;
         int Sam_y;

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

Danh sách cần khởi tạo:

 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
  };

trong chương trình trên, khi hàm tạo của lớp được thực thi, Sam_xSam_y được tạo. Sau đó, trong thân xây dựng, các biến dữ liệu thành viên được xác định.

Trường hợp sử dụng:

  1. Các biến Const và Reference trong một Class

Trong C, các biến phải được xác định trong quá trình tạo. tương tự như trong C ++, chúng ta phải khởi tạo biến Const và Reference trong quá trình tạo đối tượng bằng cách sử dụng danh sách Khởi tạo. nếu chúng ta khởi tạo sau khi tạo đối tượng (bên trong thân hàm xây dựng), chúng ta sẽ gặp lỗi thời gian biên dịch.

  1. Các đối tượng thành viên của lớp Sample1 (cơ sở) không có hàm tạo mặc định

     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 */
      {
    
      }
     };

Trong khi tạo đối tượng cho lớp dẫn xuất, nó sẽ gọi bên trong hàm tạo của lớp dẫn xuất và gọi hàm tạo của lớp cơ sở (mặc định). nếu lớp cơ sở không có hàm tạo mặc định, người dùng sẽ gặp lỗi thời gian biên dịch. Để tránh, chúng ta phải có một trong hai

 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. Tên tham số của hàm tạo lớp và thành viên dữ liệu của một lớp giống nhau:

     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; 
        }
     };

Như chúng ta đã biết, biến cục bộ có mức ưu tiên cao nhất sau đó là biến toàn cục nếu cả hai biến có cùng tên. Trong trường hợp này, chương trình xem xét giá trị "i" {cả biến bên trái và bên phải. tức là: i = i} là biến cục bộ trong hàm tạo mẫu 3 () và biến thành viên lớp (i) có ghi đè. Để tránh, chúng ta phải sử dụng một trong hai

  1. Initialization list 
  2. this operator.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.