Почему эта ‹‹ перегрузка не компилируется

Я не могу понять, почему следующий код не компилируется. Синтаксис такой же, как у других моих перегруженных операторов. Есть ли ограничение на то, что перегрузка ‹‹ должна быть добавлена ​​в друзья? Если да, то почему? Спасибо за любую помощь.

Это не работает -

#include "stdafx.h"
#include <iostream>
#include <fstream>
#include <string>

class Test
{
 public:
explicit Test(int var):
    m_Var(var)
    {   }

    std::ostream& operator<< (std::ostream& stream)
    {
        return stream << m_Var;
    }
 private:
int m_Var;

 };

 int _tmain(int argc, _TCHAR* argv[])
 {
Test temp(5);

std::cout << temp;

return 0;
}

Это работает -

#include "stdafx.h"
#include <iostream>
#include <fstream>
#include <string>

class Test
{
public:
explicit Test(int var):
    m_Var(var)
    {   }

    friend std::ostream& operator<< (std::ostream& stream, Test& temp);

private:
    int m_Var;

 };

 std::ostream& operator<< (std::ostream& stream, Test& temp)
 {
return stream << temp.m_Var;
 };

 int _tmain(int argc, _TCHAR* argv[])
 {
Test temp(5);

std::cout << temp;

return 0;
 }

person Steve    schedule 22.01.2010    source источник
comment
это, конечно, многократный обман - см. function" title="следует ли оператор быть реализованным как друг или как функция-член"> stackoverflow.com/questions/236801/, например   -  person    schedule 22.01.2010
comment
@ Нил, я долго искал ответ на этот вопрос в Google и не смог его найти. Мне было не интересно, как реализовать это с другом, но почему это нельзя было реализовать первым способом.   -  person Steve    schedule 22.01.2010


Ответы (6)


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

Возьмите этот код:

   struct Gizmo
    {
        ostream& operator<<(ostream& os) const
        {
            os << 42;
        }
    };


    int main()
    {
        Gizmo g;
        cout << g;
        return 0;
    }

Рассмотрим контекст вызова cout << g;. Когда компилятор компилирует эту функцию, он сначала пробует следующее:

cout.operator<<(g);

... и если это не найдено, то оно ищет в глобальном пространстве имен:

operator<<(cout, g);

... и если это не найдено, то его нельзя скомпилировать.

Но когда вы пытаетесь реализовать оператор вставки потока как член Gizmo, вы надеетесь, что компилятор разрешит ваш код так:

g.operator<<(cout);

... чего он не может сделать, если вы не измените свой код на:

g << cout;

... что, очевидно, не то, что вы собираетесь.

person John Dibling    schedule 22.01.2010

Это не ограничение, что любой оператор должен быть "friended". Проблема в том, что если вы объявляете оператор как метод экземпляра, первый аргумент всегда должен быть типом самого класса. В этом случае вам нужно, чтобы первый параметр оператора (слева) имел тип std::ostream&, поэтому вы не можете использовать метод экземпляра для его перегрузки и вам придется использовать глобальную функцию.

Кстати, совсем не обязательно, чтобы операторы, объявленные как отдельные функции, объявлялись и как friend функции. Они могут счастливо работать, не будучи friend классом, пока они имеют доступ только к public членам своих аргументов.

person mmx    schedule 22.01.2010
comment
Я знаю, что о других операторах, я не знал, что для операторов потоковой передачи существуют особые правила. - person Steve; 22.01.2010
comment
@Steve: В << и >> нет ничего особенного. По сути, это операторы побитового сдвига, перегруженные для потоковых классов в библиотеке C++. - person mmx; 22.01.2010

Поскольку первая форма перегружает temp << std::cout.

person kennytm    schedule 22.01.2010

Когда вы делаете std::cout << temp;, это означает, что вы применяете оператор << к std::cout (поскольку операторы остаются ассоциативными). Если вы хотите написать оператор, который является функцией-членом, для достижения этого вам придется перегрузить оператор << для любого класса, к которому принадлежит std::cout, что невозможно, поскольку это то, что вы не можете изменить.

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

std::ostream& operator<< (std::ostream& stream, Test& temp)

Теперь вы можете сделать его другом или нет. Если вы не сделаете его другом, вам придется предоставить функции получения (например, getMVar ), которые предоставят вам значение членов класса Test. Однако это не очень хороший подход. Так как это вам не нужно предоставлять функции getter. Таким образом, принято подружиться с такими операторами.

Как указывалось ранее, то, что вы делаете, приведет к тому, что код будет написан как temp << std::cout, что явно не то, что вам нужно.

person Yogesh Arora    schedule 22.01.2010

При реализации в виде функции-члена перегрузки операторов имеют неявный первый параметр, который становится this. Для потоков это не по порядку: поток должен идти первым.

Использование дружественного оператора является коротким, лаконичным и может предотвратить нежелательные неявные преобразования (из-за того, что он используется только через ADL). Если вы хотите определить его вне очереди (например, в файле реализации .cpp), вызовите непубличный и, возможно, виртуальный метод:

struct T {
  template<class Ch, class Tr>
  friend
  std::basic_ostream<Ch, Tr>& operator<<(
    std::basic_ostream<Ch, Tr>& s,
    T const& v
  ) {
    s << v.stuff; // or call v.output(s), etc.
    return s;
  }

private:
  int stuff = 0; // initialization here is c++0x only

  //virtual void output(std::ostream&) const;
  //virtual void output(std::wostream&) const;
  // etc., or make it a template, but if it's short,
  // just put it in the above friend overload
};

Дополнительные баллы: назовите элементы перегрузки оператора, у которых нет this. (Подсказка: они статичны.)

person Community    schedule 22.01.2010

Как подытожил Скотт Мейерс в книге Effective C++ 2nd Edition Пункт 19. Различия между функциями-членами, функциями, не являющимися членами, и дружественными функциями:

оператор>> и оператор‹‹ никогда не являются членами. Если f является оператором>> или оператором‹‹, сделайте f функцией, не являющейся членом. Если, кроме того, f нужен доступ к закрытым членам C, сделайте f другом C

Это утверждение является всего лишь руководством для принятия решения о том, следует ли делать членами operator‹‹ и operator>>. Лучше всего сделать их не членами. Вы можете сделать их членами, если хотите, но если бы вы это сделали, вам пришлось бы написать:

temp << std::cout // when calling operator<<
temp >> std::cin  // when calling operator>>

На самом деле вы можете исправить свой первый фрагмент кода, изменив вызов std::cout на приведенную выше форму. Но такой способ письма определенно не является естественным.

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

person Yukiko    schedule 22.01.2010
comment
ну, это несколько вводит в заблуждение. Они не являются функциями-членами, потому что это запрещено, а не потому, что это плохая идея. Я должен подружиться с ними, чтобы обойти это. - person Steve; 22.01.2010
comment
Не собираюсь на д/в, но это не отвечает на вопрос почему оп‹‹ должен быть другом - person John Dibling; 23.01.2010
comment
Другие ответы, возможно, яснее ... но я добавил некоторые разъяснения того, что это стоит;) - person Yukiko; 27.01.2010