На одном собеседовании поспорил с собеседующим что "друг" обычно первый признок плохого дизайна. На что мне долго пытались втирать, что это хорошо, а в некоторых случаях необходимо. Нопример оператор вывода в поток (С++).
Интересует ваше мнение. Друг зло или нет? Как часто вам приходилось им пользоваться?
Прошу не сносить в холивар, так как вопрос касается C++.
E_>На одном собеседовании поспорил с собеседующим что "друг" обычно первый признок плохого дизайна. На что мне долго пытались втирать, что это хорошо, а в некоторых случаях необходимо. Нопример оператор вывода в поток (С++).
E_>Интересует ваше мнение. Друг зло или нет? Как часто вам приходилось им пользоваться?
Даже у типичного интроверта-программиста могут быть друзья
Мое мнение, что друзья класса — необходимость, иногда вынужденная (например чтобы разрешить создание объектов только из одного конкретного класса). Другое дело, что дружбой часто злоупотребляют там, где достаточно пары публичных функций. Еще одно мое скромное мнение, что вместо дружбы целыми классами лучше было бы иметь возможность объявлять другом функцию-член класса, что-то типа
friend void some_class::some_method();
Не знаю, есть ли такое предложение в C++11/14
Друзья ввода/вывод в поток в принципе не нужны, если есть такое:
Здравствуйте, Europe_cn, Вы писали:
E_>На одном собеседовании поспорил с собеседующим что "друг" обычно первый признок плохого дизайна. На что мне долго пытались втирать, что это хорошо, а в некоторых случаях необходимо. Нопример оператор вывода в поток (С++). E_>Интересует ваше мнение. Друг зло или нет? Как часто вам приходилось им пользоваться? E_>Прошу не сносить в холивар, так как вопрос касается C++.
1. Статическая дружба statefull классов вредна, т.к. нарушает инкапсуляцию.
2. Дружба класса с функцией сомнительна, но иногда упрощает разработку.
3. Но есть один сценарий, когда дружба крайне полезна. Дело в том, что в C++ друг статического родительского типа достаточен, чтобы делать дружественные вызовы полиморфных наследников. Звучит круто, да? =)
Синтетический пример:
class Parent
{
friend class Family;
protected:
virtual void Answer() = 0;
};
class Child : public Parent
{
private:
void Answer() { printf("Child!\n"); }
int encapsulated;
};
class Family
{
public:
void ParentAnswer(Parent *p) { p->Answer(); } /* ok */#if 0
void ChildAnswer(Child *c) { c->Answer(); } /* error! */int breakEncapsulation(Child *c) { return c->encapsulated; } /* error again! */#endif
};
int test(void)
{
Child c;
Family f;
f.ParentAnswer(&c); /* ok, Child->answer will be called */return 0;
}
Обратите внимание: Child статически не дружит с Family и его инкапсуляция не нарушена. Parent -- stateless там нечего нарушать. Но при этом, Family может вызывать закрытые методы класса Child если они полиморфны в его Parent.
Как мне кажется, ради подобных фишек дружба и оставлена в объектной модели C++. Ну ещё можно вспомнить Barton-Nackman trick, но он уже не актуален.
Здравствуйте, Europe_cn, Вы писали:
E_>На одном собеседовании поспорил с собеседующим что "друг" обычно первый признок плохого дизайна. На что мне долго пытались втирать, что это хорошо, а в некоторых случаях необходимо. Нопример оператор вывода в поток (С++).
E_>Интересует ваше мнение. Друг зло или нет? Как часто вам приходилось им пользоваться?
Пример: Контейнер и итераторы для обхода этого контейнера.
Вполне разумно итераторы сделать "друзьями", ибо все вместе они составляют единую систему.
_____________________
С уважением,
Stanislav V. Zudin
PM>>Не знаю, есть ли такое предложение в C++11/14
L_L>да уж что тут не знать, это есть сто лет в обед, поди с первых версий C-фронта было, никаких "высоко-технологичных" C++11 и не надо.
Спасибо, действительно не знал Но не работает для еще не объявленных целиком классов, а именно такой случай мне реально требуется:
class A;
class B
{
friend A::A(); // error C2027: use of undefined type 'A'private:
B() {}
};
class A
{
A() {}
B b;
};
A a;
И не поспоришь, действительно тип A еще не объявлен.
Здравствуйте, Europe_cn, Вы писали:
E_>Прошу не сносить в холивар, так как вопрос касается C++
Да тут и холиварить-то не о чем. Если человек считает использование goto, friend, dynamic_cast, const_cast, reinterpret_cast, c-style cast, макросов нужное подчеркнуть плохим дизайном по определению, потому что прочитал об этом в каком-то там очередном блоге (со ссылкой в лучшем случае на Дейкстру, где тот говорил о Бейсике), значит он дурак по определению.
Здравствуйте, ioj, Вы писали:
I>>Возможно стоит всегда определять друга вместо члена, если не нужна виртуальность. ioj>нужно быть смелее, и переходить сразу к struct matrix с открытыми полями и набору ф-ций для работы с ней
В class matrix обычно vector<Scalar> storage; + информация о размерностях, которая вместе с размером storage составляют инвариант, который нельзя рушить.
Здравствуйте, artem.komisarenko, Вы писали:
AK>Да тут и холиварить-то не о чем. Если человек считает использование goto, friend, dynamic_cast, const_cast, reinterpret_cast, c-style cast, макросов нужное подчеркнуть плохим дизайном по определению, потому что прочитал об этом в каком-то там очередном блоге (со ссылкой в лучшем случае на Дейкстру, где тот говорил о Бейсике), значит он дурак по определению.
+1
Некоторые даже пытаются через свою фанатичную веру в то что какая-то конструкция является плохой по определению, доказать что другие конструкции которые сводятся к этим, которые "плохие по определению", тоже являются плохими.
Недавно на одном из форумов так в очередной раз пытались "доказать" вредность исключений — типа это аналог goto, соответственно исключения — это "плохо":
> throw is a way to put GOTO in your code, the more you see throw or catch , the more your code is full of flaw.
и соответственно мой ответ:
By using your "GOTO" reasoning we can go even further — we can consider harmful even "if" statements. Because at lowest level "if" statement translates into something like:
if(condition) goto label; // jne, brne, etc
So, by using your conclusions: "the more you see if statement, the more your code is full of flaw" — how funny is that?
EP>В class matrix обычно vector<Scalar> storage; + информация о размерностях, которая вместе с размером storage составляют инвариант, который нельзя рушить.
я не хочу скатываться в холивар а-ля anemic vs rich, просто по мне выбор класс + френды для операций вместо свободных ф-ций и простой структуры выглядит несколько странным.
Здравствуйте, ioj, Вы писали:
EP>>В class matrix обычно vector<Scalar> storage; + информация о размерностях, которая вместе с размером storage составляют инвариант, который нельзя рушить. ioj>я не хочу скатываться в холивар а-ля anemic vs rich, просто по мне выбор класс + френды для операций вместо свободных ф-ций и простой структуры выглядит несколько странным.
Почему? friend'ы класса указываются в нём самом — они точно такие же члены его интерфейса (которые имеют доступ к инвариантам) как и member functions — основное отличие в синтаксисе использования.
Причём синтаксис non-member более общий чем member.
Например можно сделать функцию inverse и для double и для matrix:
double x = ...;
Matrix y = .../
auto z = inverse(value);
auto solution = inverse(y) * b;
While we could make a member function to return length, it is better to make it a global friend function. If we do that, we will be able eventually to define the same function to work on built-in arrays and achieve greater uniformity of design. I made size into a member function in STL in an attempt to please the standard committee. I knew that begin, end and size should be global functions but was not willing to risk another fight with the committee. In general, there were many compromises of which I am ashamed. It would have been harder to succeed without making them, but I still get a metallic taste in my mouth when I encounter all the things that I did wrong while knowing full how to do them right.
Там class fvector_int, у которого данные private, и есть
friend std::size_t size(const fvector_int& x)
То что мы даём доступ к инвариантам класса некоторым функциям через friend — не означает, что мы даём его всем.
Здравствуйте, Europe_cn, Вы писали:
E_>Здравствуйте, artem.komisarenko, Вы писали: E_>Сечёшь разницу между "первый признок плохого дизайна" и плохим дизайном по определению есть?
Лучше расскажи нам про разницу между "первый признок плохого дизайна" и "Друг зло или нет?"
E_>Интересует ваше мнение. Друг зло или нет? Как часто вам приходилось им пользоваться?
Здравствуйте, Europe_cn, Вы писали:
E_>Все конструкции были добавлены в язык по объективным причинам, но не все конструкции нужно использовать при первом удобном случае.
А при втором удобном случае можно?
Где граница?
_____________________
С уважением,
Stanislav V. Zudin
Здравствуйте, Stanislav V. Zudin, Вы писали:
SVZ>Здравствуйте, Europe_cn, Вы писали:
E_>>Все конструкции были добавлены в язык по объективным причинам, но не все конструкции нужно использовать при первом удобном случае.
SVZ>А при втором удобном случае можно? SVZ>Где граница?
Границы нет по определению. Главное чтобы аргументация была. Одну и ту же фичу можно реализовать по разному. И стоимость поддержки будет тоже разная.
Весь код тоже можно в main запихнуть.