Должен ли оператор << быть реализован как друг или как функция-член?

Это в основном вопрос, есть ли "правильный" способ реализации operator<<? Читая это, я вижу, что-то вроде:

friend bool operator<<(obj const& lhs, obj const& rhs);

предпочтительнее чего-то вроде

ostream& operator<<(obj const& rhs);

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

Мой личный случай:

friend ostream & operator<<(ostream &os, const Paragraph& p) {
    return os << p.to_str();
}

Но я мог бы, вероятно, сделать:

ostream & operator<<(ostream &os) {
    return os << paragraph;
}

На каком обосновании я должен основывать это решение?

Примечание:

 Paragraph::to_str = (return paragraph) 

где параграф это строка.

8 ответов

Решение

Проблема здесь в вашей интерпретации статьи, на которую вы ссылаетесь.

Эта статья о ком-то, у кого есть проблемы с правильным определением операторов отношений bool.

Оператор:

  • Равенство == и!=
  • Отношения < > <= >=

Эти операторы должны возвращать bool, поскольку они сравнивают два объекта одного типа. Обычно легче определить эти операторы как часть класса. Это связано с тем, что класс автоматически становится другом самого себя, поэтому объекты типа Paragraph могут проверять друг друга (даже частные члены друг друга).

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

Операторы потока:

  • оператор << выход
  • оператор >> ввод

Когда вы используете их как операторы потока (а не двоичный сдвиг), первый параметр - это поток. Поскольку у вас нет доступа к объекту потока (это не ваше изменение), они не могут быть операторами-членами, они должны быть внешними по отношению к классу. Таким образом, они должны быть друзьями класса или иметь доступ к общедоступному методу, который будет выполнять потоковую передачу за вас.

Также традиционно эти объекты возвращают ссылку на объект потока, так что вы можете объединить операции потока вместе.

#include <iostream>

class Paragraph
{
    public:
        explicit Paragraph(std::string const& init)
            :m_para(init)
        {}

        std::string const&  to_str() const
        {
            return m_para;
        }

        bool operator==(Paragraph const& rhs) const
        {
            return m_para == rhs.m_para;
        }
        bool operator!=(Paragraph const& rhs) const
        {
            // Define != operator in terms of the == operator
            return !(this->operator==(rhs));
        }
        bool operator<(Paragraph const& rhs) const
        {
            return  m_para < rhs.m_para;
        }
    private:
        friend std::ostream & operator<<(std::ostream &os, const Paragraph& p);
        std::string     m_para;
};

std::ostream & operator<<(std::ostream &os, const Paragraph& p)
{
    return os << p.to_str();
}


int main()
{
    Paragraph   p("Plop");
    Paragraph   q(p);

    std::cout << p << std::endl << (p == q) << std::endl;
}

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

Не могли бы вы сделать это как бесплатную функцию без friendИнг это? Это то, что я предпочитаю, потому что дает понять, что это интеграция с ostream, а не основная функциональность вашего класса.

Если возможно, как функции, не являющиеся членами, так и не являющиеся друзьями.

Как описано Хербом Саттером и Скоттом Мейерсом, предпочитайте функции, не являющиеся друзьями, а не функции-члены, чтобы помочь увеличить инкапсуляцию.

В некоторых случаях, таких как потоки C++, у вас не будет выбора, и вы должны использовать функции, не являющиеся членами.

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

Об операторе << и >> прототипы

Я считаю, что примеры, которые вы привели в своем вопросе, неверны. Например;

ostream & operator<<(ostream &os) {
    return os << paragraph;
}

Я даже не могу начать думать, как этот метод может работать в потоке.

Вот два способа реализации операторов << и >>.

Допустим, вы хотите использовать потоковый объект типа T.

И что вы хотите извлечь / вставить из / в T соответствующие данные вашего объекта типа Paragraph.

Общие операторные << и >> прототипы функций

Первое существо как функции:

// T << Paragraph
T & operator << (T & p_oOutputStream, const Paragraph & p_oParagraph)
{
   // do the insertion of p_oParagraph
   return p_oOutputStream ;
}

// T >> Paragraph
T & operator >> (T & p_oInputStream, const Paragraph & p_oParagraph)
{
   // do the extraction of p_oParagraph
   return p_oInputStream ;
}

Общий оператор << и >> прототипы метода

Второе существо как методы:

// T << Paragraph
T & T::operator << (const Paragraph & p_oParagraph)
{
   // do the insertion of p_oParagraph
   return *this ;
}

// T >> Paragraph
T & T::operator >> (const Paragraph & p_oParagraph)
{
   // do the extraction of p_oParagraph
   return *this ;
}

