Tại sao các nhà xây dựng không được thừa kế?


33

Tôi bối rối không biết vấn đề có thể là gì nếu một hàm tạo được kế thừa từ một lớp cơ sở. Cpp Primer Plus nói,

Các constructor khác với các phương thức lớp khác ở chỗ chúng tạo các đối tượng mới, trong khi các phương thức khác được gọi bởi các đối tượng hiện có . Đây là một lý do các nhà xây dựng không được thừa kế . Kế thừa có nghĩa là một đối tượng dẫn xuất có thể sử dụng một phương thức lớp cơ sở, nhưng, trong trường hợp các hàm tạo, đối tượng không tồn tại cho đến khi hàm tạo hoàn thành công việc của nó.

Tôi hiểu các constructor được gọi trước khi hoàn thành việc xây dựng đối tượng.

Làm thế nào nó có thể dẫn đến các vấn đề nếu một lớp con kế thừa ( Bằng cách kế thừa tôi có nghĩa là lớp con có thể ghi đè phương thức lớp cha, v.v. Không chỉ truy cập vào phương thức lớp cha )) hàm tạo cha mẹ?

Tôi hiểu rằng không nhất thiết phải gọi một nhà xây dựng từ bên trong một mã [không phải là tôi biết.] Ngoại trừ trong khi tạo các đối tượng. Thậm chí sau đó bạn có thể làm như vậy bằng cách sử dụng một số cơ chế để gọi bộ dẫn cha mẹ [Trong cpp, sử dụng ::hoặc sử dụng member initialiser list, Trong java bằng cách sử dụng super]. Trong Java có một thực thi để gọi nó trong dòng đầu tiên, tôi hiểu rằng để đảm bảo đối tượng cha mẹ được tạo trước và sau đó tiến hành xây dựng đối tượng con.

Nó có thể ghi đè lên nó. Nhưng, tôi không thể đưa ra những tình huống có thể gây ra vấn đề. Nếu đứa trẻ thừa kế hàm tạo cha mẹ thì điều gì có thể sai?

Vì vậy, đây chỉ là để tránh kế thừa các chức năng không cần thiết. Hoặc là có nhiều đến nó?


Không thực sự theo kịp tốc độ với C ++ 11, nhưng tôi nghĩ có một số hình thức thừa kế của nhà xây dựng trong đó.
yannis

3
Hãy nhớ rằng bất cứ điều gì bạn làm trong các nhà xây dựng, bạn phải cẩn thận trong các hàm hủy.
Manoj R

2
Tôi nghĩ bạn cần xác định ý nghĩa của việc "kế thừa một hàm tạo". Tất cả các câu trả lời dường như có các biến thể về những gì họ nghĩ "kế thừa một nhà xây dựng" nghĩa là gì. Tôi không nghĩ bất kỳ ai trong số họ phù hợp với giải thích ban đầu của tôi. Mô tả "trong hộp màu xám" từ phía trên "Kế thừa có nghĩa là một đối tượng dẫn xuất có thể sử dụng một phương thức lớp cơ sở" ngụ ý rằng các hàm tạo được kế thừa. Một đối tượng dẫn xuất chắc chắn có thể và không sử dụng các phương thức xây dựng lớp cơ sở, LUÔN.
Dunk

1
Cảm ơn bạn đã làm rõ việc kế thừa một nhà xây dựng, bây giờ bạn có thể nhận được một số câu trả lời.
Dunk

Câu trả lời:


41

Không thể có bất kỳ sự kế thừa đúng đắn nào của các hàm tạo trong C ++, bởi vì hàm tạo của lớp dẫn xuất cần thực hiện các hành động bổ sung mà hàm tạo của lớp cơ sở không phải làm và không biết. Các hành động bổ sung này là khởi tạo các thành viên dữ liệu của lớp dẫn xuất (và trong một triển khai điển hình, cũng đặt vpulum để tham chiếu đến các lớp dẫn xuất vtable).

