Tôi không hiểu các đối số chống lại quá tải toán tử [đã đóng]


82

Tôi vừa đọc một trong những bài báo của Joel trong đó anh ấy nói:

Nói chung, tôi phải thừa nhận rằng tôi hơi sợ các tính năng ngôn ngữ che giấu mọi thứ . Khi bạn nhìn thấy mã

i = j * 5;

Ít nhất là trong C bạn biết rằng j đang được nhân với năm và kết quả được lưu trữ trong i.

Nhưng nếu bạn thấy cùng một đoạn mã trong C ++, bạn không biết gì cả. Không có gì. Cách duy nhất để biết những gì thực sự xảy ra trong C ++ là tìm ra loại i và j là gì, thứ gì đó có thể được khai báo ở đâu đó hoàn toàn khác. Đó là bởi vì j có thể thuộc loại đã operator*quá tải và nó làm điều gì đó cực kỳ dí dỏm khi bạn cố gắng nhân nó.

(Nhấn mạnh của tôi.) Sợ các tính năng ngôn ngữ che giấu mọi thứ? Làm thế nào bạn có thể sợ điều đó? Không che giấu những thứ (còn được gọi là trừu tượng ) một trong những ý tưởng chính của lập trình hướng đối tượng? Mỗi khi bạn gọi một phương thức a.foo(b), bạn không biết mình có thể làm gì. Bạn phải tìm ra loại avà loại b, một cái gì đó có thể được khai báo ở đâu đó hoàn toàn khác. Vì vậy, chúng ta có nên loại bỏ lập trình hướng đối tượng, bởi vì nó che giấu quá nhiều thứ từ lập trình viên?

Và làm thế j * 5nào khác với j.multiply(5), mà bạn có thể phải viết bằng ngôn ngữ không hỗ trợ quá tải toán tử? Một lần nữa, bạn sẽ phải tìm ra kiểu jvà nhìn trộm bên trong multiplyphương thức, bởi vì lo và kìa, jcó thể là một loại có một multiplyphương thức làm một cái gì đó rất dí dỏm.

"Muahaha, tôi là một lập trình viên độc ác đặt tên cho một phương thức multiply, nhưng những gì nó thực sự làm là hoàn toàn tối nghĩa và không trực quan và hoàn toàn không có gì để làm với nhiều thứ." Đó có phải là một kịch bản chúng ta phải xem xét khi thiết kế một ngôn ngữ lập trình? Sau đó, chúng ta phải từ bỏ định danh từ các ngôn ngữ lập trình với lý do chúng có thể gây hiểu nhầm!

Nếu bạn muốn biết phương thức làm gì, bạn có thể xem lướt qua tài liệu hoặc xem lén trong quá trình thực hiện. Quá tải toán tử chỉ là đường cú pháp và tôi không thấy nó thay đổi trò chơi như thế nào.

Vui lòng làm sáng tỏ cho tôi.


20
+1: Được viết tốt, được tranh luận tốt, chủ đề thú vị và gây tranh cãi cao. Một ví dụ sáng ngời của một câu hỏi p.se.
Allon Guralnek

19
+1: Mọi người nghe Joel Spolsky vì anh ấy viết tốt và nổi tiếng. Nhưng điều đó không làm cho anh ta đúng 100% thời gian. Tôi đồng ý với lập luận của bạn. Nếu tất cả chúng ta tuân theo logic của Joel ở đây, chúng ta sẽ không bao giờ đi đến đâu.
Không ai

5
Tôi tranh luận rằng tôi và j được khai báo cục bộ để bạn có thể thấy loại của họ một cách nhanh chóng, hoặc họ là những tên biến sucky và nên được đổi tên một cách thích hợp.
Cameron MacFarland

5
+1, nhưng đừng quên phần hay nhất trong bài viết của Joel: sau khi chạy marathon hướng tới câu trả lời đúng, anh ta không có lý do rõ ràng dừng lại 50 feet. Mã sai không nên nhìn sai; nó không nên biên dịch.
Larry Coleman

3
....
David Thornley

Câu trả lời:


32

Trừu tượng 'ẩn mã' để bạn không phải lo lắng về hoạt động bên trong và thường vì vậy bạn không thể thay đổi chúng, nhưng ý định không phải là để ngăn bạn nhìn vào nó. Chúng tôi chỉ đưa ra các giả định về các nhà khai thác và như Joel nói, nó có thể ở bất cứ đâu. Có một tính năng lập trình yêu cầu tất cả các toán tử quá tải được thiết lập ở một vị trí cụ thể có thể giúp tìm thấy nó, nhưng tôi không chắc nó làm cho việc sử dụng nó dễ dàng hơn.

