2008-09-23 61 views
517

Quy tắc C++ để gọi hàm tạo siêu lớp từ một lớp con là gì? Ví dụ, tôi biết trong Java, bạn phải làm điều đó như là dòng đầu tiên của hàm tạo lớp con (và nếu bạn không làm như vậy, một lời gọi ngầm đến một hàm tạo siêu no-arg sẽ được giả định - cho bạn một lỗi biên dịch. Ví dụ: nếu đó là mất tích).Các quy tắc để gọi hàm tạo siêu lớp là gì?

Trả lời

717

Trình tạo lớp cơ sở sẽ tự động được gọi cho bạn nếu chúng không có đối số. Nếu bạn muốn gọi một hàm tạo siêu lớp với một đối số, bạn phải sử dụng danh sách khởi tạo hàm tạo của lớp con. Không giống như Java, C++ hỗ trợ đa kế thừa (cho tốt hơn hoặc tệ hơn), vì vậy lớp cơ sở phải được gọi theo tên chứ không phải là "super()".

class SuperClass 
{ 
    public: 

     SuperClass(int foo) 
     { 
      // do something with foo 
     } 
}; 

class SubClass : public SuperClass 
{ 
    public: 

     SubClass(int foo, int bar) 
     : SuperClass(foo) // Call the superclass constructor in the subclass' initialization list. 
     { 
      // do something with bar 
     } 
}; 

Thông tin thêm về danh sách khởi tạo của hàm tạo herehere.

+36

tôi loại bỏ 'rõ ràng' từ các nhà xây dựng SuperClass. Mặc dù là một thực hành tốt nhất cho các nhà xây dựng một đối số, nhưng nó không phải là vấn đề nảy sinh trong cuộc thảo luận. Để biết thêm thông tin về từ khóa rõ ràng, hãy xem: http://weblogs.asp.net/kennykerr/archive/2004/08/31/Explicit-Constructors.aspx – luke

+1

toán tử dấu hai chấm mà bạn đã sử dụng để gọi hàm tạo siêu lớp trước khi tạo con class constructor, tôi cho rằng điều này cũng đúng đối với các phương thức? – ha9u63ar

+2

@hagubear, chỉ hợp lệ cho các nhà xây dựng, AFAIK – luke

14

Nếu bạn có một hàm tạo không có đối số, nó sẽ được gọi trước khi hàm tạo lớp dẫn xuất được thực hiện.

Nếu bạn muốn gọi một cơ sở-constructor với lập luận bạn phải viết một cách rõ ràng rằng trong các nhà xây dựng có nguồn gốc như thế này:

class base 
{ 
    public: 
    base (int arg) 
    { 
    } 
}; 

class derived : public base 
{ 
    public: 
    derived() : base (number) 
    { 
    } 
}; 

Bạn không thể xây dựng một lớp được thừa kế mà không gọi các nhà xây dựng các bậc cha mẹ trong C++. Điều đó xảy ra tự động nếu đó là một C'tor không phải là arg, nó sẽ xảy ra nếu bạn gọi trực tiếp hàm khởi tạo như được hiển thị ở trên hoặc mã của bạn sẽ không biên dịch.

16

Cách duy nhất để chuyển giá trị cho một hàm tạo gốc là thông qua danh sách khởi tạo. Danh sách initilization được thực hiện với một: và sau đó một danh sách các lớp và các giá trị được truyền cho hàm tạo của lớp đó.

Class2::Class2(string id) : Class1(id) { 
.... 
} 

Cũng nên nhớ rằng nếu bạn có một hàm tạo không có tham số trên lớp cha, nó sẽ tự động được gọi trước khi thực thi hàm con.

174

Trong C++, các hàm tạo không đối số cho tất cả các siêu lớp và các biến thành viên được gọi cho bạn, trước khi nhập hàm tạo của bạn. Nếu bạn muốn vượt qua họ lập luận, có một cú pháp riêng biệt cho gọi là "constructor chaining" này, trông như thế này:

class Sub : public Base 
{ 
    Sub(int x, int y) 
    : Base(x), member(y) 
    { 
    } 
    Type member; 
}; 

