Tại sao hàm tạo không tham số mặc định biến mất khi bạn tạo một tham số có tham số


161

Trong C #, C ++ và Java, khi bạn tạo một hàm tạo lấy tham số, thì tham số mặc định sẽ biến mất. Tôi đã luôn luôn chấp nhận sự thật này, nhưng bây giờ tôi đã bắt đầu tự hỏi tại sao.

Lý do cho hành vi này là gì? Có phải đó chỉ là một "biện pháp / dự đoán an toàn" nói rằng "Nếu bạn đã tạo một công cụ xây dựng của riêng mình, có lẽ bạn không muốn điều này tiềm ẩn trong đó"? Hoặc nó có một lý do kỹ thuật khiến trình biên dịch không thể thêm một khi bạn đã tự tạo một hàm tạo?


7
Bạn có thể thêm C ++ vào danh sách các ngôn ngữ có hành vi này.
Henk Holterman

18
Và trong C ++, bây giờ bạn có thể nói Foo() = default;để lấy lại mặc định.
MSalters

1
Nếu hàm tạo của bạn với các tham số có thể có các đối số mặc định cho tất cả các tham số của nó, thì nó sẽ xung đột với hàm tạo không tham số tích hợp, do đó cần phải xóa nó khi bạn tạo riêng cho mình.
Morwenn

3
Hãy tưởng tượng có mặt cho cuộc tranh luận này giữa những người sáng lập trình biên dịch đầu tiên để đưa ra yêu cầu xây dựng mặc định và cuộc thảo luận sôi nổi mà nó sẽ truyền cảm hứng.
kingdango

7
@HenkHolterman C ++ không chỉ khác với hành vi này, mà là người khởi tạo, và nó cho phép khả năng tương thích C, như thảo luận của Stroustrup trong Thiết kế và tiến hóa của C ++ và như được tóm tắt trong câu trả lời cập nhật của tôi.
Jon Hanna

Câu trả lời:


219

Không có lý do gì mà trình biên dịch không thể thêm hàm tạo nếu bạn đã thêm trình biên dịch của riêng mình - trình biên dịch có thể làm bất cứ điều gì nó muốn! Tuy nhiên, bạn phải xem xét điều gì có ý nghĩa nhất:

  • Nếu tôi chưa định nghĩa bất kỳ hàm tạo nào cho một lớp không tĩnh, rất có thể tôi muốn có thể khởi tạo lớp đó. Để cho phép điều đó, trình biên dịch phải thêm một hàm tạo không tham số, sẽ không có tác dụng gì ngoài việc cho phép khởi tạo. Điều này có nghĩa là tôi không phải bao gồm một hàm tạo trống trong mã của mình chỉ để làm cho nó hoạt động.
  • Nếu tôi đã định nghĩa một hàm tạo của riêng tôi, đặc biệt là một tham số có tham số, thì rất có thể tôi có logic của riêng tôi phải được thực thi khi tạo lớp. Nếu trình biên dịch tạo ra một hàm tạo rỗng, không tham số trong trường hợp này, nó sẽ cho phép ai đó bỏ qua logic mà tôi đã viết, điều này có thể dẫn đến việc mã của tôi bị phá vỡ theo mọi cách. Nếu tôi muốn một hàm tạo rỗng mặc định trong trường hợp này, tôi cần nói rõ ràng như vậy.

Vì vậy, trong mỗi trường hợp, bạn có thể thấy rằng hành vi của các trình biên dịch hiện tại có ý nghĩa nhất về mặt bảo tồn ý định có thể có của mã.


2
Tôi nghĩ rằng phần còn lại của câu trả lời của bạn khá nhiều chứng minh câu đầu tiên của bạn sai.
Konrad Rudolph

76
@KonradRudolph, câu đầu tiên nói rằng trình biên dịch có thể thêm một hàm tạo trong kịch bản này - phần còn lại của câu trả lời giải thích tại sao nó không (đối với các ngôn ngữ được chỉ định trong câu hỏi)
JohnL

1
Ồ không. Nếu chúng tôi thiết kế một ngôn ngữ OO từ đầu, ý nghĩa rõ ràng nhất của việc không có hàm tạo là "bạn đã bỏ qua việc thêm một hàm tạo để đảm bảo lớp bất biến" và nó sẽ gây ra lỗi biên dịch.
Jon Hanna

70

Chắc chắn không có lý do tại sao kỹ thuật ngôn ngữ phải được thiết kế theo cách này.

