Trong trường hợp cụ thể này, có sự khác biệt giữa việc sử dụng danh sách trình khởi tạo thành viên và gán giá trị trong một phương thức khởi tạo không?


90

Về nội bộ và về mã được tạo, có sự khác biệt thực sự giữa:

MyClass::MyClass(): _capacity(15), _data(NULL), _len(0)
{
}

MyClass::MyClass()
{
  _capacity=15;
  _data=NULL;
  _len=0
}

cảm ơn...

Câu trả lời:


60

Giả sử rằng những giá trị đó là kiểu nguyên thủy, thì không, không có sự khác biệt. Danh sách khởi tạo chỉ tạo ra sự khác biệt khi bạn có các đối tượng là thành viên, vì thay vì sử dụng khởi tạo mặc định theo sau là gán, danh sách khởi tạo cho phép bạn khởi tạo đối tượng tới giá trị cuối cùng của nó. Điều này thực sự có thể nhanh hơn đáng kể.


17
như Richard đã nói, nó tạo ra sự khác biệt nếu những giá trị đó là kiểu nguyên thủy và const, danh sách khởi tạo là cách duy nhất để gán giá trị cho các thành viên const.
thbusch

11
Nó chỉ hoạt động như được mô tả khi các biến không phải là tham chiếu hoặc hằng số, nếu chúng là hằng số hoặc tham chiếu thì nó thậm chí sẽ không biên dịch mà không sử dụng danh sách khởi tạo.
stefanB

77

Bạn cần sử dụng danh sách khởi tạo để khởi tạo các thành viên không đổi, tham chiếu và lớp cơ sở

Khi bạn cần khởi tạo thành viên hằng, các tham chiếu và truyền các tham số đến các hàm tạo lớp cơ sở, như đã đề cập trong phần chú thích, bạn cần sử dụng danh sách khởi tạo.

struct aa
{
    int i;
    const int ci;       // constant member

    aa() : i(0) {} // will fail, constant member not initialized
};

struct aa
{
    int i;
    const int ci;

    aa() : i(0) { ci = 3;} // will fail, ci is constant
};

struct aa
{
    int i;
    const int ci;

    aa() : i(0), ci(3) {} // works
};

Ví dụ (không đầy đủ) class / struct chứa tham chiếu:

struct bb {};

struct aa
{
    bb& rb;
    aa(bb& b ) : rb(b) {}
};

// usage:

bb b;
aa a(b);

Và ví dụ về khởi tạo lớp cơ sở yêu cầu tham số (ví dụ: không có hàm tạo mặc định):

struct bb {};

struct dd
{
    char c;
    dd(char x) : c(x) {}
};

struct aa : dd
{
    bb& rb;
    aa(bb& b ) : dd('a'), rb(b) {}
};

4
Và nếu _capacity, _data_lencó các loại lớp không có hàm tạo mặc định có thể truy cập?
CB Bailey

2
Bạn gọi những hàm tạo nào có sẵn, nếu bạn cần đặt nhiều giá trị hơn thì bạn gọi chúng từ phần thân của hàm tạo của bạn. Sự khác biệt ở đây là bạn không thể khởi tạo constthành viên trong phần thân của phương thức khởi tạo, bạn phải sử dụng danh sách khởi tạo - các constthành viên không phải là thành viên có thể được khởi tạo trong danh sách khởi tạo hoặc trong phần thân của phương thức khởi tạo.
stefanB

@stefan: Bạn không thể gọi hàm tạo. Một lớp không có hàm tạo mặc định phải được khởi tạo trong danh sách bộ khởi tạo, giống như các thành viên const.
GManNickG

2
bạn cũng phải khởi tạo tài liệu tham khảo trong danh sách khởi tạo
Andriy Tylychko

2
@stefanB: Tôi xin lỗi nếu tôi ngụ ý rằng bạn không hiểu điều này khi bạn thực sự hiểu. Trong câu trả lời của bạn, bạn chỉ nêu khi một cơ sở hoặc thành viên phải được đặt tên trong danh sách trình khởi tạo, bạn chưa giải thích sự khác biệt về khái niệm giữa việc khởi tạo trong danh sách trình khởi tạo và việc gán trong phần thân hàm tạo thực sự là gì, đó là nơi tôi hiểu lầm có thể đã đến từ.
CB Bailey