Tôi không thấy làm * làm một cái gì đó không giống với phép nhân bất kỳ tốt hơn một hàm gọi là Get_Some_Data xóa dữ liệu.


13
+1 Đối với bit 'Tôi không thấy'. Các tính năng ngôn ngữ là có để sử dụng, không lạm dụng.
Michael K

5
Tuy nhiên, chúng ta có một <<toán tử được xác định trên các luồng không liên quan gì đến sự dịch chuyển bitwise, ngay trong thư viện chuẩn của C ++.
Malcolm

toán tử 'bitwise shift' chỉ được gọi là vì lý do lịch sử. Khi được áp dụng cho các loại tiêu chuẩn, nó thực hiện dịch chuyển theo bit (giống như cách toán tử + cộng các số lại với nhau khi áp dụng cho các loại số), tuy nhiên khi áp dụng cho một loại phức tạp, nó có thể làm những gì nó thích, miễn là nó tạo ra ý nghĩa cho loại đó.
gbjbaanb

1
* cũng được sử dụng cho hội thảo (được thực hiện bởi các con trỏ và trình lặp thông minh); Không rõ nơi nào để đặt ranh giới giữa quá tải tốt và xấu
martinkunev

Nó sẽ không ở bất cứ đâu, nó sẽ là định nghĩa của j.
Andy

19

IMHO, các tính năng ngôn ngữ như quá tải toán tử cung cấp cho lập trình viên nhiều sức mạnh hơn. Và, như chúng ta đều biết, với sức mạnh to lớn sẽ có trách nhiệm lớn. Các tính năng cung cấp cho bạn nhiều sức mạnh hơn cũng cung cấp cho bạn nhiều cách hơn để tự bắn vào chân mình, và rõ ràng, nên được sử dụng một cách thận trọng.

Ví dụ, nó có ý nghĩa hoàn hảo để quá tải +hoặc *toán tử cho class Matrixhoặc class Complex. Mọi người sẽ ngay lập tức biết ý nghĩa của nó. Mặt khác, với tôi thực tế điều đó +có nghĩa là việc nối các chuỗi không hoàn toàn rõ ràng, mặc dù Java thực hiện điều này như một phần của ngôn ngữ và STL thực hiện để std::stringsử dụng quá tải toán tử.

Một ví dụ điển hình khác khi quá tải toán tử làm cho mã rõ ràng hơn là con trỏ thông minh trong C ++. Bạn muốn các con trỏ thông minh hoạt động giống như các con trỏ thông thường càng nhiều càng tốt, vì vậy nó có ý nghĩa hoàn hảo để làm quá tải các unary *->toán tử.

Về bản chất, quá tải toán tử không gì khác hơn là một cách khác để đặt tên cho hàm. Và có một quy tắc để đặt tên hàm: tên phải được mô tả, làm cho nó rõ ràng ngay lập tức những gì hàm làm. Quy tắc chính xác tương tự áp dụng cho quá tải toán tử.


1
Hai câu cuối cùng của bạn đi vào trung tâm của sự phản đối đối với toán tử quá tải: mong muốn tất cả các mã được rõ ràng ngay lập tức.
Larry Coleman

2
Không rõ M * N nghĩa là gì, trong đó M và N thuộc loại Ma trận?
Dima

2
@Fred: Không. Có một loại nhân ma trận. Bạn có thể nhân ma trận mxn với ma trận nxk và lấy ma trận mxk.
Dima

1
@FredOverflow: Có nhiều cách khác nhau để nhân một vectơ ba chiều, một cách cho bạn một vô hướng và một cách cho bạn một vectơ ba chiều khác, và do đó quá tải *cho những điều đó có thể gây nhầm lẫn. Có thể cho rằng bạn có thể sử dụng operator*()cho sản phẩm chấm và operator%()cho sản phẩm chéo, nhưng tôi sẽ không làm điều đó cho thư viện sử dụng chung.
David Thornley

2
@Martin Beckett: Không. C ++ không được phép sắp xếp lại A-Btheo thứ tự B-Avà tất cả các nhà khai thác đều theo mô hình đó. Mặc dù luôn có một ngoại lệ: khi trình biên dịch có thể chứng minh điều đó không quan trọng, nó được phép sắp xếp lại mọi thứ.
Sjoerd