Khi một lớp được xây dựng, có một số điều luôn luôn cần phải xảy ra: Các hàm tạo của lớp cơ sở (nếu có) và các thành viên trực tiếp cần được gọi và nếu có bất kỳ hàm ảo nào, vpulum phải được đặt chính xác . Nếu bạn không cung cấp hàm tạo cho lớp của mình, thì trình biên dịch sẽ tạo một hàm thực hiện các hành động cần thiết và không có gì khác. Nếu bạn làm cung cấp một constructor, nhưng bỏ lỡ một số các hành động cần thiết (ví dụ, khởi động của một số thành viên), sau đó trình biên dịch sẽ tự động thêm những hành động thiếu để xây dựng của bạn. Theo cách này, trình biên dịch đảm bảo rằng mỗi lớp có ít nhất một hàm tạo và mỗi hàm tạo khởi tạo đầy đủ các đối tượng mà nó tạo ra.

Trong C ++ 11, một dạng 'thừa kế hàm tạo' đã được giới thiệu trong đó bạn có thể hướng dẫn trình biên dịch tạo một tập hợp các hàm tạo cho bạn, lấy các đối số giống như các hàm tạo từ lớp cơ sở và chỉ chuyển tiếp các đối số đó đến lớp cơ sở.
Mặc dù nó được gọi chính thức là thừa kế, nhưng nó không thực sự như vậy bởi vì vẫn có một hàm cụ thể của lớp dẫn xuất. Bây giờ nó chỉ được tạo bởi trình biên dịch thay vì được viết rõ ràng bởi bạn.

Tính năng này hoạt động như thế này:

struct Base {
    Base(int a) : i(a) {}
    int i;
};

struct Derived : Base {
    Derived(int a, std::string s) : Base(a), m(s) {}

    using Base::Base; // Inherit Base's constructors.
    // Equivalent to:
    //Derived(int a) : Base(a), m() {}

    std::string m;
};

Derivedbây giờ có hai hàm tạo (không tính các hàm tạo sao chép / di chuyển). Một lấy một int và một chuỗi và một chỉ lấy một int.


Vì vậy, đây là giả sử, lớp con sẽ không có nhà xây dựng riêng của nó? và người xây dựng được kế thừa rõ ràng là không biết về các thành viên con và việc khởi tạo sẽ được thực hiện
Suvarna Pattayil

C ++ được định nghĩa theo cách mà một lớp không thể có (các) hàm tạo riêng của nó. Đó là lý do tại sao tôi không coi 'thừa kế nhà xây dựng' thực sự thừa kế. Nó chỉ là một cách để tránh viết lách tẻ nhạt.
Bart van Ingen Schenau

2
Tôi nghĩ rằng sự nhầm lẫn bắt nguồn từ thực tế là ngay cả một nhà xây dựng trống cũng làm công việc hậu trường. Vì vậy, constructor thực sự có hai phần: Công việc nội bộ và phần bạn viết. Vì chúng không được phân tách tốt nên các hàm tạo không được kế thừa.
Sarien

1
Vui lòng xem xét cho biết nơi m()đến từ đâu và làm thế nào nó sẽ thay đổi nếu loại của nó là ví dụ int.
Ded repeatator

Có thể làm điều đó một cách using Base::Basengấm ngầm? Nó sẽ có hậu quả lớn nếu tôi quên dòng đó trên một lớp dẫn xuất và tôi cần kế thừa hàm tạo trong tất cả các lớp dẫn xuất
Post Self

7

Không rõ ý của bạn là gì khi "kế thừa hàm tạo cha mẹ". Bạn sử dụng từ ghi đè , điều này cho thấy bạn có thể đang nghĩ về các hàm tạo hoạt động giống như các hàm ảo đa hình . Tôi cố tình không sử dụng thuật ngữ "các nhà xây dựng ảo" vì đó là tên chung cho một mẫu mã trong đó bạn thực sự yêu cầu một thể hiện đã có của một đối tượng để tạo một đối tượng khác.

Có rất ít tiện ích cho các hàm tạo đa hình bên ngoài mẫu "hàm tạo ảo" và khó có thể đưa ra một kịch bản cụ thể trong đó một hàm tạo đa hình thực tế có thể được sử dụng. Một ví dụ rất dễ hiểu là C ++ không có giá trị từ xa :

struct Base {
  virtual Base(unsigned p1, unsigned p2) {...}
};

struct Derived: public Base {
  Derived(unsigned p1, unsigned p2) : Base(p1, p2) override {...}
};

