Здравствуйте, 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{};
};