9

Trong Haskell "+", "-", "*", "/" vv chỉ là các hàm (infix).

Bạn có nên đặt tên hàm infix là "plus" như trong "4 plus 2" không? Tại sao không, nếu bổ sung là những gì chức năng của bạn làm. Bạn có nên đặt tên cho hàm "cộng" của mình "+" không? Tại sao không.

Tôi nghĩ vấn đề với cái gọi là "toán tử" là, chúng hầu hết giống với các phép toán và không có nhiều cách để diễn giải chúng và do đó có nhiều kỳ vọng về cách mà một phương thức / hàm / toán tử đó làm.

EDIT: làm cho quan điểm của tôi rõ ràng hơn


Erm, ngoại trừ những gì được thừa hưởng từ C, C ++ (và đó là những gì Fred đã hỏi về) thực hiện khá nhiều điều tương tự. Bây giờ những gì bạn có về việc này là tốt hay xấu?
sbi

@sbi Tôi yêu điều hành quá tải ... khai thác thực tế thậm chí C đã quá tải ... Bạn có thể sử dụng chúng cho int, float, long longvà bất cứ điều gì. Vì vậy, những gì tất cả về?
FUZxxl

@FUZxxl: Đây là tất cả về các toán tử do người dùng định nghĩa làm quá tải các toán tử tích hợp.
sbi

1
@sbi Haskell không có sự phân biệt giữa dựng sẵn và do người dùng định nghĩa . Tất cả các nhà khai thác đều như nhau. Bạn thậm chí có thể bật một số tiện ích mở rộng để loại bỏ tất cả nội dung được xác định trước và cho phép bạn viết bất cứ điều gì từ đầu, bao gồm mọi toán tử.
FUZxxl

@FUZxxl: Điều đó cũng có thể, nhưng những toán tử quá tải đối lập đó thường không phản đối việc sử dụng tích hợp sẵn +cho các loại số tích hợp khác nhau, nhưng tạo ra quá tải do người dùng xác định. do đó nhận xét của tôi.
sbi

7

Dựa trên các câu trả lời khác mà tôi đã thấy, tôi chỉ có thể kết luận rằng sự phản đối thực sự đối với quá tải toán tử là mong muốn mã rõ ràng ngay lập tức.

Điều này thật bi thảm vì hai lý do:

  1. Thực hiện theo kết luận hợp lý của nó, nguyên tắc rằng mã phải rõ ràng ngay lập tức sẽ khiến tất cả chúng ta vẫn đang mã hóa trong COBOL.
  2. Bạn không học từ mã đó là rõ ràng ngay lập tức. Bạn học từ mã có ý nghĩa một khi bạn dành một chút thời gian để suy nghĩ về cách nó hoạt động.

Học từ mã không phải luôn luôn là mục tiêu chính. Trong trường hợp như "tính năng X bị trục trặc, người đã viết nó rời khỏi công ty và bạn cần sửa nó càng sớm càng tốt", tôi muốn có mã rõ ràng hơn ngay lập tức.
Erroratz

5

Tôi phần nào đồng ý.

Nếu bạn viết multiply(j,5), jcó thể thuộc loại vô hướng hoặc ma trận, làm cho multiply()phức tạp hơn hay ít hơn, tùy thuộc vào cái gì j. Tuy nhiên, nếu bạn từ bỏ ý tưởng quá tải hoàn toàn, thì chức năng sẽ phải được đặt tên multiply_scalar()hoặc multiply_matrix()điều đó sẽ làm cho nó rõ ràng những gì xảy ra bên dưới.

Có mã nơi nhiều người trong chúng ta thích nó theo một cách và có mã nơi mà hầu hết chúng ta thích nó theo cách khác. Tuy nhiên, hầu hết các mã rơi vào giữa khu vực giữa hai thái cực đó. Những gì bạn thích ở đó phụ thuộc vào nền tảng và sở thích cá nhân của bạn.


Điểm tốt. Tuy nhiên, từ bỏ quá tải hoàn toàn không chơi tốt với lập trình chung ...
fredoverflow

@FredO: Tất nhiên là không. Nhưng lập trình chung là tất cả về việc sử dụng cùng một thuật toán cho các loại rất khác nhau, vì vậy những người thích multiply_matrix()không thích lập trình chung.
sbi

