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

StackOverflow https://stackoverflow.com/questions/236801

  •  04-07-2019
  •  | 
  •  

Вопрос

По сути, это вопрос: существует ли «правильный» способ реализации 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) 

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

Это было полезно?

Решение

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

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

Оператор:

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

Эти операторы должны возвращать логическое значение, поскольку они сравнивают два объекта одного и того же типа.Обычно проще всего определить эти операторы как часть класса.Это связано с тем, что класс автоматически становится другом самого себя, поэтому объекты типа 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 ;
}

Для char 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 , ни побитовым соглашениям, так что похоже на случай злоупотребления перегрузкой операторов, оператор < должен вернуть bool , но оператор < < , вероятно, должен возвращать что-то еще.

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

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

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

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

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

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

#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 Нажмите любую клавишу для продолжения & # 8230;

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

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

friend std::ostream& operator<<(std::ostream& os, const Object& object) {
    os << object._atribute1 << " " << object._atribute2 << " " << atribute._atribute3 << std::endl;
    return os;
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top