int main(void) {
  unsigned p1 = 0;
  unsigned p2 = 42;
  Derived *p_d1 = new Base(p1, p2); // This might call Derived(unsigned, unsigned).
  Derived *p_d2 = nullptr;
  p_d2 = new Base(p1, p2); // This might call Derived(unsigned, unsigned) too.
}

Trong trường hợp này, hàm tạo được gọi phụ thuộc vào loại bê tông của biến được xây dựng hoặc được gán. Rất phức tạp để phát hiện trong quá trình phân tích cú pháp / mã và không có tiện ích: bạn biết loại cụ thể mà bạn đang xây dựng và bạn đã viết một hàm tạo cụ thể cho lớp dẫn xuất. Mã C ++ hợp lệ sau đây thực hiện chính xác như vậy, ngắn hơn một chút và rõ ràng hơn trong những gì nó làm:

struct Base {
  Base(unsigned p1, unsigned p2) {...}
};

struct Derived: public Base {
  Derived(unsigned p1, unsigned p2) : Base(p1, p2) {...}
};

int main(void) {
  unsigned p1 = 0;
  unsigned p2 = 42;
  Derived *p_d1 = new Derived(p1, p2); 
  Derived *p_d2 = nullptr;
  p_d2 = new Derived(p1, p2);
}


Một cách hiểu thứ hai hoặc có lẽ là câu hỏi bổ sung là - điều gì xảy ra nếu các hàm tạo của lớp Cơ sở tự động hiện diện trong tất cả các lớp dẫn xuất trừ khi được ẩn rõ ràng.

Nếu đứa trẻ thừa kế hàm tạo cha mẹ thì điều gì có thể sai?

Bạn sẽ phải viết mã bổ sung để ẩn một hàm tạo cha mẹ không chính xác để sử dụng trong việc xây dựng lớp dẫn xuất. Điều này có thể xảy ra khi lớp dẫn xuất chuyên về lớp cơ sở theo cách mà các tham số nhất định trở nên không liên quan.

Ví dụ điển hình là hình chữ nhật và hình vuông (Lưu ý rằng hình vuông và hình chữ nhật nói chung không phải là Liskov thay thế nên nó không phải là một thiết kế tốt, nhưng nó làm nổi bật vấn đề).

struct Rectangle {
  Rectangle(unsigned width, unsigned height) {...}
};

struct Square : public Rectangle {
  explicit Square(unsigned side) : Rectangle(side, side) {...}
};

Nếu Square thừa hưởng hàm tạo hai giá trị của Hình chữ nhật, bạn có thể xây dựng các hình vuông có chiều cao và chiều rộng khác nhau ... Điều đó sai về mặt logic, vì vậy bạn muốn ẩn hàm tạo đó.


3

Tại sao các hàm tạo không được kế thừa: câu trả lời đơn giản đến mức đáng ngạc nhiên: Hàm tạo của lớp cơ sở "xây dựng" lớp cơ sở và hàm tạo của lớp kế thừa "xây dựng" lớp được kế thừa. Nếu lớp kế thừa sẽ kế thừa trình xây dựng, hàm tạo sẽ cố gắng xây dựng một đối tượng của lớp cơ sở kiểu và bạn sẽ không thể "xây dựng" một đối tượng của lớp kế thừa.

Những loại đánh bại mục đích kế thừa một lớp.


3

Vấn đề rõ ràng nhất với việc cho phép lớp dẫn xuất ghi đè lên hàm tạo của lớp cơ sở là nhà phát triển của lớp dẫn xuất hiện có trách nhiệm biết cách xây dựng (các) lớp cơ sở của nó. Điều gì xảy ra khi lớp dẫn xuất không xây dựng lớp cơ sở đúng cách?

Ngoài ra, nguyên tắc Thay thế Liskov sẽ không còn được áp dụng vì bạn không còn có thể tin tưởng vào bộ sưu tập các đối tượng của lớp cơ sở để tương thích với nhau, bởi vì không có gì đảm bảo rằng lớp cơ sở được xây dựng đúng hoặc tương thích với các loại dẫn xuất khác.