Có bốn tùy chọn hơi thực tế mà tôi có thể thấy:

  1. Không có nhà xây dựng mặc định nào cả
  2. Kịch bản hiện tại
  3. Luôn cung cấp một hàm tạo mặc định theo mặc định, nhưng cho phép nó bị triệt tiêu rõ ràng
  4. Luôn cung cấp một hàm tạo mặc định mà không cho phép nó bị chặn

Tùy chọn 1 có phần hấp dẫn, trong đó tôi càng viết mã thì tôi càng ít thực sự muốn một hàm tạo không tham số. Một ngày nào đó tôi nên tính tần suất tôi thực sự kết thúc bằng cách sử dụng một hàm tạo mặc định ...

Lựa chọn 2 tôi ổn với.

Tùy chọn 3 đi ngược lại dòng chảy của cả Java và C #, cho phần còn lại của ngôn ngữ. Không bao giờ có bất cứ điều gì mà bạn "loại bỏ" một cách rõ ràng, trừ khi bạn tính rõ ràng làm cho mọi thứ trở nên riêng tư hơn so với mặc định trong Java.

Tùy chọn 4 là khủng khiếp - bạn hoàn toàn muốn có thể buộc xây dựng với các tham số nhất định. Điều đó new FileStream()có nghĩa là gì?

Về cơ bản, nếu bạn chấp nhận tiền đề cung cấp một hàm tạo mặc định có ý nghĩa gì cả, tôi tin rằng sẽ rất có ý nghĩa để loại bỏ nó ngay khi bạn cung cấp hàm tạo của riêng bạn.


1
Tôi thích tùy chọn 3, vì khi tôi viết một cái gì đó, tôi cần thường xuyên hơn để có cả hai loại hàm tạo sau đó chỉ là hàm tạo với tham số. Vì vậy, tôi muốn một lần một ngày làm cho một số hàm tạo không tham số riêng tư sau đó viết 10 lần một ngày các hàm tạo không tham số. Nhưng đó có lẽ chỉ là tôi, tôi đang viết rất nhiều lớp có thể hiểu được ...
Petr Mensik

8
@PetrMensik: Nếu nhà xây dựng không tham số của bạn thực sự không cần phải làm gì hoàn toàn, thì đó là một lớp lót chắc chắn sẽ không mất nhiều mã hơn so với câu lệnh "loại bỏ rõ ràng".
Jon Skeet

2
@PetrMensik: Xin lỗi, lỗi của tôi, vâng. Điều đó hoàn toàn trái ngược với trải nghiệm của tôi - và tôi cho rằng bao gồm một thứ tự động cũng là lựa chọn nguy hiểm hơn ... nếu bạn vô tình kết thúc không loại trừ nó, bạn có thể làm hỏng bất biến của mình, v.v.
Jon Skeet

2
# 4 là trường hợp với C # structs, tốt hơn hoặc xấu hơn.
Jay Bazuzi

4
Tôi thích tùy chọn 1 vì nó dễ hiểu hơn. Không có hàm tạo "ma thuật" nào mà bạn không thể thấy trong mã. Với tùy chọn 1, trình biên dịch sẽ phát ra một cảnh báo khi (hoặc thậm chí không cho phép?) Một lớp không tĩnh không có các hàm tạo cá thể. Làm thế nào về: "Không tìm thấy các hàm tạo cá thể cho lớp <TYPE>. Ý của bạn là khai báo lớp tĩnh?"
Jeppe Stig Nielsen

19

Biên tập. Trên thực tế, trong khi những gì tôi nói trong câu trả lời đầu tiên của tôi là hợp lệ, thì đây là lý do thực sự:

Lúc đầu, C. C không hướng đối tượng (bạn có thể thực hiện phương pháp OO, nhưng nó không giúp bạn hoặc thực thi bất cứ điều gì).

Sau đó, có C With Classes, sau đó được đổi tên thành C ++. C ++ là hướng đối tượng và do đó khuyến khích đóng gói và đảm bảo bất biến của đối tượng - khi xây dựng và ở đầu và cuối của bất kỳ phương thức nào, đối tượng ở trạng thái hợp lệ.

Điều tự nhiên phải làm với điều này là bắt buộc một lớp phải luôn có một hàm tạo để đảm bảo nó bắt đầu ở trạng thái hợp lệ - nếu hàm tạo không phải làm gì để đảm bảo điều này, thì hàm tạo rỗng sẽ ghi lại sự thật này .

