Khi nào cần quá tải toán tử dấu phẩy?


76

Tôi thường xuyên thấy các câu hỏi trên SO về việc nạp chồng toán tử dấu phẩy trong C ++ (chủ yếu không liên quan đến việc nạp chồng, nhưng những thứ như khái niệm về điểm trình tự) và điều đó khiến tôi tự hỏi:

Khi nào bạn nên điền dấu phẩy? Một số ví dụ về công dụng thực tế của nó là gì?

Tôi chỉ không thể nghĩ ra bất kỳ ví dụ nào mà tôi đã thấy hoặc cần đến những thứ như

foo, bar;

trong mã thế giới thực, vì vậy tôi tò mò không biết khi nào (nếu có) điều này thực sự được sử dụng.


1
Bây giờ C ++ có cú pháp khởi tạo thống nhất, hầu hết các kỹ thuật này là không cần thiết.
Dan

Câu trả lời:


68

Hãy thay đổi sự nhấn mạnh một chút thành:

Khi nào bạn nên điền dấu phẩy?

Câu trả lời: Không bao giờ.

Ngoại lệ: Nếu bạn đang thực hiện lập trình siêu mẫu, operator, có một vị trí đặc biệt ở cuối danh sách ưu tiên toán tử, có thể hữu ích cho việc xây dựng các SFINAE-Guard, v.v.

Hai cách sử dụng thực tế duy nhất mà tôi đã thấy về quá tải operator,đều có trong Boost :

  • Boost.Assign
  • Boost.Phoenix - điều cơ bản ở đây là nó cho phép các lambdas Phoenix hỗ trợ nhiều câu lệnh

7
Nhưng +1 cho trường hợp ngoại lệ. : P Bạn có vui lòng giải thích một chút về cách sử dụng lập trình siêu mẫu operator,không? Nghe thật thú vị.
user541686 09/04

@Mehrdad: Sẽ quá lâu để đưa ra một nhận xét và tôi không nhớ lại bất kỳ bài báo nào về nó trên mạng ngoại trừ một số bài của Eric Niebler (hãy kiểm tra trang web của anh ấy, tất cả đều đáng đọc). Theo kinh nghiệm cá nhân của tôi, tôi chưa bao giờ gặp trường hợp mà thay vào đó tôi không thể sử dụng độ phân giải quá tải + ...để giải quyết vấn đề của mình (cá nhân tôi thấy dễ hiểu hơn so với ưu tiên toán tử + operator,).
ildjarn

2
Ngoài ra, Boost.Parameter sẽ nạp chồng toán tử dấu phẩy, đây là một cách sử dụng khác. Ngoài ra, tôi đồng ý rằng toán tử dấu phẩy hầu như không bao giờ được quá tải. Nó khó sử dụng một cách hiệu quả, vì mức độ ưu tiên của nó thấp.
Paul Fultz II

2
bạn cũng có thể tìm thấy nó ở Eigen.
Gabriel de Grimouard

1
Mức độ ưu tiên thấp cho phép thành phần của hầu hết tất cả các biểu thức có thể tưởng tượng mà không yêu cầu thêm dấu ngoặc đơn - đó là một thuộc tính rất gọn gàng của toán tử đó. Nó rất tiện dụng và trong nhiều năm, tôi đã tìm thấy rất nhiều công dụng của nó để làm cho mã dễ đọc và diễn đạt ... nhưng quy tắc của tôi là chỉ sử dụng nó khi nó không gây bất ngờ và làm cho ý nghĩa của mã rõ ràng ngay cả với người chưa đọc tài liệu về API đang được sử dụng.
Kuba vẫn chưa quên Monica

150

Tôi đã sử dụng toán tử dấu phẩy để lập chỉ mục các bản đồ có nhiều chỉ số.

enum Place {new_york, washington, ...};

pair<Place, Place> operator , (Place p1, Place p2)
{
    return make_pair(p1, p2);
}


