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

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

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) 

где абзац строка.

Ответ 1

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

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

Оператор:

  • Равенство == и!=
  • Связь < > <= > =

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

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

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

  • Оператор < < Выход
  • оператор → ввод

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

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

#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;
}

Ответ 2

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

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

Ответ 3

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

Как описано Herb Sutter и Scott Meyers, предпочитайте функции, не являющиеся членами-членами, для функций-членов, чтобы помочь увеличить инкапсуляцию.

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

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

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

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

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

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

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

Предположим, вы хотите использовать объект типа stream типа 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 - поток С++?

Вот прототипы того же самого < и → для потоков С++.

Для базового элемента basic_istream и basic_ostream

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

// 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 ;
}

Для char istream и ostream

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

// 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 ;
}

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

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

Ответ 4

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

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

Ответ 5

Подпись:

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

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

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

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

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

Ответ 6

Просто ради завершения, я хотел бы добавить, что вы действительно можете создать оператор 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;
}

Итак, чтобы подвести итог - вы можете это сделать, но вы, скорее всего, не должны:)

Ответ 7

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;

} 

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

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

Ответ 8

оператор friend = равные права как класс

friend std::ostream& operator<<(std::ostream& os, const Object& object) {
    os << object._atribute1 << " " << object._atribute2 << " " << atribute._atribute3 << std::endl;
    return os;
}