2
Bạn khá lạc quan về những cái tên phải không? Dựa trên một số nơi tôi đã làm việc, tôi mong đợi những cái tên như 'Multiply () `và' Multiplym ()` hoặc có thể real_multiply()hoặc hơn thế. Các nhà phát triển thường không tốt với tên và operator*()ít nhất là sẽ nhất quán.
David Thornley

@David: Vâng, tôi đã bỏ qua thực tế rằng tên có thể xấu. Nhưng sau đó chúng ta cũng có thể giả định rằng operator*()có thể làm điều gì đó ngu ngốc, jlà một macro đánh giá một biểu thức liên quan đến năm lệnh gọi hàm và không có gì. Sau đó, bạn không thể so sánh hai cách tiếp cận nữa. Nhưng, vâng, đặt tên cho mọi thứ tốt là khó, mặc dù cũng có giá trị bất cứ lúc nào.
sbi

5
@David: Và vì việc đặt tên rất khó, nên loại bỏ tên khỏi ngôn ngữ lập trình, phải không? Thật quá dễ để hiểu sai! ;-)
dòng chảy

4

Tôi thấy hai vấn đề với quá tải toán tử.

  1. Quá tải làm thay đổi ngữ nghĩa của toán tử, ngay cả khi điều đó không được lập trình viên dự định. Ví dụ, khi bạn quá tải &&, ||hoặc ,, bạn mất các điểm chuỗi được ngụ ý bởi các biến thể tích hợp của các toán tử này (cũng như hành vi ngắn mạch của các toán tử logic). Vì lý do này, tốt hơn là không làm quá tải các toán tử này, ngay cả khi ngôn ngữ cho phép.
  2. Một số người thấy quá tải nhà điều hành là một tính năng tốt như vậy, họ bắt đầu sử dụng nó ở mọi nơi, ngay cả khi đó không phải là giải pháp thích hợp. Điều này khiến người khác phản ứng quá mức theo hướng khác và cảnh báo chống lại việc sử dụng quá tải nhà điều hành. Tôi không đồng ý với một trong hai nhóm, nhưng hãy xem xét trung gian: Quá tải toán tử nên được sử dụng một cách tiết kiệm và chỉ khi
    • toán tử quá tải có ý nghĩa tự nhiên đối với cả chuyên gia miền và chuyên gia phần mềm. Nếu hai nhóm đó không đồng ý về ý nghĩa tự nhiên của toán tử, đừng quá tải nó.
    • đối với các loại liên quan, không có ý nghĩa tự nhiên cho toán tử và bối cảnh ngay lập tức (tốt nhất là cùng một biểu thức, nhưng không quá một vài dòng) luôn làm rõ ý nghĩa của toán tử. Một ví dụ về thể loại này sẽ operator<<dành cho các luồng.

1
+1 từ tôi, nhưng đối số thứ hai cũng có thể được áp dụng cho kế thừa. Nhiều người không có manh mối về thừa kế và cố gắng áp dụng nó vào mọi thứ. Tôi nghĩ rằng hầu hết các lập trình viên sẽ đồng ý rằng có thể lạm dụng thừa kế. Điều đó có nghĩa là thừa kế là "xấu xa" và nên bị loại bỏ khỏi các ngôn ngữ lập trình? Hoặc chúng ta nên để nó trong bởi vì nó cũng có thể hữu ích?
fredoverflow

@FredOverflow Đối số thứ hai có thể được áp dụng cho bất cứ điều gì "mới và nóng". Tôi không đưa ra nó như là một đối số để loại bỏ quá tải toán tử khỏi một ngôn ngữ, mà là một lý do tại sao mọi người tranh luận chống lại nó. Theo tôi thấy, quá tải toán tử là hữu ích nhưng nên được sử dụng cẩn thận.
Bart van Ingen Schenau

IMHO, cho phép quá tải &&||theo cách không ngụ ý trình tự là một sai lầm lớn (IMHO, nếu C ++ sẽ cho phép quá tải những thứ đó, thì nên sử dụng định dạng "hai chức năng" đặc biệt, với chức năng đầu tiên được yêu cầu để trả về một kiểu có thể chuyển đổi hoàn toàn thành một số nguyên, hàm thứ hai có thể lấy hai hoặc ba đối số, với đối số "phụ" của hàm thứ hai là kiểu trả về của hàm thứ nhất. Trình biên dịch sẽ gọi hàm thứ nhất và sau đó, nếu nó trả về giá trị khác không, hãy đánh giá toán hạng thứ hai và gọi hàm thứ hai theo nó.)
supercat

Tất nhiên, điều đó gần như không kỳ quái khi cho phép toán tử dấu phẩy bị quá tải. Ngẫu nhiên, một điều quá tải mà tôi chưa thực sự thấy, nhưng muốn, sẽ là một phương tiện truy cập thành viên ràng buộc chặt chẽ, cho phép một biểu thức muốn foo.bar[3].Xđược xử lý bởi foolớp, thay vì yêu cầu foophơi bày một thành viên mà có thể hỗ trợ đăng ký và sau đó tiếp xúc với một thành viên X. Nếu một người muốn buộc đánh giá thông qua truy cập thành viên thực tế, người ta sẽ viết ((foo.bar)[3]).X.
supercat

3

Dựa trên kinh nghiệm cá nhân của tôi, cách Java cho phép nhiều phương thức, nhưng không làm quá tải toán tử, có nghĩa là bất cứ khi nào bạn nhìn thấy một toán tử bạn đều biết chính xác nó làm gì.

Bạn không cần phải xem liệu có *gọi mã lạ hay không nhưng biết rằng đó là một bội số và nó hoạt động chính xác như cách được định nghĩa bởi Đặc tả ngôn ngữ Java. Điều này có nghĩa là bạn có thể tập trung vào hành vi thực tế thay vì tìm hiểu tất cả các công cụ wicket được xác định bởi lập trình viên.

Nói cách khác, cấm quá tải toán tử là một lợi ích cho người đọc , không phải người viết và do đó làm cho các chương trình dễ bảo trì hơn!


+1, với một cảnh báo: C ++ cung cấp cho bạn đủ dây để treo mình. Nhưng nếu tôi muốn triển khai danh sách được liên kết trong C ++, tôi muốn có khả năng sử dụng [] để truy cập phần tử thứ n. Thật hợp lý khi sử dụng các toán tử cho dữ liệu (nói theo cách toán học) mà chúng có giá trị.
Michael K

@Michael, bạn không thể sống với list.get(n)cú pháp?

@ Thorbjørn: Nó thực sự tốt, có lẽ là một ví dụ nghèo. Thời gian có thể tốt hơn - quá tải +, - sẽ có ý nghĩa hơn là time.add (AnotherTime).
Michael K

4
@Michael: Giới thiệu về danh sách được liên kết, std::listkhông quá tải operator[](hoặc đưa ra bất kỳ phương tiện lập chỉ mục nào khác vào danh sách), vì hoạt động như vậy sẽ là O (n) và giao diện danh sách sẽ không hiển thị chức năng như vậy nếu bạn quan tâm đến hiệu quả. Khách hàng có thể bị cám dỗ lặp lại các danh sách được liên kết với các chỉ mục, làm cho thuật toán O (n) không cần thiết O (n ^ 2). Bạn thấy điều đó khá thường xuyên trong mã Java, đặc biệt nếu mọi người làm việc với Listgiao diện nhằm hoàn toàn trừu tượng hóa sự phức tạp.
fredoverflow

5
@Thor: "Nhưng để chắc chắn, bạn phải kiểm tra :)" ... Một lần nữa, điều này không liên quan đến quá tải toán tử . Nếu bạn thấy time.add(anotherTime), bạn cũng sẽ phải kiểm tra xem lập trình viên thư viện có thực hiện thao tác thêm "chính xác" hay không (bất kể điều đó có nghĩa là gì).
dòng chảy

3

Một sự khác biệt giữa quá tải a * bvà gọi multiply(a,b)là cái sau có thể dễ dàng được grepping cho. Nếu multiplychức năng không bị quá tải cho các loại khác nhau thì bạn có thể tìm hiểu chính xác chức năng sẽ làm gì, mà không phải theo dõi các loại ab.

Linus Torvalds có một lập luận thú vị về tình trạng quá tải toán tử. Trong một cái gì đó giống như phát triển nhân linux, nơi hầu hết các thay đổi được gửi qua các bản vá qua email, điều quan trọng là các nhà bảo trì có thể hiểu bản vá sẽ làm gì chỉ với một vài dòng ngữ cảnh xung quanh mỗi thay đổi. Nếu các hàm và toán tử không bị quá tải thì bản vá có thể dễ dàng được đọc hơn theo cách độc lập theo ngữ cảnh, vì bạn không phải xem qua tệp đã thay đổi để biết tất cả các loại là gì và kiểm tra các toán tử bị quá tải.


Không phải kernel linux được phát triển trong C thuần túy sao? Tại sao thảo luận (nhà điều hành) quá tải ở tất cả trong bối cảnh này?
dòng chảy

Các mối quan tâm là giống nhau cho bất kỳ dự án có quá trình phát triển tương tự, bất kể ngôn ngữ. Quá tải quá mức có thể gây khó khăn cho việc hiểu tác động của các thay đổi nếu tất cả những gì bạn phải tiếp tục là một vài dòng từ một tệp vá.
Scott Wales

@FredOverflow: Nhân Linux có trong GCC C thực sự. Nó sử dụng tất cả các loại tiện ích mở rộng mang lại cho C cảm giác gần như C ++ tại một số thời điểm. Tôi đang nghĩ về một số thao tác kiểu lạ mắt.
Zan Lynx

2
@Scott: Không có lý do nào để thảo luận về "sự xấu xa" của quá tải đối với các dự án được lập trình trong C, bởi vì C không có khả năng quá tải các chức năng.
fredoverflow

3
Linus Torvalds dường như có quan điểm hạn hẹp. Đôi khi, ông chỉ trích những thứ không thực sự hữu ích cho lập trình nhân Linux như thể điều đó khiến chúng không phù hợp để sử dụng chung. Lật đổ là một ví dụ. Đó là một VCS tốt, nhưng sự phát triển nhân Linux thực sự cần một VCS phân tán, vì vậy Linus đã chỉ trích SVN nói chung.
David Thornley

2

Tôi nghi ngờ nó có một cái gì đó để làm với sự phá vỡ kỳ vọng. Tôi đã quen với C ++, bạn đã quen với hành vi của nhà điều hành không bị ngôn ngữ hoàn toàn sai khiến và bạn sẽ không ngạc nhiên khi một nhà điều hành làm điều gì đó kỳ quặc. Nếu bạn đã quen với các ngôn ngữ không có tính năng đó và sau đó xem mã C ++, bạn sẽ mang theo những kỳ vọng từ các ngôn ngữ khác đó và có thể rất ngạc nhiên khi bạn phát hiện ra rằng một nhà điều hành quá tải làm điều gì đó thú vị.

Cá nhân tôi nghĩ rằng có một sự khác biệt. Khi bạn có thể thay đổi hành vi của cú pháp tích hợp của ngôn ngữ, nó trở nên mờ hơn đối với lý do. Các ngôn ngữ không cho phép lập trình meta về mặt cú pháp ít mạnh mẽ hơn, nhưng về mặt khái niệm thì đơn giản hơn để hiểu.


Toán tử quá tải không bao giờ nên làm "một cái gì đó kỳ lạ". Tất nhiên là ổn nếu nó làm một cái gì đó phức tạp. Nhưng chỉ quá tải khi nó có một, ý nghĩa rõ ràng.
Sjoerd

2

Tôi nghĩ rằng quá tải toán tử toán học không phải là vấn đề thực sự với quá tải toán tử trong C ++. Tôi nghĩ rằng các toán tử quá tải không nên dựa vào ngữ cảnh của biểu thức (tức là kiểu) là "xấu xa". Ví dụ, quá tải , [ ] ( ) -> ->* new deletehoặc thậm chí là unary *. Bạn có một số kỳ vọng nhất định từ các nhà khai thác không bao giờ thay đổi.


+1 Đừng biến [] tương đương với ++.
Michael K

3
Bạn đang nói chúng ta không nên có khả năng quá tải các toán tử mà bạn đề cập ở tất cả ? Hay bạn chỉ nói rằng chúng ta nên quá tải chúng cho mục đích lành mạnh mà thôi? Bởi vì tôi ghét nhìn thấy các container mà không có operator[]functor mà không có các operator()con trỏ thông minh mà không có operator->vân vân.
fredoverflow

Tôi đang nói rằng vấn đề tiềm ẩn của quá tải toán tử với các phép toán là nhỏ so với các toán tử đó. Làm điều gì đó thông minh hoặc điên rồ với các toán tử toán học có thể gây rắc rối, nhưng các toán tử mà tôi liệt kê, mà mọi người thường không nghĩ là toán tử mà là các yếu tố ngôn ngữ cơ bản, phải luôn đáp ứng kỳ vọng được xác định bởi ngôn ngữ. []phải luôn là một trình ->truy cập giống như mảng và luôn luôn có nghĩa là truy cập một thành viên. Không thành vấn đề nếu đó thực sự là một mảng hoặc một thùng chứa khác, hoặc nó có phải là một con trỏ thông minh hay không.
Allon Guralnek

2

Tôi hoàn toàn hiểu bạn không thích tranh luận của Joel về việc che giấu. Tôi cũng không. Thực sự tốt hơn nhiều khi sử dụng '+' cho những thứ như các loại số tích hợp hoặc cho các loại của riêng bạn như ma trận. Tôi thừa nhận điều này là gọn gàng và thanh lịch để có thể nhân hai ma trận với '*' thay vì '.multiply ()'. Và sau tất cả chúng ta đã có một loại trừu tượng giống nhau trong cả hai trường hợp.

Điều đau lòng ở đây là khả năng đọc mã của bạn. Trong một trường hợp thực tế, không phải trong ví dụ học thuật về nhân ma trận. Đặc biệt là nếu ngôn ngữ của bạn cho phép xác định các toán tử ban đầu không có trong lõi ngôn ngữ =:=. Rất nhiều câu hỏi thêm phát sinh tại thời điểm này. Nhà điều hành chết tiệt đó là về cái gì? Ý tôi là sự ưu tiên của thứ đó là gì? Sự kết hợp là gì? Theo thứ tự nào là a =:= b =:= cthực sự thực hiện?

Đó đã là một đối số chống lại quá tải toán tử. Vẫn không thuyết phục? Kiểm tra các quy tắc ưu tiên đưa bạn không quá 10 giây? Ok, chúng ta hãy đi xa hơn.

Nếu bạn bắt đầu sử dụng ngôn ngữ cho phép quá tải toán tử, ví dụ ngôn ngữ phổ biến có tên bắt đầu bằng 'S', bạn sẽ nhanh chóng biết rằng các nhà thiết kế thư viện thích ghi đè toán tử. Tất nhiên họ được giáo dục tốt, họ tuân theo các thực tiễn tốt nhất (không có sự hoài nghi ở đây) và tất cả các API của họ có ý nghĩa hoàn hảo khi chúng ta xem xét chúng một cách riêng biệt.

Bây giờ hãy tưởng tượng bạn phải sử dụng một vài API sử dụng nhiều toán tử quá tải với nhau trong một đoạn mã. Hoặc thậm chí tốt hơn - bạn phải đọc một số mã kế thừa như thế. Đây là khi các nhà điều hành quá tải thực sự hút. Về cơ bản, nếu có nhiều toán tử quá tải ở một nơi, chúng sẽ sớm bắt đầu trộn lẫn với các ký tự không phải là số alpha khác trong mã chương trình của bạn. Chúng sẽ trộn lẫn với các ký tự không phải là số không thực sự là toán tử mà là một số yếu tố ngữ pháp ngôn ngữ cơ bản hơn xác định những thứ như khối và phạm vi, hình dạng câu lệnh kiểm soát dòng chảy hoặc biểu thị một số điều meta. Bạn sẽ cần đặt kính và di chuyển mắt gần 10 cm với màn hình LCD để hiểu được sự lộn xộn thị giác đó.


1
Quá tải các toán tử hiện có và phát minh ra các toán tử mới không giống nhau, nhưng +1 từ tôi.
dòng chảy

1

Nói chung, tôi tránh sử dụng quá tải toán tử theo những cách không trực quan. Đó là, nếu tôi có một lớp số, quá tải * là chấp nhận được (và được khuyến khích). Tuy nhiên, nếu tôi có một Nhân viên lớp, thì quá tải * sẽ làm gì? Nói cách khác, các toán tử quá tải theo những cách trực quan giúp bạn dễ đọc và dễ hiểu.

Chấp nhận / Khuyến khích:

class Complex
{
public:
    double r;
    double i;

    Complex operator*(const Compex& rhs)
    {
        Complex result;
        result.r = (r * rhs.r) - (i * rhs.i);
        result.i = (r * rhs.i) + (i * rhs.r);
        return result;
    }
};

Không thể chấp nhận:

class Employee
{
public:
    std::string name;
    std::string address;
    std::string phone_number;

    Employee operator* (const Employee& e)
    {
        // what the hell do I do here??
    }
};

1
Nhân rộng nhân viên? Chắc chắn đó là một hành vi phạm tội có thể sa thải, nếu họ làm điều đó trên bàn phòng họp, đó là.
gbjbaanb

1

Ngoài những gì đã được nói ở đây, còn có một cuộc tranh luận nữa về việc quá tải toán tử. Thật vậy, nếu bạn viết +, đây là một điều hiển nhiên mà bạn muốn nói thêm vào một cái gì đó. Nhưng đây không phải là luôn luôn như vậy.

C ++ tự cung cấp một ví dụ tuyệt vời về trường hợp như vậy. Làm thế nào được stream << 1cho là được đọc? luồng dịch chuyển trái 1? Không rõ ràng chút nào trừ khi bạn biết rõ rằng << trong C ++ cũng ghi vào luồng. Tuy nhiên, nếu thao tác này được thực hiện như một phương thức, không có nhà phát triển lành mạnh nào viết o.leftShift(1), nó sẽ giống như vậy o.write(1).

Điểm mấu chốt là bằng cách làm cho quá tải toán tử không khả dụng, ngôn ngữ làm cho các lập trình viên nghĩ về tên của các hoạt động. Ngay cả khi tên được chọn không hoàn hảo, vẫn khó hiểu sai tên hơn là một dấu hiệu.


1

So với các phương thức đánh vần, các toán tử ngắn hơn, nhưng chúng cũng không yêu cầu dấu ngoặc đơn. Dấu ngoặc tương đối bất tiện để gõ. Và bạn phải cân bằng chúng. Tổng cộng, bất kỳ cuộc gọi phương thức nào cũng yêu cầu ba ký tự nhiễu đơn giản so với toán tử. Điều này làm cho việc sử dụng các toán tử rất, rất hấp dẫn.
Tại sao người khác muốn điều này : cout << "Hello world"?

Vấn đề với quá tải là, hầu hết các lập trình viên đều lười biếng không thể tin được và hầu hết các lập trình viên không thể đủ khả năng.

Điều khiến các lập trình viên C ++ lạm dụng quá tải toán tử không phải là sự hiện diện của nó, mà là sự thiếu vắng một cách gọn gàng hơn để thực hiện các cuộc gọi phương thức. Và mọi người không chỉ sợ quá tải nhà điều hành vì điều đó là có thể, nhưng bởi vì nó đã được thực hiện.
Lưu ý rằng ví dụ trong Ruby và Scala không ai sợ quá tải toán tử. Ngoài thực tế, việc sử dụng các toán tử không thực sự ngắn hơn các phương thức, một lý do khác là, Ruby giới hạn toán tử quá tải đến mức tối thiểu hợp lý, trong khi Scala cho phép bạn khai báo các toán tử của riêng mình do đó tránh được va chạm tầm thường.


hoặc, trong C #, để sử dụng + = để liên kết một sự kiện với một đại biểu. Tôi không nghĩ rằng đổ lỗi cho các tính năng ngôn ngữ cho sự ngu ngốc của lập trình viên là một cách xây dựng về phía trước.
gbjbaanb

0

Lý do Toán tử quá tải là đáng sợ, bởi vì có một số lượng lớn các lập trình viên thậm chí sẽ không bao giờ nghĩ rằng điều *đó không có nghĩa đơn giản là "nhân", trong khi một phương pháp như foo.multiply(bar)ít nhất chỉ ra ngay lập tức cho lập trình viên rằng ai đó đã viết một phương pháp nhân tùy chỉnh . Tại thời điểm đó, họ sẽ tự hỏi tại sao và đi điều tra.

Tôi đã làm việc với "lập trình viên giỏi", những người ở vị trí cấp cao sẽ tạo ra các phương thức gọi là "So sánh giá trị" sẽ có 2 đối số và áp dụng các giá trị từ cái này sang cái kia và trả về một giá trị boolean. Hoặc một phương thức gọi là "LoadTheValues" sẽ đi đến cơ sở dữ liệu cho 3 đối tượng khác, lấy giá trị, thực hiện tính toán, sửa đổi thisvà lưu nó vào cơ sở dữ liệu.

Nếu tôi đang làm việc trong một nhóm với những loại lập trình viên đó, tôi sẽ biết ngay để điều tra những thứ họ đã làm việc. Nếu họ làm quá tải một nhà điều hành, tôi không có cách nào để biết rằng họ đã làm điều đó ngoại trừ giả định họ đã làm và đi tìm.

Trong một thế giới hoàn hảo, hoặc một nhóm với các lập trình viên hoàn hảo, quá tải toán tử có lẽ là một công cụ tuyệt vời. Tôi vẫn chưa làm việc với một nhóm lập trình viên hoàn hảo, vì vậy đó là lý do tại sao nó đáng sợ.

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.