map< pair<Place, Place>, double> distance;

distance[new_york, washington] = 100;

33
Tôi thực sự thực sự thích điều này, +1.
ildjarn

12
Mặt khác, điều này là để khắc phục thực tế là chúng ta chỉ có thể truyền một tham số cho operator[]. Một số người đã đề xuất rằng nó có thể mất một số tham số: xem Báo cáo Lỗi Tiến hóa 88 .
Morwenn

2
Nó giống như một cú pháp tuyệt vời cũng được sử dụng để triển khai mảng nhiều chiều nhưng tiếc là không quá tốt cho các kiểu tích phân.
sim642

11
distance[{new_york, washington}]hoạt động mà không làm quá tải bất cứ điều gì. Một bộ dấu ngoặc bổ sung là một cái giá nhỏ phải trả để tránh một điều gì đó quá xấu xa!
Arthur Tacca

3
Điều gì sẽ xảy ra nếu bạn gọi một hàm foo(new_york, washington), mà sẽ có hai vị trí riêng biệt làm đối số?
OutOfBound

38

Boost.Assign sử dụng nó để cho phép bạn làm những việc như:

vector<int> v; 
v += 1,2,3,4,5,6,7,8,9;

Và tôi đã thấy nó được sử dụng để hack ngôn ngữ kỳ quặc, tôi sẽ xem nếu tôi có thể tìm thấy một số.


Aha, tôi nhớ một trong những cách sử dụng kỳ quặc đó: thu thập nhiều biểu thức . (Cảnh báo, ma thuật hắc ám.)


1
Meh, không thể tìm thấy nó. Những thứ rất góc cạnh.
GManNickG

1
Nhưng nghiêm túc mà nói, bạn có thực sự muốn viết mã như thế này không? Đối với ai đó đang đọc mã của bạn, điều này sẽ hoàn toàn khó hiểu. Tôi giả sử rằng đoạn mã là viết tắt của a push_backtrên 8 giá trị đó, nhưng có vẻ như 9 đang được thêm vào a vector<int>, điều này không có ý nghĩa gì. Thành thật mà nói, đây là một lập luận phản bác mạnh mẽ cho việc Boost là một "thư viện chất lượng cao". Mã phải rõ ràng và rõ ràng. Nếu không, người ta cũng có thể triển khai một cái gì đó như T& operator--(int){ delete this; return *this; }, cái gì đó cũng có thể hoạt động tốt. Nó chỉ là không rõ ràng cho người khác những gì sẽ xảy ra.
Damon

2
Vâng, toán tử + = thêm vào, theo cách hiểu thông thường, giá trị của biểu thức ở phía bên phải. Biểu thức 1,2, ... 9 đánh giá là 9 theo cách hiểu thông thường. Việc nạp chồng các toán tử sẽ làm đảo lộn ngữ nghĩa và mặc dù nó có giá trị về mặt cú pháp, nhưng điều đó không có nghĩa là nó nhất thiết phải tốt. Nạp chồng toán tử là tốt nếu nó làm cho mã rõ ràng, nhưng ở đây nó làm cho mã trở nên mơ hồ và khó hiểu (ít nhất là theo cảm nhận của tôi). Nó khác nhiều so với việc gán initializer_list trong C ++ 0x vì dấu ngoặc nhọn làm cho nó rõ ràng ngay lập tức những gì đang xảy ra. Ngoài ra, tôi cho là quá tải toán tử + = cho một vectơ ...
Damon

5
... có thể không phải là một trong những lựa chọn khôn ngoan nhất, bởi vì có ít nhất hai cách giải thích hợp lệ như nhau của toán tử đó trên một vectơ. Tôi giả sử rằng "nối (các) phần tử vào cuối" là ý nghĩa ở đây, nhưng nó cũng có thể là "gọi toán tử + = trên mỗi phần tử trong vectơ với các đối số này". Nó rất có thể được định nghĩa chỉ cho các tập hợp có kích thước bằng nhau, hoặc nó có thể không mở rộng tập hợp nhỏ hơn, hoặc bất cứ điều gì ... điều mà bạn không biết nếu không nghiên cứu kỹ tài liệu, nó không rõ ràng. Mã tốt là điều hiển nhiên mà không cần giải thích.
Damon