18

Đúng. Trong trường hợp đầu tiên mà bạn có thể khai báo _capacity, _data_lennhư hằng số:

class MyClass
{
private:
    const int _capacity;
    const void *_data;
    const int _len;
// ...
};

Điều này sẽ rất quan trọng nếu bạn muốn đảm bảo tính toàn constvẹn của các biến phiên bản này trong khi tính toán các giá trị của chúng trong thời gian chạy, ví dụ:

MyClass::MyClass() :
    _capacity(someMethod()),
    _data(someOtherMethod()),
    _len(yetAnotherMethod())
{
}

constcác cá thể phải được khởi tạo trong danh sách trình khởi tạo hoặc các kiểu bên dưới phải cung cấp các hàm tạo không tham số công khai (mà các kiểu nguyên thủy làm).


2
Đối với tài liệu tham khảo cũng vậy. Nếu lớp của bạn có các thành viên tham chiếu, chúng phải được khởi tạo trong danh sách trình khởi tạo.
Mark Ransom

7

Tôi nghĩ liên kết này http://www.cplusplus.com/forum/articles/17820/ đưa ra lời giải thích tuyệt vời - đặc biệt là đối với những người mới làm quen với C ++.

Lý do tại sao danh sách intialiser hiệu quả hơn là bên trong phần thân hàm tạo, chỉ các phép gán diễn ra chứ không phải khởi tạo. Vì vậy, nếu bạn đang xử lý một kiểu không dựng sẵn, thì hàm tạo mặc định cho đối tượng đó đã được gọi trước khi phần thân của hàm tạo được nhập vào. Bên trong phần thân hàm tạo, bạn đang gán một giá trị cho đối tượng đó.

Trên thực tế, đây là một lệnh gọi đến hàm tạo mặc định, theo sau là một lệnh gọi đến toán tử gán sao chép. Danh sách trình khởi tạo cho phép bạn gọi trực tiếp hàm tạo bản sao và điều này đôi khi có thể nhanh hơn đáng kể (nhớ lại rằng danh sách trình khởi tạo nằm trước phần thân của hàm tạo)


5

Tôi sẽ nói thêm rằng nếu bạn có các thành viên của loại lớp không có sẵn phương thức khởi tạo mặc định, thì khởi tạo là cách duy nhất để xây dựng lớp của bạn.


3

Một sự khác biệt lớn là phép gán có thể khởi tạo các thành viên của một lớp cha; trình khởi tạo chỉ hoạt động trên các thành viên được khai báo ở phạm vi lớp hiện tại.


2

Phụ thuộc vào các loại liên quan. Sự khác biệt tương tự giữa

std::string a;
a = "hai";

std::string a("hai");

trong đó biểu mẫu thứ hai là danh sách khởi tạo - nghĩa là, nó tạo ra sự khác biệt nếu kiểu yêu cầu đối số phương thức khởi tạo hoặc hiệu quả hơn với đối số phương thức khởi tạo.


1

Sự khác biệt thực sự nằm ở cách trình biên dịch gcc tạo mã máy và bố trí bộ nhớ. Giải thích:

  • (phase1) Trước thân init (bao gồm danh sách init): trình biên dịch cấp phát bộ nhớ cần thiết cho lớp. Lớp học đã sống động rồi!
  • (phase2) Trong phần thân init: vì bộ nhớ được cấp phát, mọi phép gán bây giờ chỉ ra một thao tác trên biến đã thoát / 'khởi tạo'.

Chắc chắn có nhiều cách khác để xử lý các thành viên kiểu const. Nhưng để giảm bớt cuộc sống của họ, những người viết trình biên dịch gcc quyết định thiết lập một số quy tắc

  1. Các thành viên kiểu const phải được khởi tạo trước phần thân init.
  2. Sau giai đoạn 1, bất kỳ hoạt động ghi nào chỉ hợp lệ cho các thành viên không phải là hằng số.

1

Chỉ có một cách để khởi tạo các cá thể lớp cơ sở và các biến thành viên không tĩnh và đó là sử dụng danh sách trình khởi tạo.

