Здравствуйте, Hоmunculus, Вы писали:
H>Как считаете — friend это костыль для кривой архитектуры или норм?
Иными словами, потребность/возможность локализовать видимость внутри группы связанных, но не родственных, классов (а не глобально/локально/в пределах файла) — это костыль для кривой архитектуры или норм?
Вот, C++ дал тебе дополнительную возможность управления областью видимости. Вопрос в том, как ты ей воспользуешься. Любым другим способом можно распорядиться разумно или безумно, так вот и этим.
Ну смотри. Есть какое-то приватное поле. Никто не должен ничего о нем знать. Вот никто. Это суть этого поля. Оно просто как-то там работает. А тут класс говорит — вот это мой друг. Все. Друг может ВСЕ!!! Это правильно?
Это все равно что, ну например, кишки и зубы. Это приватные поля человека. Никого нельзя пускать то трогать. Но ты приходишь к зубному и разрешаешь ему доступ к зубам. При этом бац!!! Он может вполне что-то сделать с твоими кишками. Это как? Норм?
Наверное более правильно уж если и разрешать, то не всегда и не ко всему. Нет?
Здравствуйте, Hоmunculus, Вы писали:
H>Здравствуйте, Pzz, Вы писали:
H>Ну смотри. Есть какое-то приватное поле. Никто не должен ничего о нем знать. Вот никто. Это суть этого поля. Оно просто как-то там работает. А тут класс говорит — вот это мой друг. Все. Друг может ВСЕ!!! Это правильно?
H>Это все равно что, ну например, кишки и зубы. Это приватные поля человека. Никого нельзя пускать то трогать. Но ты приходишь к зубному и разрешаешь ему доступ к зубам. При этом бац!!! Он может вполне что-то сделать с твоими кишками. Это как? Норм?
H>Наверное более правильно уж если и разрешать, то не всегда и не ко всему. Нет?
Объявление друга происходит в самом классе, т.е. нельзя написать класс и объявить его другом другого класса, т.е. инкапсуляция не нарушается. Другое дело, что это делает класс "кособоким"
Здравствуйте, Hоmunculus, Вы писали:
H>Ну смотри. Есть какое-то приватное поле. Никто не должен ничего о нем знать. Вот никто. Это суть этого поля. Оно просто как-то там работает. А тут класс говорит — вот это мой друг. Все. Друг может ВСЕ!!! Это правильно?
Вот смотри, есть два класса. Которые, так уж получилось, выниждены знать про внутреннее устройства друг друга.
Слово friend позволяет выразить эту мысль, не делая поля и методы, через которые эти классы взаимодействуют, публичным достоянием.
Строгое ООП, это, наверное, хорошо. Но реальная жизнь не всегда в него укладывается.
H>Это все равно что, ну например, кишки и зубы. Это приватные поля человека. Никого нельзя пускать то трогать. Но ты приходишь к зубному и разрешаешь ему доступ к зубам. При этом бац!!! Он может вполне что-то сделать с твоими кишками. Это как? Норм?
Ну вот как раз friend позволяет объявить зубного другом зубам, но не кишкам.
H>Наверное более правильно уж если и разрешать, то не всегда и не ко всему. Нет?
Ну так оно так и есть. Класс может назначить другую функцию или другой класс своим другом — позволив ему копаться в своих приватных методах и полях. Но именно ему, а не всем подряд.
Здравствуйте, Hоmunculus, Вы писали:
H>Это все равно что, ну например, кишки и зубы. Это приватные поля человека. Никого нельзя пускать то трогать. Но ты приходишь к зубному и разрешаешь ему доступ к зубам. При этом бац!!! Он может вполне что-то сделать с твоими кишками. Это как? Норм?
Здравствуйте, Marty, Вы писали:
M>Здравствуйте, Hоmunculus, Вы писали:
H>>Это все равно что, ну например, кишки и зубы. Это приватные поля человека. Никого нельзя пускать то трогать. Но ты приходишь к зубному и разрешаешь ему доступ к зубам. При этом бац!!! Он может вполне что-то сделать с твоими кишками. Это как? Норм?
M>Хирург иногда делает что-то подобное
Здравствуйте, Hоmunculus, Вы писали:
H>>>Это все равно что, ну например, кишки и зубы. Это приватные поля человека. Никого нельзя пускать то трогать. Но ты приходишь к зубному и разрешаешь ему доступ к зубам. При этом бац!!! Он может вполне что-то сделать с твоими кишками. Это как? Норм?
M>>Хирург иногда делает что-то подобное
H>Ты не понял мою мысль про зубного? Ну ок.
Здравствуйте, Hоmunculus, Вы писали:
H>Как считаете — friend это костыль для кривой архитектуры или норм?
Само по себе нет, зависит от ситуации. В качестве классического примера можно привести определённые пользователем операторы ввода/вывода. Можно конечно, определять в классах функции-члены типа read/write, save/load и т.п и через них определять операторы, но зачем же плодить лишние сущности.
А вот когда на этапе проектирования что-то провтыкали, а потом начинают латать эти косяки добавлением друзей, вот это уже костыли.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, Hоmunculus.
Давно подумаю над тем, что зону действия friend-деклараций стоило ограничить одной private-protected секцией. Чтобы можно было явно указать что именно и кому ты открываешь.
Здравствуйте, Hоmunculus, Вы писали:
H>Как считаете — friend это костыль для кривой архитектуры или норм?
Это очень полезный костыль, если поверх существующего кода резко понадобилось добавить, например, сбор статистики или мониторинг валидности состояния объектов не затрагивая имеющийся отлаженный код и его поведение.
Здравствуйте, Went, Вы писали:
W>Давно подумаю над тем, что зону действия friend-деклараций стоило ограничить одной private-protected секцией. Чтобы можно было явно указать что именно и кому ты открываешь.
Не думаю, что это хорошая идея. По-моему, это будет способствовать как раз той самой нездоровой дружественности при которой возникают избыточные и зацикленные зависимости между классами. Одно дело наделить дружественностью функцию/оператор, которая является неотъемлемой частью контракта класса и объявлена в одном с классом заголовке. И совсем другое дело — плести замысловатые кружева зависимостей между классами.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали:
R>Не думаю, что это хорошая идея. По-моему, это будет способствовать как раз той самой нездоровой дружественности при которой возникают избыточные и зацикленные зависимости между классами. Одно дело наделить дружественностью функцию/оператор, которая является неотъемлемой частью контракта класса и объявлена в одном с классом заголовке. И совсем другое дело — плести замысловатые кружева зависимостей между классами.
Тут не поспоришь. Но, можно рассуждать, что дружественность бывает двух видов: первая, это "дружественность единой сущности", про которую ты написал выше, вторая — "дружественность единой системы". Это когда создаётся некоторая система объектов, и мы хотим открыть пользователю только некоторый интерфейс. Ну, самое простое — у нас есть какая-то "божественная базовая сущность", а она обслуживается разными сервисами. Ну, совсем примитивно — базовый контрол, который может быть рисуем, управляем, наводим мышкой и т.п. Все эти и сервисы, и функции божественного предка, которыми они оперируют, пользователю не нужны. Ну, чтобы он, например, не додумался вызывать контролу "нарисуйся" вручную. И очень хочется сказать "вот у нас набор приватных функций рисования, открытых только для рисователя", например. В C# есть для этого (приблизительно) модификатор internal. Я не хочу сейчас разводить дискуссию о чистоте и идеологической трушности такого подхода, понятно, можно как-то обойтись, заменить на защищенные интерфейсы или композицию объектов. Но вот так просто, без изысков это было бы удобно, мне кажется.
Здравствуйте, Hоmunculus, Вы писали:
H>Как считаете — friend это костыль для кривой архитектуры или норм?
friend для лохов, нормальные пацаны используют public.
Вообще не видел когда применяли криво. Для реализаций оператора сравнения и std::hash использование более чем оправдано.
Случаи, когда из одного класса нужно получить потроха другого можно назвать костылём, но как выше уже сказал, нормальные пацанчики обходят это обычным public. Если кто-то написал friend, уже повод задуматься о личности написавшего и его внутреннем мире. Может он маньяк и обзывать его код костылём себе дороже.
Здравствуйте, Hоmunculus, Вы писали:
H>Как считаете — friend это костыль для кривой архитектуры или норм?
В прямых руках норм. В конце-концов, при помощи friend можно не только наивно открыть доступ ко всем своим потрохам. Например, для friend придумали что-то вроде инверсии зависимости в виде паттерна Passkey, при помощи которого можно разрешать вызывать одну конкретную функцию классам из белого списка. Что-то вроде:
class Me {
public:
struct Passkey {
friend class Class1;
friend class Class2;
private:
Passkey() = default; // Доступен только друзьям
};
void SomeFunc(Passkey);
};
class Class1 {
public:
void CallMe() {
Me me;
me.SomeFunc(Me::Passkey{});
}
};
Здравствуйте, Hоmunculus, Вы писали:
H>Ну смотри. Есть какое-то приватное поле. Никто не должен ничего о нем знать. Вот никто. Это суть этого поля. Оно просто как-то там работает. А тут класс говорит — вот это мой друг. Все. Друг может ВСЕ!!! Это правильно?
Инкапсуляция — это не нечто самоценное само по себе. Инкапсуляция — это механизм для упрощения программирования, когда одна часть кода изолируется от другой, исключая тем самым необдуманное влияние одного кода на другой. Второй аспект инкапсуляции — это возможность переиспользовать один и тот же класс в различных независимых частях кода, в какой-то степени обеспечивая его универсальность. Если мы объявляем другом класса некий элемент, то получаем два следствия: мы усложняем код увеличивая его связность и уменьшаем универсальность. Чтобы оценить оправданность добавления друга надо посмотреть на объём кода, который придётся написать, чтобы сделать тоже самое без объявления дружбы. Может оказаться, что трудозатраты на работу без дружбы столь значительны, что не имеют смысла. Однако, также следует иметь ввиду, что тут есть классическая дилемма: выиграв на короткой дистанции можно проиграть на длинной и наоборот. Понятно, что для простых проектов (которые могут быть разработаны и написаны одним человеком) в дружбе нет особой опасности. Если же речь идёт о проекте рассчитанным на десятилетия и десятки сменяющихся программистов, то излишняя дружба ведёт к проблемам.
Думаю, что здесь следует заметить, что, в отличии от вышеприведённого случая, дружба может использоваться для ограничения доступа и увеличению инкапсуляции.
Ниже я попытался проиллюстрировать, как это может быть.
// the data that can be created only by TOwner class
//template<class TOwner, class TData>
class PrivateData
{
private:
friend TOwner;
using value_type = TData;
private:
PrivateData(const value_type& constData)
: m_data{constData}
{
}
public:
bool operator == (const PrivateData& constData) const
{
return m_data == constData.m_data;
}
bool operator != (const PrivateData& constData) const
{
return m_data != constData.m_data;
}
private:
value_type m_data{};
};
Здравствуйте, Hоmunculus, Вы писали:
H>Как считаете — friend это костыль для кривой архитектуры или норм?
Да, считаю подобные заморачивания лишним пложением сущностей. Вообще использую структуры, чтобы не писать public.
А если хочется скрыть члены класса, делаю это через наследование.
И опять же все public, на случай дебага удобно бывает временно в лог закинуть что-то из кишок не городя дополнительных членов класса для этого.
Разделение на интерфейс и реализацию должно натурально получаться.
Thing.h:
#ifndef Thing_H
#define Thing_H
#include"Thing_inline.h"struct Thing:Thing_private{
//публичные члены
};
#include"Thing_inline.h"#endif
Thing_inline.h:
#ifndef Thing_private
#define Thing_private
#include"Thing.h"struct Thing_private{
//приватные члены
};
#else
#ifndef Thing_inline
#define Thing_inline
//А сюда инлайн функции, чтоб тоже глаза не мозолили в основном хидере#endif
Здравствуйте, Hоmunculus, Вы писали:
H>Как считаете — friend это костыль для кривой архитектуры или норм?
Но ты ведь понимаешь, что код со всегда открытыми модификаторами доступа будет работать точно так же как с закрытыми, защищёнными и дружественными. То есть если я приду и сотру все твои модификаторы доступа в готовой программе назначив все члены открытыми, то ничего не изменится.
По сути всё это синтаксический сахар для стадии компиляции программы. Это нужно тебе как программисту, а не компьютеру. А теперь задумайся, действительно ли нужны модификаторы доступа, зачем так усложнять? Их же надо ещё продумывать, тратить на это усилия.
Говорят почему что-то сделано так, а не иначе написано в книге "Дизайн и эволюция языка C++. Страуструп Бьерн". Ну я не знаю, не читал и не осуждаю. А нужно ли ООП? А нужно ли то? А нужно ли это?
Я смотрю сейчас модно стало спрашивать всё у нейронок. Это только динозавры пишут сами.
Когда friend — это нормально (обоснованное архитектурное решение)
Перегрузка операторов ввода/вывода (<< и >>): Это самый распространенный пример. Операторы должны иметь доступ к внутренним данным класса, но не являются его членами (они принимают поток в качестве левого операнда), поэтому объявление их friend — стандартная практика.
Тесная взаимосвязь между классами (сильная связность): Иногда набор классов или функций представляет собой единый логический модуль, который просто разбит на части по техническим причинам (например, управление временем жизни объектов). В этом случае предоставление доступа через friend позволяет сохранить инкапсуляцию внутри модуля, не выставляя внутренние детали во внешний публичный интерфейс.
Вспомогательные функции, не требующие публичного API: Для некоторых служебных функций, которым нужен доступ к внутренним данным, но которые не должны быть частью публичного интерфейса класса.
Оптимизация производительности: В некоторых случаях friend-функции могут обеспечить более эффективный доступ к данным, чем использование публичных геттеров/сеттеров, что может быть критично для высокопроизводительных систем.
Когда friend — это "костыль" (признак кривой архитектуры)
Нарушение инкапсуляции без необходимости: Если friend используется просто для удобства доступа к приватным данным из произвольного места программы, когда можно обойтись публичным интерфейсом, это нарушает принципы ООП и усложняет поддержку кода.
Свидетельство плохого разбиения на классы: Чрезмерное использование friend может указывать на то, что логика, которая должна находиться внутри одного класса (или родственных классов), неоправданно разнесена по разным частям системы.
Усложнение отладки и поддержки: Большое количество "дружественных" функций или классов увеличивает зависимости, делая код менее модульным и более трудным для понимания и отладки.
Резюме: friend — это мощный инструмент, который дает разработчику возможность сознательно управлять доступом к приватным членам. Использовать его нужно осознанно и только тогда, когда это действительно оправдано дизайном, а не как обходной путь для решения проблем, вызванных поспешным проектированием. При правильном применении он помогает создать более чистую и эффективную архитектуру.