2
Một ví dụ khác, tôi nhớ đã chạy qua một lớp chuỗi cách đây vài năm, lớp này đã quá tải operator<=. Điều đó cho phép bạn viết mã tuyệt vời như thế nào str <= "foo";. Ngoại trừ việc không hay ho chút nào khi người tiếp theo đọc mã của bạn nói "cái quái gì vậy?" và nó trở nên hoàn toàn không thoải mái khi lần đầu tiên bạn dành một tuần để gỡ lỗi mà không có gì vì ai đó không biết và viết một cái gì đó như thế if(str <= "bar").
Damon

24

Dấu phẩy có một thuộc tính thú vị là nó có thể nhận một tham số kiểu void . Nếu đúng như vậy, thì toán tử dấu phẩy có sẵn sẽ được sử dụng.

Điều này rất hữu ích khi bạn muốn xác định xem một biểu thức có kiểu void:

namespace detail_
{
    template <typename T>
    struct tag
    {
        static T get();
    };

    template <typename T, typename U>
    tag<char(&)[2]> operator,(T, tag<U>);

    template <typename T, typename U>
    tag<U> operator,(tag<T>, tag<U>);
}

#define HAS_VOID_TYPE(expr) \
    (sizeof((::detail_::tag<int>(), \
             (expr), \
             ::detail_::tag<char>).get()) == 1)

Tôi để người đọc tự tìm hiểu xem chuyện gì đang xảy ra. Hãy nhớ rằng các operator,liên kết ở bên phải.



9

Trong SOCI - Thư viện truy cập cơ sở dữ liệu C ++, nó được sử dụng để triển khai phần gửi đến của giao diện:

sql << "select name, salary from persons where id = " << id,
       into(name), into(salary);

Từ câu hỏi thường gặp cơ sở :

Hỏi: Toán tử dấu phẩy bị quá tải chỉ là sự xáo trộn, tôi không thích nó.

Vâng, hãy xem xét những điều sau:

"Gửi truy vấn X đến máy chủ Y và đặt kết quả vào biến Z."

Ở trên, dấu "và" đóng vai trò của dấu phẩy. Ngay cả khi nạp chồng toán tử dấu phẩy không phải là một thực hành rất phổ biến trong C ++, một số thư viện làm điều này, đạt được cú pháp ngắn gọn và dễ học. Chúng tôi khá chắc chắn rằng trong SOCI, toán tử dấu phẩy đã bị quá tải với một hiệu ứng tốt.


8

Tôi sử dụng toán tử dấu phẩy để in đầu ra nhật ký. Nó thực sự rất giống ostream::operator<<nhưng tôi thấy toán tử dấu phẩy thực sự tốt hơn cho nhiệm vụ.

Vì vậy, tôi có:

template <typename T>
MyLogType::operator,(const T& data) { /* do the same thing as with ostream::operator<<*/ }

Nó có những đặc tính tốt

  • Toán tử dấu phẩy có mức ưu tiên thấp nhất. Vì vậy, nếu bạn muốn truyền trực tuyến một biểu thức, mọi thứ sẽ không rối tung nếu bạn quên dấu ngoặc đơn. So sánh:

    myLog << "The mask result is: " << x&y; //operator precedence would mess this one up
    myLog, "The result is: ", x&y;
    

    bạn thậm chí có thể trộn các toán tử so sánh bên trong mà không gặp vấn đề gì, ví dụ:

    myLog, "a==b: ", a==b;
    
  • Toán tử dấu phẩy là nhỏ. Nó không làm rối mắt việc đọc khi dán nhiều thứ lại với nhau

    myLog, "Coords=", g, ':', s, ':', p;
    
  • Nó phù hợp với ý nghĩa của toán tử dấu phẩy, tức là "in cái này" và sau đó "in cái kia".


