Коли перевантажувати оператор кома?


76

Я так часто бачу запитання щодо SO щодо перевантаження оператора коми в C ++ (головним чином, не пов'язаного з самим перевантаженням, але такими речами, як поняття точок послідовності), і це змушує мене задуматися:

Коли слід перевантажувати кому? Які приклади практичного використання?

Я просто не можу придумати жодних прикладів, які б я бачив чи потребував чогось подібного

foo, bar;

в реальному коді, тому мені цікаво, коли (якщо взагалі) це насправді використовується.


1
Тепер, коли C ++ має єдиний синтаксис ініціалізації, більшість із цих методів непотрібні.
Ден,

Відповіді:


68

Давайте трохи змінимо акцент на:

Коли повинні ви перевантажувати кому?

Відповідь: Ніколи.

Виняток: якщо ви робите метапрограмування шаблону, у operator,нього є спеціальне місце в самому низу списку пріоритетів операторів, що може стати в нагоді для побудови SFINAE-охоронців тощо.

Єдине практичне використання перевантажень, яке я бачив, - operator,це Boost :


7
Але +1 для винятку. : P Не могли б Ви трохи детальніше розглянути використання метапрограмування шаблону operator,? Це звучить по-справжньому цікаво.
user541686

@Mehrdad: Було б занадто довго коментувати, і я не згадую жодних статей про це в Інтернеті, окрім, можливо, кількох Еріка Ніблера (перевірте його веб-сайт, усі вони варто прочитати). На моєму особистому досвіді я ніколи не стикався з екземпляром, коли б я не міг використовувати ...вирішення проблеми перевантаження + для вирішення своєї проблеми (натомість мені особисто легше зрозуміти, ніж пріоритет оператора + operator,).
ildjarn

2
Крім того, Boost.Parameter перевантажує оператор коми, що є іншим використанням. Крім того, я згоден з тим, що оператор-кома майже ніколи не повинен перевантажуватися. Його важко використовувати ефективно через низький пріоритет.
Paul Fultz II

2
Ви також можете знайти його в Eigen.
Габріель де Гримуар

1
Низький пріоритет дозволяє складати майже всі мислимі вирази, не вимагаючи додаткових дужок - це дуже акуратна властивість цього оператора. Це справді зручно, і протягом багатьох років я знайшов для нього безліч застосувань, які зробили код читабельним та виразним ... але моє правило полягає в тому, щоб використовувати його лише тоді, коли він не викликає сюрпризів і робить значення коду очевидним навіть хтось, хто не читав документацію використовуваного API.
Куба не забув Моніку

150

Я використовував оператор кома для індексації карт з кількома індексами.

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
Мені насправді це дуже подобається, +1.
ildjarn

12
З іншого боку, це для подолання того факту, що ми можемо передати лише один параметр operator[]. Деякі припускають, що він може приймати кілька параметрів: див. Звіт про дефекти еволюції 88 .
Морвенн,

2
Це схоже на чудовий синтаксис, який також можна використовувати для реалізації багатовимірного масиву, але, на жаль, не настільки перевантажений для інтегральних типів.
sim642

11
distance[{new_york, washington}]працює, не перевантажуючи нічого. Додатковий набір дужок - це невелика ціна, яку потрібно заплатити, щоб уникнути чогось такого злого!
Артур Такка,

3
Що станеться, якщо ви викликаєте функцію foo(new_york, washington), яка повинна взяти два окремі місця як аргументи?
OutOfBound

38

Boost.Assign використовує його, щоб дозволити вам робити такі речі:

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

І я бачив, як це використовується для химерних мовних хакерів, я подивлюсь, чи зможу я їх знайти.


Ага, я пам’ятаю одне з тих химерних застосувань: збирання кількох виразів . (Попередження, темна магія.)


1
Ме, не можу знайти. Дуже кутові речі.
GManNickG,

1
Але якщо серйозно, чи справді ви хочете написати такий код? Для когось, хто читає ваш код, це буде абсолютно заплутано. Я припускаю, що фрагмент - це скорочення для push_backцих 8 значень, але, схоже, 9 додається до a vector<int>, що не має жодного сенсу. Чесно кажучи, це вагомий контраргумент того, що Boost є "високоякісною бібліотекою". Кодекс повинен бути чітким і очевидним. Інакше можна було б також реалізувати щось на зразок того T& operator--(int){ delete this; return *this; }, що, мабуть, теж добре працювало б. Просто для когось іншого не очевидно, що відбувається.
Деймон,

2
Ну, оператор + = додає, в загальному розумінні, значення виразу з правого боку. Вираз 1,2, ... 9 оцінюється як 9 у загальному розумінні. Оператори перевантаження підривають семантику, і хоча вона синтаксично достовірна, це не означає, що вона обов'язково хороша. Перевантаження оператора добре, якщо це робить код чітким, але тут воно робить код неоднозначним і заплутаним (принаймні, на мою думку). Це набагато інакше, наприклад, при присвоєнні списку ініціалізаторів у C ++ 0x, оскільки фігурні дужки відразу дають зрозуміти, що відбувається. Крім того, я вважаю оператор перевантаження + = для вектора ...
Деймон

5
... як, можливо, не один з наймудріших виборів, тому що є принаймні два однаково вірні інтерпретації цього оператора на векторі. Я припускаю, що тут мається на увазі "додати елемент (и) до кінця", але однаково цілком може бути "викликати оператор + = для кожного елемента у векторі з цими аргументами". Це цілком може бути визначено лише для наборів однакового розміру, або це може розширити нуль менший набір, або що завгодно ... річ у тому, що ви не знаєте без інтенсивного вивчення документації, це не очевидно. Хороший код очевидний без пояснень.
Деймон,

2
Як інший приклад, я пам’ятаю, як кілька років тому натрапив на рядовий клас, який перевантажився operator<=. Це дозволило вам писати крутий код, як str <= "foo";. Окрім того, що це зовсім не круто, коли наступний, хто читає ваш код, каже "що, біса?" і це стає абсолютно нехолодним, коли ви вперше витрачаєте тиждень на налагодження нічого, тому що хтось не знав і писав щось подібне if(str <= "bar").
Деймон,

24

Кома має цікаву властивість, оскільки вона може приймати параметр типу void. Якщо це так, то використовується вбудований оператор-кома.

Це зручно, коли потрібно визначити, чи має вираз тип 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)