Обратите внимание, что для использования этой нотации вы должны расширить объявление класса T. Для объектов STL это невозможно (вы не должны изменять их...).

А что, если T - поток C++?

Вот прототипы тех же операторов << и >> для потоков C++.

Для базовых basic_istream и basic_ostream

Обратите внимание, что это случай потоков, так как вы не можете изменить поток C++, вы должны реализовать функции. Что означает что-то вроде:

// OUTPUT << Paragraph
template <typename charT, typename traits>
std::basic_ostream<charT,traits> & operator << (std::basic_ostream<charT,traits> & p_oOutputStream, const Paragraph & p_oParagraph)
{
   // do the insertion of p_oParagraph
   return p_oOutputStream ;
}

// INPUT >> Paragraph
template <typename charT, typename traits>
std::basic_istream<charT,traits> & operator >> (std::basic_istream<charT,traits> & p_oInputStream, const CMyObject & p_oParagraph)
{
   // do the extract of p_oParagraph
   return p_oInputStream ;
}

Для чар istream и ostream

Следующий код будет работать только для потоков на основе символов.

// OUTPUT << A
std::ostream & operator << (std::ostream & p_oOutputStream, const Paragraph & p_oParagraph)
{
   // do the insertion of p_oParagraph
   return p_oOutputStream ;
}

// INPUT >> A
std::istream & operator >> (std::istream & p_oInputStream, const Paragraph & p_oParagraph)
{
   // do the extract of p_oParagraph
   return p_oInputStream ;
}

Рис Улерих прокомментировал тот факт, что код на основе символов является всего лишь "специализацией" общего кода над ним. Конечно, Рис прав: я не рекомендую использовать пример на основе символов. Это только дано здесь, потому что это легче читать. Поскольку это возможно только в том случае, если вы работаете только с потоками на основе символов, вы должны избегать этого на платформах, где распространен код wchar_t (например, в Windows).

Надеюсь, это поможет.

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

На самом деле я собирал все эти функции без выходного вывода в заголовочный файл и файл реализации "ostreamhelpers", он хранит эту вторичную функциональность далеко от реальной цели классов.

Подпись:

bool operator<<(const obj&, const obj&);

Кажется довольно подозрительным, это не соответствует stream соглашение или побитовое соглашение, так что это выглядит как случай злоупотребления перегрузкой оператора, operator < должен вернуться bool но operator << должно, вероятно, вернуть что-то еще.

Если вы имели в виду так сказать:

ostream& operator<<(ostream&, const obj&); 

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

Просто для завершения, я хотел бы добавить, что вы действительно можете создать оператора ostream& operator << (ostream& os) внутри класса, и это может работать. Из того, что я знаю, это не очень хорошая идея, потому что это очень запутанно и не интуитивно понятно.

Давайте предположим, что у нас есть этот код:

#include <iostream>
#include <string>

using namespace std;

struct Widget
{
    string name;

    Widget(string _name) : name(_name) {}

    ostream& operator << (ostream& os)
    {
        return os << name;
    }
};

int main()
{
    Widget w1("w1");
    Widget w2("w2");

    // These two won't work
    {
        // Error: operand types are std::ostream << std::ostream
        // cout << w1.operator<<(cout) << '\n';

        // Error: operand types are std::ostream << Widget
        // cout << w1 << '\n';
    }

    // However these two work
    {
        w1 << cout << '\n';

        // Call to w1.operator<<(cout) returns a reference to ostream&
        w2 << w1.operator<<(cout) << '\n';
    }

    return 0;
}

Подводя итог - вы можете сделать это, но, скорее всего, не стоит:)

operator<< реализована как функция друга:

#include <iostream>
#include <string>
using namespace std;

class Samp
{
public:
    int ID;
    string strName; 
    friend std::ostream& operator<<(std::ostream &os, const Samp& obj);
};
 std::ostream& operator<<(std::ostream &os, const Samp& obj)
    {
        os << obj.ID<< “ ” << obj.strName;
        return os;
    }

int main()
{
   Samp obj, obj1;
    obj.ID = 100;
    obj.strName = "Hello";
    obj1=obj;
    cout << obj <<endl<< obj1;

} 

ВЫХОД: 100 Hello 100 Hello Нажмите любую клавишу, чтобы продолжить...

Это может быть функция друга только потому, что объект находится на правой стороне operator<< и аргумент cout находится на левой стороне. Так что это не может быть функцией-членом класса, это может быть только функция-друг.

Друг оператор = равные права как класс

friend std::ostream& operator<<(std::ostream& os, const Object& object) {
    os << object._atribute1 << " " << object._atribute2 << " " << atribute._atribute3 << std::endl;
    return os;
}
Другие вопросы по тегам