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

В этом и заключается вопрос: существует ли «правильный» способ реализации 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 ]

106 голосов
/ 26 октября 2008

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

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

Оператор:

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

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

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

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

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

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

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

#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;
}
52 голосов
/ 25 октября 2008

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

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

30 голосов
/ 26 октября 2008

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

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

В некоторых случаях, таких как потоки 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 ++.

Для базовых базовых_потоков и базовых_потоков

Обратите внимание, что это случай потоков, так как вы не можете изменить поток 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).

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

10 голосов
/ 25 октября 2008

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

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

6 голосов
/ 25 октября 2008

Подпись:

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

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

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

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

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

2 голосов
/ 02 июня 2017

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

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

0 голосов
/ 01 февраля 2016

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

friend std::ostream& operator<<(std::ostream& os, const Object& object) {
    os << object._atribute1 << " " << object._atribute2 << " " << atribute._atribute3 << std::endl;
    return os;
}
0 голосов
/ 20 января 2012

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 Привет 100 Привет Нажмите любую клавишу, чтобы продолжить ...

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...