Pull to refresh

Comments 42

У вас в комментариях к методам оператора декремента «инкремент».
Заметил как раз перед комментарием :-)
fixed
new и delete вообще отдельная тема, раздувать топик не стал. Могу как-нибудь потом написать пару строк :-)
Самое интересное-то вы и не написали :)
Я соглаен с автором, new и delete это управление памятью — отдельная тема. И в добавок очень большая. Отдельно операторы рассматривать нет смысла, имеет смысл рассмотреть вместе с созданием своего менеджера памяти.

А вот конструктор копий к оператору равно все же имело смысл прицепить для галочки и объяснить почему написано =, а вызывается конструктор копий.
Точно, очередные издержки копипаста.
fixed
> Использовать её следует только тогда, когда это упростит написание кода. Но, не настолько, чтобы это затрудняло чтение. Ведь, как известно, код читается намного чаще, чем пишется.

Только вы забыли указать самый существенный «ньюанс», то что код читать придется через полгода-годик, а то и больше. А единственное желание когда смотришь на код, в котором с не очевидно что он делает и как — это переписать его нахрен. Мой многолетний опыт говорит — хочешь чтобы тебя ненавидели — перегружай операторы, делай код неочевидным даже самому себе!

А ещё есть операторы преобразования типов:

class A
{
    int x;

public:
    A(int _x): x(_x) {}

    operator int() const {
        return x;
    }
};

void foo() {
    A a;
    int b;
    b = a;
}
1. Топик стал бы самодостаточным, если бы перегрузка операторов была показана на примерах в которых она улучшает читаемость кода или позволяет гибко его изменять.

Например для проверки доступа по одному индексу я всегда перегружаю оператор[], а по нескольким индексам оператор ().
Перегрузка операторов +, -, *, / часто полезно для записи вычислительных алгоритмов где вместо стандартных числовых типов могут быть матрицы, полиномы, вектора, так называемые «длинные числа» (для них можно перегрузить и сдвиги >>,
и т.д.
Для обращения по указателю, например при работе со списком, для доступа к данным часто перегружаю оператор *.

2. Желательно было бы привести в разделе «Рекомендации к форме определения операторов» более четкое описание почему именно рекомендован такой выбор формы friend или член класса.

friend только для бинарных операторов и когда, с точки зрения типа данных (класса), входные аргументы по смыслу находятся в равном положении. Классический пример арифметические операторы +, -, *? /.
Во всех остальных случаях член класса.

3. Важное упущение при описании. Оператор = при наследовании не наследуется. Если подумать, то будет видно, что при наследовании могут быть добавлены новые поля или изменено их смысловое значение и следовательно оператор = может не работать корректно.

Очень многие делают эту ошибку. Поскольку оператор = не наследуется, то по умолчанию будет простое побайтовое копирование полей класса, а программист будет думать, что вызывается его перегрузка оператора.
Многие против того, чтобы перегружать () для доступа по индексу, все же это оператор вызова функции или если быть точнее функтора в данном контексте. В итоге если новый программист увидит круглые скобки, он и подумает, что это функция/функтор, но никак не доступ к массиву по индексу.

Всегда придерживаюсь правила, перегружать операторы только для тех целей, для которых они предназначены. Разве что это совсем улучшит читаемость кода.

Написать то можно много, потом самому в этом и запутаться:-)
Вы правы с одной стороны, но есть понятие «соглашения» для разработчиков и тут, когда нужно обеспечить доступ, для тривиальности например к трехмерному массиву представленному (с учетом кешей и т.д.) в виде одномерного то в операторной форме нет альтернативы. Особенно когда много обращений в одной формуле. Вообщем разумное замечание.

