Друг против функций-членов в Перегрузке Оператора C++

Ранее я узнал о перегрузке операторов в C++ как функций-членов, а также функций-друзей класса. Хотя я знаю, как перегрузить операторы в C++, используя оба метода. Но я все еще смущен, что ** какой лучше **? Функция-член или функция-друг для перегрузки операторов, какую из них использовать и почему? Пожалуйста, ведите меня! Ваш ответ будет высоко оценен. Я буду рад и благодарен за ваши ответы.

4 ответа

Выбор не "участник или друг", но "участник или не участник".
(Дружба часто используется слишком часто, и в школах она преподается слишком рано.)

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

Например:

class A
{
public:
    explicit A(int y) : x(y) {}
    A plus(const A& y) const { return A{x + y.x}; }
private:
    int x;
};

A operator+(const A& lhs, const A& rhs) { return lhs.plus(rhs); }

Что касается того, как выбрать: если оператор не принимает экземпляр класса в качестве своего левого операнда, он должен быть свободной функцией, в противном случае это в значительной степени вопрос личного вкуса (или стандартов кодирования, если вы не в одиночестве).

Пример:

// Can't be a member because the int is on the left.
A operator+ (int x, const A& a) { return A{x} + a; }

Для операторов, которые имеют соответствующий оператор мутирования (например, + а также +=), оператор мутирования обычно является членом, а другой - не членом:

class B
{
public:
    explicit B(int y) : x(y) {}
    B& operator+= (const B& y) { x += y.x; return *this; }
private:
    int x;
};

B operator+(B lhs, const B& rhs) { return lhs += rhs; }

но, конечно, это тоже можно разобрать:

class C
{
public:
    explicit C(int y) : x(y) {}
    C& add(const C& y) { x += y.x; return *this; }
private:
    int x;
};

C& operator+=(C& lhs, const C& rhs) { return lhs.add(rhs); }
C operator+(C lhs, const C& rhs) { return lhs += rhs; }

Функции-члены и друг не являются двумя противоположностями, среди которых вам нужно выбрать только одну. Они оба могут быть использованы, например, чтобы избежать дублирования кода путем реализации одного, а другой вызовет его.

struct A
{
    A operator+=(A const & second); 
};

A operator+(A const &first, A const &second)
{
    A temp(first);
    temp += second;
    return temp;
}

Но в случае, если вы хотите выбрать одну функцию, если она должна быть членом или нет, вот правило, которому я следую: реализовать функцию, не являющуюся членом (friend или нет), когда у меня есть бинарный оператор, чтобы уважать симметрию, которую мы ожидаем. Пример: с A и возможность конвертировать int в A неявно (с конструктором, принимающим int), если у меня есть функция-член, то каков будет результат.

A a1, a2; 
a1 + a2; // OK 
a1 + 42; // OK
42 + a2; // KO

С бесплатной функцией, есть результат.

A a1, a2; 
a1 + a2; // Ok 
a1 + 42; // Ok 
42 + a2; // Ok

Конкретный класс C++, использующий эту возможность: std::string,

#include <iostream>
#include <string>

int main()
{
    std::string s {"World"};
    // Works with absolutely no problem.
    std::string chaine = "Hello " + s;
    std::cout << chaine << std::endl;
}

В заключение, вот хорошая ссылка на SO, чтобы помочь вам.

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

Причиной предпочтения свободных функций для перегрузки операторов было бы поддержание класса как можно более скудным (используйте Friend только при необходимости).

В некоторых случаях операторы не могут быть членами, поэтому в этом случае нет обсуждения. В основном функции-члены имеют экземпляр класса в качестве первого параметра, что не всегда возможно (например, оператор<< для ввода-вывода).

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

Когда оператор принимает в качестве аргументов только члены класса, который вы проектируете, инкапсуляция голосует за использование функции-члена. Пример: добавление двух объектов:

class A {
    ...
    A operator + (const class A& other);   // naturally a member function
    ...
};

Напротив, когда член класса, который вы пишете, является вторым аргументом оператора, вы можете использовать только функцию друга

std::outstream& operator << (std::outstream& out, const class A& a);

class A {
    ...
    friend std::outstream& operator << (std::outstream& out, const class A& a); // must be friend here
};

Самое большее, он может быть членом std::outstream но A класс не существовал, когда std::outstream класс был создан в стандартной библиотеке C++...

Другие вопросы по тегам