Nếu bất cứ điều gì chạy vào thời điểm này ném, các căn cứ/thành viên mà trước đó đã hoàn thành xây dựng có người hủy diệt được gọi và ngoại lệ được trả lại cho người gọi. Nếu bạn muốn bắt ngoại lệ trong chaining, bạn phải sử dụng một khối chức năng thử:

class Sub : public Base 
{ 
    Sub(int x, int y) 
    try : Base(x), member(y) 
    { 
    // function body goes here 
    } catch(const ExceptionType &e) { 
    throw kaboom(); 
    } 
    Type member; 
}; 

Ở dạng này, lưu ý rằng khối try các cơ quan chức năng, chứ không phải là bên trong cơ thể của chức năng; điều này cho phép nó bắt các ngoại lệ được đưa ra bởi các khởi tạo lớp và lớp cơ sở ngầm hoặc ngầm, cũng như trong phần thân của hàm. Tuy nhiên, nếu một khối catch chức năng không ném một ngoại lệ khác, thời gian chạy sẽ trả về lỗi ban đầu; ngoại lệ trong khi khởi tạo không thể bỏ qua.

+1

Tôi không chắc tôi hiểu cú pháp của ví dụ thứ hai của bạn ... Có phải try/catch xây dựng một sự thay thế cho phần thân của hàm tạo không? – levik

+2

Có. Tôi đã viết lại phần này và sửa lỗi (từ khóa thử đi trước danh sách khởi tạo). Tôi nên đã tìm nó lên thay vì viết từ bộ nhớ, nó không phải cái gì đó được sử dụng thường xuyên :-) – puetzk

+76

Cảm ơn bạn đã bao gồm cú pháp try/catch cho initializers. Tôi đã sử dụng C++ trong 10 năm, và đây là lần đầu tiên tôi từng thấy điều đó. – jakar

6
CDerived::CDerived() 
: CBase(...), iCount(0) //this is the initialisation list. You can initialise member variables here too. (e.g. iCount := 0) 
    { 
    //construct body 
    } 
40

Trong C++ có một khái niệm về danh sách khởi tạo của hàm tạo, là nơi bạn có thể và nên gọi hàm tạo của lớp cơ sở và nơi bạn cũng nên khởi tạo thành viên dữ liệu. Danh sách khởi tạo xuất hiện sau chữ ký của hàm tạo sau dấu hai chấm và trước phần thân của hàm tạo. Hãy nói rằng chúng tôi có một lớp A:


class A : public B 
{ 
public: 
    A(int a, int b, int c); 
private: 
    int b_, c_; 
}; 

Sau đó, giả sử B có một constructor mà phải mất một int, A của constructor có thể trông như thế này:


A::A(int a, int b, int c) 
    : B(a), b_(b), c_(c) // initialization list 
{ 
    // do something 
} 

Như bạn có thể thấy, các nhà xây dựng của lớp cơ sở được gọi trong danh sách khởi tạo. Việc khởi tạo các thành viên dữ liệu trong danh sách khởi tạo, bằng cách này, là thích hợp hơn để gán các giá trị cho b_ và c_ bên trong phần thân của hàm tạo bởi vì bạn đang tiết kiệm chi phí bổ sung cho việc gán.

Hãy nhớ rằng các thành viên dữ liệu luôn được khởi tạo theo thứ tự mà chúng được khai báo trong định nghĩa lớp, bất kể thứ tự của chúng trong danh sách khởi tạo. Để tránh các lỗi lạ, có thể phát sinh nếu các thành viên dữ liệu của bạn phụ thuộc vào nhau, bạn nên luôn đảm bảo thứ tự của các thành viên giống nhau trong danh sách khởi tạo và định nghĩa lớp. Vì lý do tương tự, hàm tạo lớp cơ sở phải là mục đầu tiên trong danh sách khởi tạo. Nếu bạn bỏ qua nó hoàn toàn, thì hàm tạo mặc định cho lớp cơ sở sẽ được gọi tự động. Trong trường hợp đó, nếu lớp cơ sở không có một hàm tạo mặc định, bạn sẽ nhận được một lỗi trình biên dịch.

+1

Đợi một chút ... Bạn nói người khởi tạo tiết kiệm chi phí cho các bài tập. Nhưng không cùng một nhiệm vụ diễn ra bên trong chúng nếu được gọi? – levik

+5