6

Một khả năng là Boost Assign thư viện (mặc dù tôi khá chắc chắn rằng một số người sẽ coi việc lạm dụng này hơn là sử dụng tốt).

Boost Spirit có thể làm quá tải toán tử dấu phẩy (nó làm quá tải hầu hết mọi thứ khác ...)


Thư viện chắc chắn thú vị! +1
dùng541686

5

Cùng dòng đó, tôi đã được gửi một yêu cầu kéo github với quá tải toán tử dấu phẩy. Nó trông giống như sau

class Mylogger {
    public:
            template <typename T>
            Mylogger & operator,(const T & val) {
                    std::cout << val;
                    return * this;
            }
 };

 #define  Log(level,args...)  \
    do { Mylogger logv; logv,level, ":", ##args; } while (0)

thì trong mã của tôi, tôi có thể làm:

 Log(2, "INFO: setting variable \", 1, "\"\n");

Ai đó có thể giải thích tại sao đây là một trường hợp sử dụng tốt hay xấu?


1
Tôi không biết nó có phải là xấu hay không. Nhưng tránh viết code như thế này: ... << "This is a message on line " << std::to_string(__LINE__) << " because variable a = " << std::to_string(a) << " which is larger than " << std::to_string(limit) << "\n". Điều này rất phổ biến trong việc báo cáo lỗi hoặc xây dựng thông báo cho các trường hợp ngoại lệ. Tôi không chắc chắn nếu dấu phẩy là lựa chọn duy nhất: bất kỳ nhà điều hành khác có thể đạt được điều này, ví dụ operator+hoặc operator|hoặc operator&&hoặc thậm chí operator<<bản thân. Nhưng nó là một trường hợp xen kẽ.
alfC

4
Tôi nghĩ rằng C ++ hiện đại sẽ sử dụng các nhiệt độ đa dạng để thay thế.
Petter

Thật tệ khi trả lời câu hỏi bằng câu hỏi ;-)
lmat - Phục hồi Monica

4

Một trong những cách sử dụng thực tế là sử dụng hiệu quả nó với các đối số biến trong macro. Nhân tiện, các đối số biến trước đây là một phần mở rộng trong GCC và bây giờ là một phần của tiêu chuẩn C ++ 11.

Giả sử chúng ta có một class X, thêm đối tượng kiểu Avào nó. I E

class X {
  public: X& operator+= (const A&);
};

Nếu chúng ta muốn thêm 1 hoặc nhiều đối tượng Avào X buffer;thì sao?
Ví dụ,

#define ADD(buffer, ...) buffer += __VA_ARGS__

Macro trên, nếu được sử dụng như:

ADD(buffer, objA1, objA2, objA3);

thì nó sẽ mở rộng thành:

buffer += objA1, objeA2, objA3;

Do đó, đây sẽ là một ví dụ hoàn hảo về việc sử dụng toán tử dấu phẩy, vì các đối số biến mở rộng cùng một.

Vì vậy, để giải quyết điều này, chúng tôi nạp chồng commatoán tử và quấn nó xung quanh +=như bên dưới

  X& X::operator, (const A& a) {  // declared inside `class X`
    *this += a;  // calls `operator+=`
  }

Có lẽ bây giờ nó nên như vậy template<typename ... A> X& ADD(X& buff, A ... args) { int sink[]={ 0,(void(buff+=args),0)... }; return buff;}. Lưu ý: bạn có thể phải ngăn chặn việc tối ưu hóa bồn rửa bằng một (void) sink;câu lệnh. Điều này né tránh macro, tức là imo, thậm chí còn tốt hơn
WorldSEnder

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.