Nhưng một mục tiêu với C ++ là tương thích với C đến mức có thể, tất cả các chương trình C hợp lệ cũng là các chương trình C ++ hợp lệ (không còn là mục tiêu hoạt động và sự phát triển của C tách biệt với C ++ có nghĩa là nó không còn giữ ).

Một ảnh hưởng của điều này là sự trùng lặp về chức năng giữa structclass. Cái trước làm mọi thứ theo cách C (mọi thứ theo mặc định) và cái sau làm mọi thứ theo cách OO tốt (mọi thứ theo mặc định, nhà phát triển chủ động công khai những gì họ muốn công khai).

Một điều nữa là để một C struct, không thể có hàm tạo vì C không có hàm tạo, có giá trị trong C ++, thì phải có ý nghĩa đối với cách nhìn của C ++. Và vì vậy, trong khi không có hàm tạo sẽ đi ngược lại thực tiễn OO là chủ động đảm bảo bất biến, C ++ đã hiểu điều này có nghĩa là có một hàm tạo không tham số mặc định hoạt động giống như nó có phần thân trống.

Tất cả C structsbây giờ là C ++ hợp lệ structs, (có nghĩa là chúng giống với C ++ classesvới mọi thứ - thành viên và thừa kế - công khai) được xử lý từ bên ngoài như thể nó có một hàm tạo không tham số.

Tuy nhiên, nếu bạn đã đặt một hàm tạo vào một classhoặc struct, thì bạn đang thực hiện mọi thứ theo cách C ++ / OO thay vì cách C và không cần một hàm tạo mặc định.

Vì nó được dùng như một cách viết tắt, mọi người vẫn tiếp tục sử dụng nó ngay cả khi khả năng tương thích là không thể (nó sử dụng các tính năng C ++ khác không có trong C).

Do đó, khi Java xuất hiện (dựa trên C ++ theo nhiều cách) và sau đó là C # (dựa trên C ++ và Java theo các cách khác nhau), họ đã giữ cách tiếp cận này như một thứ mà các lập trình viên có thể đã sử dụng.

Stroustrup viết về điều này trong Ngôn ngữ lập trình C ++ của mình và thậm chí còn hơn thế, tập trung nhiều hơn vào "các hệ thống" của ngôn ngữ trong Thiết kế và tiến hóa của C ++ .

=== Câu trả lời gốc ===

Hãy nói điều này đã không xảy ra.

Giả sử tôi không muốn một nhà xây dựng không tham số, bởi vì tôi không thể đưa lớp của mình vào trạng thái có ý nghĩa mà không có ai. Thật vậy, đây là điều có thể xảy ra với structC # (nhưng nếu bạn không thể sử dụng một cách có ý nghĩa các số không và không structtrong C # thì tốt nhất bạn nên sử dụng tối ưu hóa không hiển thị công khai và nếu không thì có lỗ hổng thiết kế trong việc sử dụng struct).

Để làm cho lớp của tôi có thể bảo vệ sự bất biến của nó, tôi cần một removeDefaultConstructortừ khóa đặc biệt . Ít nhất, tôi cần tạo một hàm tạo không tham số riêng để đảm bảo không có mã gọi nào gọi mặc định.

Mà làm phức tạp thêm một số ngôn ngữ. Tốt hơn không nên làm điều đó.

Nói chung, tốt nhất không nên nghĩ đến việc thêm một hàm tạo như loại bỏ mặc định, tốt hơn hết là nghĩ rằng không có hàm tạo nào là đường cú pháp để thêm một hàm tạo không tham số mà không làm gì cả.


1
Và một lần nữa, tôi thấy ai đó cảm thấy đây là một câu trả lời đủ tệ để bỏ phiếu, nhưng không thể làm phiền tôi hoặc bất kỳ ai khác. Mà có thể, bạn biết đấy, hữu ích.
Jon Hanna

Tương tự với câu trả lời của tôi. Chà, tôi không thấy có gì sai ở đây nên +1 từ tôi.
Botz3000

@ Botz3000 Tôi không quan tâm đến điểm số, nhưng nếu họ bị chỉ trích, tôi thà đọc nó. Tuy nhiên, nó đã khiến tôi nghĩ về một cái gì đó để thêm vào ở trên.
Jon Hanna

1
Và một lần nữa với việc bỏ phiếu mà không có bất kỳ lời giải thích. Xin vui lòng, nếu tôi thiếu một cái gì đó quá rõ ràng đến nỗi nó không yêu cầu giải thích, chỉ cần cho rằng tôi ngu ngốc và giúp tôi giải thích nó bằng mọi cách.
Jon Hanna