Nó phức tạp hơn nữa khi có nhiều hơn 1 cấp thừa kế được thêm vào. Bây giờ lớp dẫn xuất của bạn cần biết cách xây dựng tất cả các lớp cơ sở lên chuỗi.

Sau đó, điều gì xảy ra nếu bạn thêm một lớp cơ sở mới vào đầu phân cấp thừa kế? Bạn sẽ phải cập nhật tất cả các hàm tạo của lớp dẫn xuất.


2

Các nhà xây dựng khác về cơ bản so với các phương pháp khác:

  1. Chúng được tạo ra nếu bạn không viết chúng.
  2. Tất cả các hàm tạo của lớp cơ sở được gọi ngầm ngay cả khi bạn không thực hiện thủ công
  3. Bạn không gọi chúng một cách rõ ràng mà bằng cách tạo các đối tượng.

Vậy tại sao chúng không được thừa kế? Câu trả lời đơn giản: Bởi vì luôn có một ghi đè, được tạo hoặc viết bằng tay.

Tại sao mỗi lớp cần một nhà xây dựng? Đó là một câu hỏi phức tạp và tôi tin rằng câu trả lời phụ thuộc vào trình biên dịch. Có một thứ như là một hàm tạo "tầm thường" mà trình biên dịch không bắt buộc rằng nó sẽ được gọi là dường như. Tôi nghĩ đó là điều gần gũi nhất với ý nghĩa của bạn khi thừa kế nhưng vì ba lý do đã nêu ở trên tôi nghĩ rằng so sánh các hàm tạo với các phương thức bình thường không thực sự hữu ích. :)


1

Mỗi lớp cần một hàm tạo, ngay cả các giá trị mặc định.
C ++ sẽ tạo các hàm tạo mặc định cho bạn trừ khi bạn tạo một hàm tạo chuyên dụng.
Trong trường hợp lớp cơ sở của bạn sử dụng một hàm tạo chuyên dụng, bạn sẽ cần phải viết hàm tạo chuyên biệt trên lớp dẫn xuất ngay cả khi cả hai đều giống nhau và xâu chuỗi chúng lại.
C ++ 11 cho phép bạn tránh sao chép mã trên các hàm tạo bằng cách sử dụng :

Class A : public B {
using B:B;
....
  1. Một tập hợp các hàm tạo kế thừa bao gồm

    • Tất cả các hàm tạo không phải của lớp cơ sở (sau khi bỏ qua các tham số dấu chấm lửng, nếu có) (kể từ C ++ 14)
    • Đối với mỗi hàm tạo với các đối số mặc định hoặc tham số dấu chấm lửng, tất cả các chữ ký của hàm tạo được hình thành bằng cách bỏ dấu chấm lửng và bỏ qua các đối số mặc định từ đầu của đối số liệt kê từng cái một
    • Tất cả các mẫu hàm tạo của lớp cơ sở (sau khi bỏ qua các tham số dấu chấm lửng, nếu có) (kể từ C ++ 14)
    • Đối với mỗi mẫu của hàm tạo với các đối số mặc định hoặc dấu chấm lửng, tất cả các chữ ký của hàm tạo được hình thành bằng cách bỏ dấu chấm lửng và bỏ qua các đối số mặc định từ đầu của đối số liệt kê từng cái một
  2. Tất cả các hàm tạo được kế thừa không phải là hàm tạo mặc định hoặc hàm tạo sao chép / di chuyển và có chữ ký không khớp với các hàm tạo do người dùng định nghĩa trong lớp dẫn xuất, được khai báo ngầm trong lớp dẫn xuất. Các tham số mặc định không được kế thừa


0

Bạn có thể dùng:

MyClass() : Base()

Bạn đang hỏi tại sao bạn phải làm điều này?

Lớp con có thể có các thuộc tính bổ sung có thể cần được khởi tạo trong hàm tạo hoặc nó có thể khởi tạo các biến của lớp cơ sở theo một cách khác.

Làm thế nào khác bạn sẽ tạo đối tượng loại phụ?


Cảm ơn. Trên thực tế, tôi đang cố gắng tìm hiểu tại sao một hàm tạo không được kế thừa trong lớp con, giống như các phương thức lớp cha khác.
Suvarna Pattayil
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.