Nếu bạn không chỉ định biến cơ sở hoặc biến thành viên không tĩnh trong danh sách trình khởi tạo của phương thức khởi tạo thì thành viên hoặc cơ sở đó sẽ được khởi tạo mặc định (nếu thành viên / cơ sở là kiểu lớp không phải POD hoặc mảng của lớp không phải POD loại) hoặc không được khởi tạo nếu không.

Khi phần thân của hàm tạo được nhập, tất cả các cơ sở hoặc thành viên sẽ được khởi tạo hoặc không được khởi tạo (tức là chúng sẽ có giá trị không xác định). Không có cơ hội nào trong phần thân hàm tạo để ảnh hưởng đến cách chúng nên được khởi tạo.

Bạn có thể gán các giá trị mới cho các thành viên trong phần thân hàm tạo nhưng không thể gán cho constcác thành viên hoặc các thành viên của loại lớp đã được thực hiện là không thể gán được và không thể gắn lại các tham chiếu.

Đối với các kiểu dựng sẵn và một số kiểu do người dùng xác định, việc gán trong phần thân hàm tạo có thể có tác dụng giống hệt như việc khởi tạo với cùng một giá trị trong danh sách trình khởi tạo.

Nếu bạn không đặt tên cho thành viên hoặc cơ sở trong danh sách trình khởi tạo và thực thể đó là tham chiếu, có loại lớp không có phương thức khởi tạo mặc định do người dùng khai báo có thể truy cập, constđủ điều kiện và có kiểu POD hoặc là kiểu lớp POD hoặc mảng thuộc kiểu lớp POD có chứa một constthành viên đủ điều kiện (trực tiếp hoặc gián tiếp) thì chương trình không được hình thành.


0

Nếu bạn viết danh sách trình khởi tạo, bạn thực hiện tất cả trong một bước; nếu bạn không viết danh sách trình khởi tạo, bạn sẽ thực hiện 2 bước: một để khai báo và một để gán giá trị.


0

Có sự khác biệt giữa danh sách khởi tạo và câu lệnh khởi tạo trong một phương thức khởi tạo. Hãy xem xét mã dưới đây:

#include <initializer_list>
#include <iostream>
#include <algorithm>
#include <numeric>

class MyBase {
public:
    MyBase() {
        std::cout << __FUNCTION__ << std::endl;
    }
};

class MyClass : public MyBase {
public:
    MyClass::MyClass() : _capacity( 15 ), _data( NULL ), _len( 0 ) {
        std::cout << __FUNCTION__ << std::endl;
    }
private:
    int _capacity;
    int* _data;
    int _len;
};

class MyClass2 : public MyBase {
public:
    MyClass2::MyClass2() {
        std::cout << __FUNCTION__ << std::endl;
        _capacity = 15;
        _data = NULL;
        _len = 0;
    }
private:
    int _capacity;
    int* _data;
    int _len;
};

int main() {
    MyClass c;
    MyClass2 d;

    return 0;
}

Khi MyClass được sử dụng, tất cả các thành viên sẽ được khởi tạo trước khi câu lệnh đầu tiên trong một phương thức khởi tạo được thực thi.

Tuy nhiên, khi MyClass2 được sử dụng, tất cả các thành viên không được khởi tạo khi câu lệnh đầu tiên trong một phương thức khởi tạo được thực thi.

Trong trường hợp sau, có thể xảy ra sự cố hồi quy khi ai đó thêm một số mã vào một hàm tạo trước khi một thành viên nhất định được khởi tạo.


0

Đây là một điểm mà tôi không thấy những người khác đề cập đến nó:

class temp{
public:
   temp(int var);
};

Lớp tạm thời không có ctor mặc định. Khi chúng tôi sử dụng nó trong một lớp khác như sau:

class mainClass{
public:
 mainClass(){}
private:
  int a;
  temp obj;
};

mã sẽ không biên dịch, vì trình biên dịch không biết cách khởi tạo obj, vì nó chỉ có một ctor rõ ràng nhận giá trị int, vì vậy chúng ta phải thay đổi ctor như sau:

mainClass(int sth):obj(sth){}

Vì vậy, nó không chỉ là về const và tham chiếu!

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.