1
@jogojapan Tôi cũng không phải là chuyên gia, nhưng anh chàng - là người đưa ra quyết định - đã viết về nó, vì vậy tôi không cần phải như vậy. Ngẫu nhiên, đó là một cuốn sách rất thú vị; ít về công nghệ cấp thấp và rất nhiều quyết định thiết kế, với những điều thú vị như bài phát biểu của một người nói rằng bạn nên hiến thận trước khi đề xuất một tính năng mới (bạn sẽ suy nghĩ rất kỹ và chỉ làm điều đó hai lần) ngay trước bài phát biểu của mình giới thiệu cả hai mẫu và ngoại lệ.
Jon Hanna

13

Trình xây dựng không tham số mặc định được thêm vào nếu bạn không tự mình làm bất cứ điều gì để kiểm soát việc tạo đối tượng. Khi bạn đã tạo một hàm tạo duy nhất để kiểm soát, trình biên dịch sẽ "sao lưu" và cho phép bạn có toàn quyền kiểm soát.

Nếu nó không theo cách này, bạn sẽ cần một số cách rõ ràng để vô hiệu hóa hàm tạo mặc định nếu bạn chỉ muốn các đối tượng có thể xây dựng được thông qua một hàm tạo với các tham số.


Bạn thực sự có lựa chọn đó. Làm cho các constructor tham số riêng tư.
mw_21

5
Điều đó không giống nhau. Bất kỳ phương thức nào của lớp, bao gồm các phương thức tĩnh, có thể gọi nó. Tôi thích có nó hoàn toàn không tồn tại.
Anders Abel

3

Đây là một chức năng tiện lợi của trình biên dịch. Nếu bạn xác định Trình xây dựng có tham số nhưng không xác định hàm tạo không tham số, khả năng bạn không muốn cho phép hàm tạo không tham số cao hơn nhiều.

Đây là trường hợp đối với nhiều đối tượng không có ý nghĩa để khởi tạo với một hàm tạo trống.

Nếu không, bạn phải khai báo một hàm tạo không tham số riêng cho mỗi lớp mà bạn muốn hạn chế.

Theo tôi, phong cách không tốt khi cho phép một hàm tạo không tham số cho một lớp cần các tham số để hoạt động.


3

Tôi nghĩ rằng câu hỏi nên có cách khác: Tại sao bạn không cần khai báo một hàm tạo mặc định nếu bạn chưa xác định bất kỳ hàm tạo nào khác?

Một constructor là bắt buộc cho các lớp không tĩnh.
Vì vậy, tôi nghĩ rằng nếu bạn chưa xác định bất kỳ hàm tạo nào, hàm tạo mặc định được tạo chỉ là một tính năng tiện lợi của trình biên dịch C #, thì lớp của bạn sẽ không hợp lệ nếu không có hàm tạo. Vì vậy, không có gì sai với việc tạo ra một hàm tạo mà không làm gì cả. Nó chắc chắn trông sạch sẽ hơn là có các nhà xây dựng trống xung quanh.

Nếu bạn đã định nghĩa một hàm tạo, lớp của bạn là hợp lệ, vậy tại sao trình biên dịch lại cho rằng bạn muốn một hàm tạo mặc định? Nếu bạn không muốn thì sao? Thực hiện một thuộc tính để báo cho trình biên dịch không tạo ra hàm tạo mặc định đó? Tôi không nghĩ rằng đó sẽ là một ý tưởng tốt.


1

Hàm xây dựng mặc định chỉ có thể được xây dựng khi lớp không có hàm tạo. Trình biên dịch được viết theo cách chỉ cung cấp điều này như một cơ chế sao lưu.

Nếu bạn có một hàm tạo được tham số hóa, bạn có thể không muốn tạo một đối tượng bằng cách sử dụng hàm tạo mặc định. Nếu trình biên dịch cung cấp một hàm tạo mặc định, bạn sẽ phải viết một hàm tạo không có đối số và đặt nó ở chế độ riêng tư để ngăn các đối tượng được tạo mà không sử dụng đối số.

Ngoài ra, sẽ có nhiều khả năng bạn quên việc vô hiệu hóa hoặc 'tư nhân hóa' trình xây dựng mặc định và do đó gây ra lỗi chức năng tiềm ẩn khó có thể bắt được.

Và bây giờ bạn phải xác định rõ ràng một hàm tạo không có đối số nếu bạn muốn một đối tượng được tạo theo cách mặc định hoặc bằng cách truyền tham số. Điều này được kiểm tra mạnh mẽ, và trình biên dịch phàn nàn khác, do đó đảm bảo không có lỗ hổng ở đây.