PS. Я сначала обрадовался, Вы дали разумное, грамотное замечание, а потом посмотрел на Ваш рейтинг, он даже хуже чем у меня. Позор Хабру, он ориентирован на «толпу», если кто не понял, посмотри в иннете закон элитных групп и как наступает вырождение.
Оффтоп, я просто имел неосторожность высказать некоторые соображения по поводу платформы Android. Карма резво слетела куда-то вниз.
А я как препод (http://www.sgu.ru/node/20122) с 20 летним стажем, сказал все что думаю о изучении paskal фирмы Borland и о том, что лучше начинать с haskell, работать на python, а для продвинутых C++.

Но я суда шел чисто из аудитории, может кто задумается, о чем я говорю. Моя задача проста, из этого болота хоть кого то выдернуть.
Запрещена перегрузка оператора возведения в степень (как в Fortran) "**".

В С++ нет оператора возведения в степень
В продолженtи, также запрещена перегрузка терарного ?: и бинарного,
Нет никаких проблем с тем, чтобы перегрузить оператор «запятая» в виде функции-не-члена класса.

#include <iostream>

class A {};

const A& operator, (const A& a, int i)
{
    cout << i << " ";
    return a;
}

int main()
{
    A a;
    a, 1, 2, 3;
}


Все это верно, если я правильно понял значение «бинарного,».
Но на самом деле, в этом случае произойдет вызов конструктора в первой строке, далее вызов конструктора копирования, который скопирует объект, а далее, при раскрутке стека вызовется деструктор

В этом, частном, примере сработает NRVO, и все будет не так как Вы описали.
Для C++11, как Вы правильно заметили, это тоже будет несколько отличаться при наличии move ctor
Кроме. и .* нельзя также перегружать операторы :: и ?:
так как :: и ?: перегружать вообще бессмысленно.
Ещё бы статью про использование встроенных целых типов, например. А то ни в одной книжке по С++ ничего про них нет.
А что вы хотите видеть в статье про использование встроенных целых типов, чего нет в книжках? Там вроде всё тривиально.
Вот именно, что это есть в любой книжке для начинающих. И про операторы тоже.
Если оператор должен возвращать новое значение, то необходимо создавать новый объект (как в случае бинарного плюса). Если вы хотите запретить изменение объекта как l-value, то нужно возвращать его константным.


Если не секрет, как вы предлагаете возвращать константный объект?

Я это к тому, что если как в
const Integer Integer::operator-(const Integer& i) ;


то номер не пройдёт, т.к. при возврате по значению сохраняется семантика копирования объекта.
Речь идет не о семантике копирования, а о семантике операторов вообще.
К примеру, если существует перегрузка Integer Integer::operator-(const Integer& i), возвращающая неконстантный объект, то становится возможным существование следующего кода:

Integer a = 0, b = 1;
a — b = 2;
// или запись вот таких забавных уравнений:
Integer x = 1;
x — 1 = 0;

Cо встроенным int такое, конечно, не работает.
то становится возможным существование следующего кода:

Не становится.

3.10 Lvalues and rvalues
5 The result of calling a function that does not return a reference is an rvalue. User defined operators are
functions, and whether such operators expect or yield lvalues is determined by their parameter and return
types.

Убедитесь сами:

class Integer
{
private:
    int value;
public:
    Integer(int i): value(i) 
    {}
    friend const Integer operator+(const Integer& left, const Integer& right);

    friend Integer& operator+=(Integer& left, const Integer& right);

    Integer operator-(const Integer& i) { value= -value; return *this; }

    friend bool operator==(const Integer& left, const Integer& right);
};

const Integer operator+(const Integer& left, const Integer& right) {
    return Integer(left.value + right.value);
}

Integer& operator+=(Integer& left, const Integer& right) {
    left.value += right.value;
    return left;
}

bool operator==(const Integer& left, const Integer& right) {
    return left.value == right.value;
}

int main ()
{
    Integer i = 0, j = 1;
    i - j = 0;
    i - 1 = 0;
}
Спасибо. Я считал, что это противоречит стандарту, но тут видимо есть какие-то правила мне не известные…
Вы уж извините, но я не понимаю смысла данной статьи на хабре, мы это на первом курсе делали. Если автор хотел показать что-то особенное, то я этого к сожалению не увидел.
Вышлите, пожалуйста, полный список того, что вы делали на первом курсе, чтобы впредь хабраавторы точно знали, какие темы затрагивать не нужно.
Говоря, что мы подобное делали на первом курсе, я хотел показать то, что задача тривиальна, и я полагал, что на Хабре собрались специалисты более-менее высокого уровня, для которых это не очень актуальный материал. Иначе так можно слиться до уровня форумов для школьников.

P.S. Никого не хочу обидеть, всё моё личное мнение, автор в любом случае молодец, потому что любая статья — труд.
P.P.S. хотя студенты будут рады статье)
На Хабре собрались в том числе и студенты ВУЗов, в которых уровень преподавания профильных предметов ниже (или просто по-другому составлен учебный план), чем в том, который заканчивали вы, а также изрядное количество школьников, которые даже до первого курса ВУЗа ещё не доросли, а также изрядное количество самоучек, осваивающих программирование (и C++ в частности) самостоятельно, поэтому хорошее и грамотное освещение подобной темы точно не повредит.

По крайней мере данная статья уж точно более полезна, чем очередное «мегафон ворует деньги» или «мы снова нашли стопицот ошибок копи-пэйста в чьём-то C++-коде».
Ну если так, то вы безусловно правы.
Соглашусь с вами частично: если бы статья на живом примере была показана, а не на абстрактном Integer, который ни для чего, кроме усложнения понимания не нужен.
«фиктивный параметр int в постфиксной версии» вообще-то не такой уж и фиктивный. его можно использовать. Пример из MSDN:
class Int
{
public:
    Int &operator++( int n );
private:
    int _i;
};

Int& Int::operator++( int n )
{
    if( n != 0 )    // Handle case where an argument is passed.
        _i += n;
    else
        _i++;       // Handle case where no argument is passed.
    return *this;
}
int main()
{
   Int i;
   i.operator++( 25 ); // Increment by 25.
}
Конечно можно. Как говорится в статье, перегрузка операторов — это всего лишь более удобный способ вызова функций.
Почему для запятой тяжело придумать оператор? Вот, только что пригодилось.
Есть класс «точка на плоскости» — class Point. Нужно определить ее координаты:
Point &operator= (const long double X) { x = X; return *this; } void operator, (const long double Y) { y = Y; }

В итоге потом использую очень удобную конструкцию:
Point pt; // ... pt = 7, 8;
Полезная статья. Плюсанул бы, да поезд ушёл :)
«Честно говоря, не знаю, какая ситуация актуальна для C++11, все рассуждения далее справедливы для C++98» —
поправьте меня если я не прав, но ситуация

Integer temp(left.value + right.value);
return temp;

в с++ до 11 стандарта во всех компиляторах (кроме написанных пьяными студентами за ночь) сработает return value optimization,
в с++ 11 сработает move semantic.
Sign up to leave a comment.

Articles