Я даю читачеві зрозуміти як вправу, що відбувається. Пам'ятайте, що operator,товариші праворуч.



9

У SOCI - Бібліотека доступу до бази даних C ++ використовується для реалізації вхідної частини інтерфейсу:

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

З обґрунтування поширених запитань :

З: Перевантажений оператор-кома просто затуманює, мені це не подобається.

Ну, розглянемо наступне:

"Надішліть запит X на сервер Y і помістіть результат у змінну Z."

Вище "і" відіграє роль коми. Навіть якщо перевантаження оператора коми не є дуже популярною практикою в C ++, деякі бібліотеки роблять це, досягаючи стислого та простого для вивчення синтаксису. Ми майже впевнені, що в SOCI оператор коми був перевантажений з хорошим ефектом.


8

Я використовую оператор коми для друку журналу. Це насправді дуже схоже на, ostream::operator<<але я вважаю, що оператор-кома насправді кращий для цього завдання.

Отже я маю:

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

Він має ці приємні властивості

  • Оператор-кома має найменший пріоритет. Отже, якщо ви хочете транслювати вираз, все не запсується, якщо ви забудете дужки. Порівняйте:

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

    Ви навіть можете змішувати оператори порівняння всередині без проблем, наприклад

    myLog, "a==b: ", a==b;
    
  • Оператор-кома візуально маленький. Це не псує читання, коли склеюєш багато речей

    myLog, "Coords=", g, ':', s, ':', p;
    
  • Він узгоджується зі значенням оператора коми, тобто "друкувати це", а потім "друкувати те".


6

Однією з можливостей є бібліотека Boost Assign (хоча я майже впевнений, що деякі люди вважають це зловживанням, а не корисним використанням).

Boost Spirit, ймовірно, також перевантажує оператор коми (він перевантажує майже все інше ...)


Безумовно цікаві бібліотеки! +1
користувач541686,

5

У тому ж напрямку мені надіслали запит на витягування github із перевантаженням оператора коми. Це виглядало приблизно як наступне

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)

тоді в моєму коді я можу зробити:

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

Хтось може пояснити, чому це хороший чи поганий випадок використання?


1
Не знаю, погано це чи ні. Але уникає писати код , як це: ... << "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". Що дуже часто зустрічається у звітах про помилки або створення повідомлень для винятків. Я не впевнений, чи кома була єдиним вибором: будь-який інший оператор міг би цього досягти, наприклад operator+або operator|або, operator&&або навіть operator<<сам. Але це випадкові випадки.
alfC

4
Я думаю, що сучасний С ++ використовував би замість цього варіативні шаблони.
Петтер

Погано відповідати на запитання питаннями ;-)
lmat - Поновити Моніку

4

Одне з практичних завдань - це ефективне використання із змінними аргументами в макросі. До речі, аргументи змінних раніше були розширенням у GCC, а тепер є частиною стандарту C ++ 11.

Припустимо, у нас є a class X, який додає до нього об’єкт типу A. тобто

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

Що робити, якщо ми хочемо додати 1 або більше об’єктів Aв X buffer;?
Наприклад,

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

Вгорі макрос, якщо він використовується як:

ADD(buffer, objA1, objA2, objA3);

то він розшириться до:

buffer += objA1, objeA2, objA3;

Отже, це буде прекрасним прикладом використання оператора кома, оскільки аргументи змінної розширюються з однаковими.

Тому, щоб вирішити цю проблему, ми перевантажуємо commaоператор і обертаємо його, +=як показано нижче

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

Можливо, зараз це повинно бути template<typename ... A> X& ADD(X& buff, A ... args) { int sink[]={ 0,(void(buff+=args),0)... }; return buff;}. Примітка: вам, мабуть, доведеться запобігти оптимізації раковини за допомогою (void) sink;заяви. Це ухиляється від макросу, що, imo, ще краще
WorldSEnder

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.