1

Tiền đề

Hành vi này có thể được xem như là một phần mở rộng tự nhiên của quyết định cho các lớp có một hàm tạo không tham số công khai mặc định . Dựa trên câu hỏi đã được hỏi, chúng tôi lấy quyết định này làm tiền đề và cho rằng chúng tôi không đặt câu hỏi trong trường hợp này.

Các cách để loại bỏ Trình xây dựng mặc định

Theo sau đó phải có một cách để loại bỏ hàm tạo không tham số công khai mặc định. Việc loại bỏ này có thể được thực hiện theo các cách sau:

  1. Khai báo một hàm tạo không tham số không công khai
  2. Tự động loại bỏ hàm tạo không tham số khi hàm tạo có tham số được khai báo
  3. Một số từ khóa / thuộc tính để chỉ ra cho trình biên dịch loại bỏ hàm tạo không tham số (đủ lúng túng để dễ loại trừ)

Chọn giải pháp tốt nhất

Bây giờ chúng tôi tự hỏi: Nếu không có hàm tạo không tham số, nó phải được thay thế bằng gì? Theo loại kịch bản nào chúng ta muốn loại bỏ hàm tạo không tham số công khai mặc định?

Mọi thứ bắt đầu rơi vào vị trí. Đầu tiên, nó phải được thay thế bằng một hàm tạo với các tham số hoặc bằng một hàm tạo không công khai. Thứ hai, các kịch bản mà bạn không muốn một hàm tạo không tham số là:

  1. Chúng tôi không muốn lớp được khởi tạo hoàn toàn hoặc chúng tôi muốn kiểm soát mức độ hiển thị của hàm tạo: khai báo hàm tạo không công khai
  2. Chúng tôi muốn buộc các tham số được cung cấp khi xây dựng: khai báo hàm tạo với các tham số

Phần kết luận

Ở đó chúng ta có nó - chính xác là hai cách mà C #, C ++ và Java cho phép loại bỏ hàm tạo không tham số công khai mặc định.


Cấu trúc rõ ràng và dễ theo dõi, +1. Nhưng liên quan đến (3.) ở trên: Tôi không nghĩ rằng một từ khóa đặc biệt để loại bỏ hàm tạo là một ý tưởng khó xử như vậy và trên thực tế, C ++ 11 đã giới thiệu = deletecho mục đích này.
Nhật Bản

1

Tôi nghĩ rằng điều này được xử lý bởi trình biên dịch. Nếu bạn mở cụm .nettrong, ILDASMbạn sẽ thấy hàm tạo mặc định, ngay cả khi nó không nằm trong mã. Nếu bạn xác định một hàm tạo được tham số hóa thì hàm tạo mặc định sẽ không được nhìn thấy.

Thực tế khi bạn định nghĩa lớp (không tĩnh), trình biên dịch cung cấp tính năng này với suy nghĩ rằng bạn sẽ chỉ tạo một thể hiện. Và nếu bạn muốn bất kỳ hoạt động cụ thể nào thực hiện, bạn chắc chắn sẽ có nhà xây dựng của riêng bạn.


0

Đó là bởi vì khi bạn không định nghĩa một hàm tạo, trình biên dịch sẽ tự động tạo ra một hàm tạo cho bạn mà không lấy bất kỳ đối số nào. Khi bạn muốn một cái gì đó nhiều hơn từ một nhà xây dựng, bạn vượt quá nó. Đây không phải là quá tải chức năng. Vì vậy, hàm tạo duy nhất mà trình biên dịch nhìn thấy bây giờ là hàm tạo của bạn có một đối số. Để khắc phục vấn đề này, bạn có thể chuyển một giá trị mặc định nếu hàm tạo được truyền không có giá trị.


0

Một lớp cần một người xây dựng. Đây là yêu cầu bắt buộc.

  • Nếu bạn không tạo một, hàm tạo không tham số sẽ tự động được cung cấp cho bạn.
  • Nếu bạn không muốn một hàm tạo không tham số, thì bạn cần phải tạo một cái riêng.
  • Nếu bạn cần cả hai, hàm tạo không tham số và hàm tạo dựa trên tham số, bạn có thể thêm chúng theo cách thủ công.

Tôi sẽ trả lời câu hỏi của bạn với người khác, tại sao chúng ta luôn muốn một hàm tạo không tham số mặc định? có những trường hợp không mong muốn, vì vậy nhà phát triển có quyền kiểm soát để thêm hoặc xóa nó khi cần.

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.