Перегрузка оператора сложения

641
30 января 2017, 18:04
class Cat {
private:
    int value = 1;
public:
    Cat(int _value) {
        value = _value;
    }
    operator+(Cat a, Cat b) {
        return new Cat(a.value + b.value);
    }
};

Казалось бы все пишу правильно, сложение двух котов даст нового кота, у которого value будет суммой их value'ов. Но получаю две ошибки.

main.cpp:17:5: error: C++ requires a type specifier for all declarations
    operator+(Cat a, Cat b) {
    ^
main.cpp:18:16: error: cannot initialize return object of type 'int' with an rvalue of type 'Cat *'
        return new Cat(a.value + b.value);
               ^~~~~~~~~~~~~~~~~~~~~~~~~~
2 errors generated.
Answer 1

А кто будет объявлять тип возвращаемого значения?

Cat* operator+(Cat a, Cat b) {
    return new Cat(a.value + b.value);
}

Только это - во-первых, не решение, а во-вторых, плохое решение.

Не решение - потому что вообще оператор + бинарный. Он складывает два значения, а у вас в сложении участвуют три - еще и ваш объект (this).

Cat* operator+(Cat b) {
    return new Cat(this->value + b.value);
}

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

Этого можно избежать, если возвращать готового кота:

Cat operator+(Cat b) {
    return Cat(this->value + b.value);
}

И последнее. Если кот большой (не в смысле значения value, а занимает много памяти) - то имеет смысл передавать его в оператор не по значению, а по ссылке:

Cat operator+(const Cat& b) {
    return Cat(this->value + b.value);
}
Answer 2

Бинарный оператор, такой как, например, оператор сложения operator +должен быть определен либо как нестатическая функция - член класса с одним параметром, либо как функция, которая не является членом класса, с двумя параметрами.

Выданные вам сообщения компилятора

main.cpp:17:5: error: C++ requires a type specifier for all declarations
    operator+(Cat a, Cat b) {
    ^
main.cpp:18:16: error: cannot initialize return object of type 'int' with an rvalue of type 'Cat *'
        return new Cat(a.value + b.value);
               ^~~~~~~~~~~~~~~~~~~~~~~~~~

говорят о том, что у определенного вами оператора отсутствует тип возвращаемого значения.

Тип возвращаемого значения может отсутствовать только у функций преобразования. В случае же оператора сложения вы обязаны указать тип возвращаемого значения.

При сложении двух объектов класса не имеет никакого смысла возвращать указатель на объект. В этому случае вы не сможете связывать операторы сложения в цепочку без применения дополнительных операторов, и, более того, это может привести к утечке памяти.

Оператор должен возвращать сам объект либо с квалификатором const либо без него.

Как уже выше упомянуто, оператор может быть объявлен как нестатическая функция-член класса с одним параметром.

В этом случае оператор operator + может выглядеть так

class Cat 
{
private:
    int value = 1;
public:
    Cat(int _value) 
    {
        value = _value;
    }
    Cat operator +( const Cat &a ) const 
    {
        return Cat( this->value + a.value );
    }
};

либо как

class Cat 
{
private:
    int value = 1;
public:
    Cat(int _value) 
    {
        value = _value;
    }
    const Cat operator +( const Cat &a ) const 
    {
        return Cat( this->value + a.value );
    }
};

Вы могли бы перегрузить оператор также для rvalue-ссылок, как, например,

Cat operator +( const Cat &&a ) const 
{
    return Cat( this->value + a.value );
}

или

Cat operator +( Cat &&a ) const 
{
    return Cat( this->value + a.value );
}

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

Обратите внимание на присутствие квалификатора condt после списка параметров. Это говорит о том, что сам объект, который будет присутствовать в левой части от оператора, изменяться не будет, так же, как и правый объект, так как соответствующий ему параметр оператора определен также с квалификатором const.

Имейте в виду, так как конструктор класса объявлен как преобразующий конструктор, то вы в этом случае можете складывать объекты класса Cat с числами. Например,

#include <iostream>
class Cat 
{
private:
    int value = 1;
public:
    Cat(int _value) 
    {
        value = _value;
    }
    const Cat operator +( const Cat &a ) const 
    {
        return Cat( this->value + a.value );
    }
};
int main() 
{
    Cat c1( 10 );
    c1 + 5.5;
    return 0;
}

Насколько это оправдано - это решать вам, исходя из того, какой смысл вкладывается в этот оператор сложения. Если вы не хотите допускать такого неявного преобразования из числа в объект класса Cat, то вы можете объявить конструктор, как явный. В этом случае программа не будет компилироваться, если имеет попытка сложить объект класса Cat с числом.

#include <iostream>
class Cat 
{
private:
    int value = 1;
public:
    explicit Cat(int _value) 
    {
        value = _value;
    }
    const Cat operator +( const Cat &a ) const 
    {
        return Cat( this->value + a.value );
    }
};
int main() 
{
    Cat c1( 10 );
    c1 + 5.5;
    return 0;
}

Для этой программы компилятор выдаст сообщение об ошибке на подобие следующего

prog.cpp:24:5: error: no match for 'operator+' (operand types are 'Cat' and 'double')
  c1 + 5.5;
     ^

Второй способ объявить этот оператор - это объявить его как функцию, которая не является членом класса. Так как эта функция должна иметь доступ к закрытому члену класса value, то ее нужно будет объявить как дружественную функцию класса.

Саму функцию вы можете определить как внутри определения класса, так и вне его.

Например,

#include <iostream>
class Cat 
{
private:
    int value = 1;
public:
    Cat(int _value) 
    {
        value = _value;
    }
    friend const Cat operator +( const Cat &a, const Cat &b )  
    {
        return Cat( a.value + b.value );
    }
};
int main() 
{
    Cat c1( 10 );
    Cat c2( 5 );
    Cat c3 = c1 + c2;
    return 0;
}

Имейте в виду, что это плохая идея объявлять переменные, начинающиеся с подчеркивания. В общем случае такие имена, начинающиеся с подчеркивания, зарезервированы за компилятором.

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

Cat( int value ) : value( value )
{
}

Если член класса value не может принимать отрицательные значения, то лучше его и соответствующий параметр конструктора объявить, как имеющие тип unsigned int.

Answer 3

Используя friend:

#include <iostream>
class Foo
{
public:
    Foo( int p_a )
        : m_a( p_a )
    {}
    int getA() const { return m_a; }
protected:
    int m_a;
friend Foo operator+( Foo const& lF, Foo const& rF )
{
    return Foo( lF.m_a + rF.m_a );  
}
};

int main() {
    Foo f1( 12 ), f2( 13 ), f4( 14 );
    Foo f3 = f1 + f2 + f4;
    std::cout << f3.getA() << std::endl;
    return 0;
}
READ ALSO
Работа с бинарными (1-bit per pixel) изображениями c++

Работа с бинарными (1-bit per pixel) изображениями c++

Проблема в следующемНеобходимо работать с бинарными изображениями довольно внушительных размеров (100000x100000 пикселей)

345
отсортировать недосортированный файл

отсортировать недосортированный файл

здравствуйте, есть файл, который до этого частично сортировывалсячастично значит файл состоит, допустим, из такого порядка чисел: 1 2 3 3 5 6 6

319