По меньшей мере, это немного эзотерично, поскольку вы уже осознали, что я мог бы почесать голову на мгновение, когда я впервые столкнулся с вашим кодом, задающимся вопросом, что вы делаете и где реализованы эти вспомогательные классы, пока я не начну подбирать ваш стиль / привычки (с этого момента я мог бы полностью привыкнуть к этому).
Мне нравится, что вы уменьшаете количество информации в заголовках. Особенно в очень больших кодовых базах, которые могут иметь практические последствия для уменьшения зависимостей времени компиляции и, в конечном итоге, времени сборки.
Моя внутренняя реакция, однако, заключается в том, что если вы чувствуете необходимость скрыть детали реализации таким образом, отдавайте предпочтение передаче параметров автономным функциям с внутренней связью в исходном файле. Обычно вы можете реализовать вспомогательные функции (или целые классы), полезные для реализации определенного класса, не имея доступа ко всем внутренним элементам класса, а вместо этого просто передать соответствующие функции от реализации метода к функции (или конструктору). И, естественно, это имеет преимущество в уменьшении связи между вашим классом и «помощниками». Он также имеет тенденцию обобщать то, что в противном случае могло бы быть «помощниками», если вы обнаружите, что они начинают служить более обобщенной цели, применимой к более чем одной реализации класса.
Я также иногда немного съеживаюсь, когда вижу много «помощников» в коде. Это не всегда так, но иногда они могут быть симптомом для разработчика, который просто произвольно декомпозирует функции, чтобы устранить дублирование кода с огромными каплями данных, передаваемых функциям с едва понятными именами / целями, за исключением того факта, что они уменьшают количество код, необходимый для реализации некоторых других функций. Чуть-чуть более продуманный заранее может иногда привести к гораздо большей ясности с точки зрения того, как реализация класса разбивается на дополнительные функции, и может помочь передача определенных параметров для передачи целых экземпляров вашего объекта с полным доступом к внутренним компонентам. продвигать этот стиль дизайна мысли. Я не предполагаю, что вы делаете это, конечно (я понятия не имею),
Если это станет громоздким, я бы рассмотрел второе, более идиоматическое решение, которое является прыщом (я понимаю, что вы упомянули проблемы с ним, но я думаю, что вы можете обобщить решение, чтобы избежать их с минимальными усилиями). Это может отодвинуть всю информацию, которую необходимо реализовать вашему классу, включая его личные данные, из оптовых заголовков. Проблемы производительности pimpl могут быть в значительной степени устранены с помощью дешевого распределителя постоянного времени *, такого как свободный список, при сохранении семантики значений без необходимости реализации полноценного пользовательского копирования ctor.
- С точки зрения производительности pimpl, по крайней мере, вводит накладные расходы на указатель, но я думаю, что случаи должны быть довольно серьезными, когда возникают практические проблемы. Если пространственная локальность существенно не ухудшается с помощью распределителя, то ваши узкие циклы, повторяющиеся по объекту (которые обычно должны быть однородными, если производительность вызывает такое большое беспокойство) будут по-прежнему стремиться минимизировать ошибки кэширования на практике, если вы используете что-то вроде свободный список для размещения pimpl, помещающий поля класса в в основном непрерывные блоки памяти.
Лично только после исчерпания этих возможностей я мог бы рассмотреть что-то вроде этого. Я думаю, что это хорошая идея, если альтернатива похожа на более приватные методы, открытые для заголовка, и, возможно, только эзотерическая природа этого является практической проблемой.
Альтернатива
Одна альтернатива, которая пришла мне в голову только сейчас, которая в значительной степени выполняет ваши же цели, отсутствующие друзья, такова:
struct PredicateListData
{
int somePrivateField;
};
class PredicateList
{
PredicateListData data;
public:
bool match() const;
};
// In source file:
static bool fullMatch(const PredicateListData& p)
{
// Can access p.somePrivateField here.
}
bool PredicateList::match() const
{
return fullMatch(data);
}
Теперь это может показаться очень спорным отличием, и я все равно буду называть его «помощником» (возможно, в уничижительном смысле, поскольку мы все еще передаем функции все внутреннее состояние класса независимо от того, нужно ему все это или нет) за исключением того, что он избегает «шокового» фактора столкновения friend
. В общем, friend
выглядит немного страшно, что часто отсутствует дополнительная проверка, поскольку в нем говорится, что внутренние компоненты вашего класса доступны в другом месте (что подразумевает, что он не может поддерживать свои собственные инварианты). С тем, как вы используете, friend
становится довольно спорным, если люди знают о практике, так какfriend
просто находится в одном и том же исходном файле, помогая реализовать приватную функциональность класса, но вышеприведенное обеспечивает практически тот же эффект, по крайней мере, с одним возможным спорным преимуществом, заключающимся в том, что в нем не участвуют друзья, избегающие такого рода («О блин, у этого класса есть друг. Куда еще его рядовые получают доступ / видоизменяются? "). Принимая во внимание, что вышеприведенная версия немедленно сообщает о том, что для рядовых не может быть доступа / мутирования вне всего, что сделано в реализации PredicateList
.
Возможно, это движется к несколько догматическим территориям с таким уровнем нюансов, поскольку любой может быстро выяснить, если вы единообразно называете вещи *Helper*
и помещаете их все в один и тот же исходный файл, который все это как-то связано вместе как часть частной реализации класса. Но если мы становимся придирчивыми, то, возможно, сразу вышеперечисленный стиль не вызовет такой же коленной реакции с первого взгляда без friend
ключевого слова, которое выглядит немного пугающим.
По другим вопросам:
Потребитель может определить свой собственный класс PredicateList_HelperFunctions и разрешить ему доступ к закрытым полям. Хотя я не рассматриваю это как огромную проблему (если вы действительно хотите на этих частных полях, вы могли бы провести кастинг), возможно, это побудит потребителей использовать его таким образом?
Это может быть возможностью через границы API, когда клиент может определить второй класс с тем же именем и получить доступ к внутренним компонентам таким образом без ошибок компоновки. С другой стороны, я в основном программист на C, работающий в графике, где проблемы безопасности на этом уровне «что если» очень низки в списке приоритетов, поэтому такие проблемы, как эти, - это те, на которые я склонен махать руками и танцевать и попытаться притвориться, будто их не существует. :-D Если вы работаете в области, где подобные проблемы довольно серьезны, я думаю, это достойное рассмотрение.
Вышеупомянутое альтернативное предложение также позволяет избежать страданий от этой проблемы. Если вы все еще хотите придерживаться использования friend
, вы также можете избежать этой проблемы, сделав помощника закрытым вложенным классом.
class PredicateList
{
...
// Declare nested class.
class Helper;
// Make it a friend.
friend class Helper;
public:
...
};
// In source file:
class PredicateList::Helper
{
...
};
Это хорошо известный шаблон дизайна, название которого существует?
Ни один, насколько мне известно. Я в некотором роде сомневаюсь, что он будет, поскольку он действительно вдавается в мелочи деталей и стиля реализации.
"Хелпер Ад"
Я получил запрос на дополнительные разъяснения относительно точки о том , как я иногда передергивает , когда я вижу реализации с большим количеством «хелпера» кода, и это может быть немного спорным с некоторыми , но это на самом деле фактическая , как я действительно съежиться , когда я отладки некоторых реализации моих коллег только для того, чтобы найти множество «помощников». :-D И я был не единственным в команде, почесывая голову, пытаясь понять, что именно должны делать все эти помощники. Я также не хочу отказываться от догматики типа «Не пользуйся помощниками», но я хотел бы сделать крошечное предположение, что это может помочь подумать о том, как реализовать вещи, которых нет у них, когда это практически возможно.
Разве все частные функции-члены не являются вспомогательными функциями по определению?
И да, я в том числе частные методы. Если я вижу класс с простым общедоступным интерфейсом, но с бесконечным набором частных методов, которые в некоторой степени плохо определены по назначению, например find_impl
или find_detail
или find_helper
, то я также искажаю подобным образом.
В качестве альтернативы я предлагаю функции, не являющиеся членами, не являющимися членами, с внутренней связью (объявленной static
или внутри анонимного пространства имен), чтобы помочь реализовать ваш класс по крайней мере с более общей целью, чем «функция, которая помогает реализовать другие». И я могу привести Херба Саттера из C ++ «Стандарты кодирования» здесь, почему это может быть предпочтительнее с общей точки зрения SE:
Избегайте членских взносов: по возможности, предпочитайте, чтобы функции не были членами группы. [...] Функции, не являющиеся членами, не являющимися членами, улучшают инкапсуляцию за счет минимизации зависимостей: тело функции не может зависеть от непубличных членов класса (см. Пункт 11). Они также разбивают монолитные классы, освобождая разделяемую функциональность, дополнительно уменьшая связь (см. Пункт 33).
Вы также можете понять «членские взносы», о которых он говорит в некоторой степени с точки зрения основного принципа сужения переменной сферы действия. Если вы представите, как самый крайний пример, объект God, который имеет весь код, необходимый для запуска всей вашей программы, то предпочтение отдается «помощникам» такого рода (функциям, будь то функции-члены или друзья), которые могут получить доступ ко всем внутренним объектам ( Приват) класса в основном делают эти переменные не менее проблематичными, чем глобальные переменные. У вас есть все трудности, связанные с правильным управлением состоянием, безопасностью потоков и поддержкой инвариантов, которые вы получили бы с помощью глобальных переменных в этом наиболее экстремальном примере. И, конечно, большинство реальных примеров, надеюсь, не близко к этому крайнему значению, но сокрытие информации настолько же полезно, насколько и ограничение объема информации, к которой осуществляется доступ.
Теперь Саттер уже дает хорошее объяснение здесь, но я также добавил бы, что развязка имеет тенденцию способствовать психологическому улучшению (по крайней мере, если ваш мозг работает как мой) с точки зрения того, как вы проектируете функции. Когда вы начинаете разрабатывать функции, которые не могут получить доступ ко всему в классе, кроме только тех параметров, которые вы передаете, или, если вы передаете экземпляр класса в качестве параметра, только его открытые члены, это способствует развитию мышления, которое благоприятствует функции, которые имеют более четкое назначение, помимо разделения и продвижения улучшенной инкапсуляции, чем то, что вы могли бы спроектировать, если бы могли просто получить доступ ко всему.
Если мы вернемся к конечностям, то кодовая база, пронизанная глобальными переменными, не совсем соблазняет разработчиков на разработку функций в понятном и обобщенном виде. Очень быстро, чем больше информации вы можете получить в функции, тем больше у нас, смертных, искушений выродить ее и уменьшить ее ясность в пользу доступа ко всей этой дополнительной информации, которую мы имеем вместо того, чтобы принимать более конкретные и релевантные параметры для этой функции. сузить его доступ к государству, расширить его применимость и повысить ясность намерений. Это относится (хотя, как правило, в меньшей степени) к функциям-членам или друзьям.