Không. Init và nhiệm vụ là những thứ khác nhau. Khi một constructor được gọi, nó sẽ cố gắng khởi tạo mọi thành viên dữ liệu với bất cứ thành phần nào mà nó nghĩ là giá trị mặc định. Trong danh sách init bạn có thể cung cấp các giá trị mặc định. Vì vậy, bạn phải chịu chi phí khởi tạo trong cả hai trường hợp. – Dima

+1

Và nếu bạn sử dụng chuyển nhượng bên trong cơ thể, thì bạn phải chịu chi phí khởi tạo, và sau đó chi phí chuyển nhượng trên đầu trang đó. – Dima

11

Mọi người đều đề cập đến một hàm tạo thông qua danh sách khởi tạo, nhưng không ai nói rằng hàm tạo của lớp cha có thể được gọi một cách rõ ràng từ phần tử của hàm tạo. Xem câu hỏi Calling a constructor of the base class from a subclass' constructor body, ví dụ. Vấn đề là nếu bạn sử dụng một lời gọi rõ ràng cho một lớp cha hoặc một hàm tạo siêu lớp trong phần thân của lớp dẫn xuất, đây thực sự chỉ là tạo một cá thể của lớp cha và nó không gọi trình tạo lớp cha trên vật. Cách duy nhất để triệu gọi một lớp cha hoặc một hàm tạo siêu lớp trên đối tượng lớp dẫn xuất là thông qua danh sách khởi tạo và không phải trong thân hàm khởi tạo của lớp dẫn xuất. Vì vậy, có lẽ nó không nên được gọi là một "superclass constructor gọi". Tôi đặt câu trả lời ở đây vì ai đó có thể bị lẫn lộn (như tôi đã làm).

+10

Câu trả lời này hơi khó hiểu mặc dù tôi đã đọc qua một vài lần và xem xét câu hỏi được liên kết. Tôi nghĩ rằng những gì nó nói là nếu bạn sử dụng một lời gọi rõ ràng tới một lớp cha hoặc một hàm tạo siêu lớp trong phần thân của lớp dẫn xuất, đây thực sự chỉ là tạo một cá thể của lớp cha và nó không gọi lớp cha constructor trên đối tượng dẫn xuất. Cách duy nhất để triệu gọi một lớp cha hoặc một hàm tạo siêu lớp trên đối tượng lớp dẫn xuất là thông qua danh sách khởi tạo và không phải trong thân hàm khởi tạo của lớp dẫn xuất. –

+0

@Richard Chambers Nó có thể gây nhầm lẫn vì tiếng Anh không phải là ngôn ngữ đầu tiên của tôi, nhưng bạn đã mô tả chính xác những gì tôi đã cố gắng nói. –

1

Không ai đề cập đến trình tự gọi hàm tạo khi lớp xuất phát từ nhiều lớp. Trình tự như đã đề cập trong khi phát sinh các lớp.

+2

Nếu không ai nói về nó, nó được đề cập ở đâu? – EJP

+3

@EJP vì câu hỏi là về quy tắc gọi, điều đáng nói đến là chuỗi gọi trong câu trả lời –

7

Nếu bạn có các tham số mặc định trong hàm khởi tạo cơ bản, lớp cơ sở sẽ được gọi tự động.

using namespace std; 

class Base 
{ 
    public: 
    Base(int a=1) : _a(a) {} 

    protected: 
    int _a; 
}; 

class Derived : public Base 
{ 
    public: 
    Derived() {} 

    void printit() { cout << _a << endl; } 
}; 

int main() 
{ 
    Derived d; 
    d.printit(); 
    return 0; 
} 

Output là: 1

+0

Đây chỉ là vì khai báo cụ thể đó tạo ra một 'Base()' ngầm định, có cùng nội dung là 'Base (int) 'nhưng cộng với một khởi tạo ngầm cho': _a {1} '. Đó là 'Base()' luôn được gọi nếu không có hàm tạo cơ sở cụ thể nào bị xích trong danh sách init. Và, như đã đề cập ở những nơi khác, các nhà xây dựng ủy nhiệm của C++ 11 và khởi tạo dấu ngoặc đơn hoặc bằng nhau làm cho các đối số mặc định thay vì ít cần thiết (khi chúng đã được mã hóa mùi trong nhiều ví dụ). –

Các vấn đề liên quan