Я читал C++ FAQ, и мне было интересно узнать об объявлении friend. Я лично никогда им не пользовался, но мне интересно изучать язык.
Какой хороший пример использования friend?
Прочитав FAQ еще немного, мне нравится идея перегрузки оператора <<>> и добавления его в друзья этих классов. Однако я не уверен, как это не нарушает инкапсуляцию. Когда эти исключения могут оставаться в рамках строгости ООП?
Вы могли бы использовать класс друзей, где уже есть тесная связь. Вот для чего это сделано. Например, таблица базы данных и ее индексы тесно связаны. Когда таблица изменяется, все ее индексы должны быть обновлены. Итак, класс DBIndex объявит DBTable своим другом, чтобы DBTable мог напрямую обращаться к внутренним компонентам индекса. Но открытого интерфейса к DBIndex не будет; нет смысла даже читать индекс.
«Пуристы» с небольшим практическим опытом утверждают, что друг нарушает принципы ООП, потому что класс должен быть единственным лицом, поддерживающим свое частное состояние. Это нормально, пока вы не столкнетесь с общей ситуацией, когда два класса должны поддерживать общее частное состояние.





При реализации древовидных алгоритмов для класса в коде фреймворка, который нам предоставил профессор, был древовидный класс как друг класса узла.
На самом деле это не приносит никакой пользы, кроме как позволить вам получить доступ к переменной-члену без использования функции настройки.
Во-первых (ИМО) не слушайте людей, которые говорят, что friend бесполезен. Это полезно. Во многих ситуациях у вас будут объекты с данными или функциями, которые не предназначены для публичного доступа. Это особенно верно в отношении больших кодовых баз со многими авторами, которые могут быть только поверхностно знакомы с различными областями.
Существуют альтернативы спецификатору друга, но часто они громоздки (конкретные классы на уровне cpp / замаскированные определения типов) или не надежны (комментарии или соглашения об именах функций).
На ответ;
Спецификатор friend позволяет назначенному классу получить доступ к защищенным данным или функциям внутри класса, создавая оператор friend. Например, в приведенном ниже коде любой может спросить ребенка, как его имя, но только мать и ребенок могут изменить имя.
Вы можете продолжить этот простой пример, рассмотрев более сложный класс, такой как Window. Вполне вероятно, что в Window будет много элементов функций / данных, которые не должны быть общедоступными, но НЕОБХОДИМЫ для связанного класса, такого как WindowManager.
class Child
{
//Mother class members can access the private parts of class Child.
friend class Mother;
public:
string name( void );
protected:
void setName( string newName );
};
В качестве дополнительного примечания в C++ FAQ упоминается инкапсуляция friendусиливает. friend предоставляет участникам выборочный доступ, как и protected. Любой детальный контроль лучше, чем публичный доступ. Другие языки также определяют механизмы избирательного доступа, например internal в C#. Наиболее негативная критика использования friend связана с более плотным сцеплением, что обычно считается плохим. Однако в некоторых случаях более плотное соединение - это именно то, что вам нужно, и friend дает вам эту мощность.
Не могли бы вы рассказать больше о (конкретных классах уровня cpp) и (маскированные определения типов), Андрей?
Этот ответ, кажется, больше сосредоточен на объяснении, что такое friend, а не на приведении примера мотивирующий. Пример Window / WindowManager лучше, чем показанный пример, но слишком расплывчатый. Этот ответ также не касается части вопроса, касающейся инкапсуляции.
Для примера Window / WindowManager, почему бы нам не использовать указатели Window в качестве атрибутов в WindowManager? Я думаю, это лучше, чем объявлять WindowManager «другом» Window.
Так эффективно «друг» существует, потому что C++ не имеет понятия о пакете, в котором все члены могут совместно использовать детали реализации? Мне действительно был бы интересен пример из реального мира.
@ weberc2 В настоящее время я использую friend, чтобы мой MessageBus мог получить доступ к закрытым членам MessageHandler, не подвергая их воздействию чего-либо еще.
Я считаю, что пример матери / ребенка неудачен. Эти имена подходят для экземпляров, но не для классов. (Проблема показывает, если у нас 2 матери, и у каждой есть свой ребенок).
Для меня функции друзей тесно связаны с реализацией методов сравнения между объектами, которым требуется доступ к закрытым членам для выполнения своей работы. Классы друзей немного редки (откуда я родом), но я нашел хорошее применение им - это наличие диспетчера трансляций, который может слушать каждый, но лишь немногие избранные могут добавить сообщение для трансляции. Те, у кого есть привилегия на трансляцию, - это классы друзей.
@ weberc2, вам бы не пришлось этого делать, если бы у Go были друзья, а не пакеты.
@iheanyi Есть много лучших практик, которым я бы не стал следовать, если бы Go поощрял худшие методы, но я не совсем уверен, как это связано с рассматриваемой темой ...
@weberc Я не знаю, какие «плохие практики» поощряет C++ в целом или конкретно в контексте дружбы. Сказать «хорошо в Go, можно просто использовать x вместо y из C++» бессмысленно, особенно с учетом того, что x и y являются взаимоисключающими функциями двух языков.
@iheanyi Дружба поощряет нарушения инкапсуляции. Вместо того, чтобы внедрять зависимости, я часто вижу, как разработчики new () объединяют в классе соавторов своего класса, а затем подвергают их тестированию через дружбу (вместо внедрения зависимостей, например). Это приводит к объединению обязанностей внутри одного и того же класса (построение / уничтожение графа объектов + независимо от фактической ответственности класса). Несколько человек даже ответили на этот вопрос похожими предложениями ниже: stackoverflow.com/a/28615295/483347stackoverflow.com/a/35846/483347
@weberc По этой логике глобальные переменные также способствуют нарушению инкапсуляции. Полагаю, в Го их тоже нет? То, что программисты могут чем-то злоупотреблять, не означает, что язык поощряет это. Насколько я могу судить, язык предполагает дружбу для улучшения инкапсуляции. Я использую дружбу, чтобы ограничить внешний доступ к данным или методам класса. Единственный человек, поощряющий нарушения инкапсуляции, когда они используют дружбу, - это программист, который изначально неправильно ее использовал.
@iheanyi Нет, вы неправильно рассудили - я никогда не говорил, что Go не поддерживает никаких плохих практик, я просто сказал, что есть много плохих практик, которые Go не одобряет. Точно так же я не утверждал, что дружбу нельзя использовать должным образом - мой аргумент заключался в том, что ею очень легко злоупотреблять (больше, чем многие другие функции, которыми можно злоупотреблять), о чем свидетельствуют многие ответы в этой теме.
@weberc Я только что прочитал большинство других ответов, и хотя есть только несколько исчерпывающих, большинство из них действительно дают правильные, ограниченные способы правильного использования дружбы на C++. Я не понимаю, чем злоупотреблять этим легче, чем многими другими вещами в языке. Многие люди думают, как объявлять переменные и массивы, указатели уважения или обрабатывать память, и все время терпят неудачу. В лучшем случае можно сказать: «С ++ позволяет легко выстрелить себе в ногу». Но независимо от того, насколько это легко, это не значит, что вас поощряют к этому.
@iheanyi Я только что прочитал список сам. Большинство «причин использовать друга» относятся к модульному тестированию, синглетонам и в целом «нарушениям инкапсуляции удобства» (что вряд ли лучше, чем сделать все ваши внутренние компоненты общедоступными) - все это считается плохой практикой в более широком сообществе ООП. . Есть также несколько правильных ответов, в первую очередь «использовать друга, когда это инкапсуляция улучшается» и этот ответ, который мы комментируем. Ради потомков, несколько вопросов были не ответами, а спорами о семантике.
@iheanyi Было несколько ответов, которые могли быть правильными, например, шаблонный ответ и ограничение наследования - я не знаю о первом, а второе звучит как взлом (и я не знаю, почему объект хочет ограничивать его наследование - для меня это звучит как деталь графа объекта).
Всегда хорошо иметь друзей; без них ты был бы один на свете! Мы участвуем в этом вместе, однако некоторые вещи должны оставаться конфиденциальными (наша конфиденциальность), в то время как другие вещи мы делимся с друзьями.
Пример дерева - довольно хороший пример: Реализация объекта в нескольких разных классах без имея отношения наследования.
Возможно, вам также может понадобиться, чтобы конструктор был защищен и принудительно народ использовать фабрику "друга".
... Хорошо, честно говоря, вы можете жить без этого.
Вы контролируете права доступа для членов и функций, используя право Private / Protected / Public? Итак, если предположить, что идея каждого из этих трех уровней ясна, тогда должно быть ясно, что мы чего-то упускаем ...
Объявление члена / функции как защищенного, например, является довольно общим. Вы говорите, что эта функция недоступна для каждый (за исключением, конечно, унаследованного потомка). А как насчет исключений? каждая система безопасности позволяет вам иметь какой-то «белый список», не так ли?
Таким образом, друг позволяет вам иметь гибкость, обеспечивающую изоляцию твердых объектов, но позволяет создать «лазейку» для вещей, которые, по вашему мнению, оправданы.
Думаю, люди говорят, что это не нужно, потому что всегда есть дизайн, который обойдется без этого. Я думаю, это похоже на обсуждение глобальных переменных: вы никогда не должны их использовать, всегда есть способ обойтись без них ... но на самом деле вы видите случаи, когда это оказывается (почти) наиболее элегантным способом. .. Я думаю, что то же самое и с друзьями.
It doesn't really do any good, other than let you access a member variable without using a setting function
ну, это не совсем способ смотреть на это. Идея состоит в том, чтобы контролировать, кто может получить доступ к чему, наличие или отсутствие функция настройки имеет мало общего с этим.
Как friend лазейка? Он позволяет методы, перечисленные в классе получать доступ к своим закрытым членам. Он по-прежнему не позволяет произвольному коду получить к ним доступ. Таким образом, он не отличается от общедоступной функции-члена.
friend максимально приближен к доступу на уровне пакетов C# / Java в C++. @jalf - как насчет классов друзей (таких как фабричный класс)?
@Ogre: А что насчет них? Вы по-прежнему специально предоставляете этот класс и никому другому доступ к внутренним компонентам класса. Вы не просто оставляете ворота открытыми для произвольного неизвестного кода, который может испортить ваш класс.
Канонический пример - перегрузка оператора
Вот несколько советов о друзьях C++, которые я слышал. Последний особенно запомнился.
"Канонический пример - перегрузка оператора "Канонический отказ от использования friend, я полагаю.
@roo: инкапсуляция здесь не нарушена, потому что класс сам определяет, кто может получить доступ к его закрытым членам. Инкапсуляция будет нарушена, только если это может быть вызвано извне класса, например если ваш operator << будет провозглашать «Я друг класса foo».
friend заменяет использование public, а не private!
Собственно, FAQ по C++ уже отвечает на это.
"друг заменяет использование публичного, а не частного!"
@Assaf: да, но FQA - это по большей части бессвязная злая тарабарщина, не имеющая реальной ценности. Деталь на friend не исключение. Единственное реальное наблюдение здесь заключается в том, что C++ обеспечивает инкапсуляцию только во время компиляции. И вам не нужно больше слов, чтобы это сказать. В остальном чушь. Итак, вкратце: об этом разделе FQA упоминать не стоит.
Большая часть этого FQA - сплошной блин :)
@Konrad: «Единственное реальное наблюдение здесь - то, что C++ обеспечивает инкапсуляцию только во время компиляции». Обеспечивают ли это какие-либо языки во время выполнения? Насколько мне известно, возврат ссылок на частные члены (и функции для языков, которые позволяют указатели на функции или функции как объекты первого класса) разрешен в C#, Java, Python и многих других.
@ André: насколько мне известно, JVM и CLR на самом деле может обеспечивают это. Я не знаю, всегда ли это делается, но якобы вы можете защитить пакеты / сборки от такого вторжения (хотя я никогда не делал этого).
@Konrad: технически любая среда типа ВМ с доступом к стеку вызовов может довольно легко проверить это, но я думаю, что это нарушит большую часть существующего кода. Например, я регулярно объявляю обратные вызовы наблюдателя как частные в C#, чтобы я мог контролировать, где они регистрируются. Я имею в виду, что если ваш объект возвращает ссылку на элементы данных или функции-члены, это на его страх и риск. Я не вижу преимущества любой в таких ограничениях, только потерю возможности предоставлять детальный выборочный доступ.
@ André: объявленный частным образом делегат ни в малейшей степени не нарушает инкапсуляцию. Он все равно будет нормально работать со средой выполнения с контролем доступа, потому что он четко определен в системе типов. Что было бы запрещено (и проверялось), так это попытки ниспровергать системы типов, используя либо отражение, либо функцию C# unsafe. - Чтобы прояснить это, я действую по слухам, я никогда не пытался использовать эти функции безопасности. Но если бы их не было, большая часть инфраструктуры политики безопасности .NET не имела бы никакого смысла.
@Konrad: если, используя блоки unsafe, вы имеете в виду эквивалент приведения к unsigned char * и использования смещения члена для доступа к частным членам в любом случае, то я согласен с вами, что C++ не проверяет это во время выполнения. В случае .NET я не думаю, что нужно делать какую-либо конкретную проверку для принудительной инкапсуляции, потому что вы в любом случае не сможете указать какой-либо «указатель + смещение» для ссылки на объект.
@ AndréCaron "Обеспечивают ли это какие-либо языки во время выполнения?" Любой язык с сильными отражательными способностями и целью быть "безопасным по типу" должен лучше проверять инкапсуляцию во время выполнения!
+1 за ". Инкапсуляция будет нарушена, только если это может быть вызвано извне класса, например если ваш оператор «Я друг класса foo».". Хороший!
Вы мог придерживаетесь самых строгих и чистейших принципов ООП и гарантируете, что ни у одного члена данных для любого класса даже нет аксессуары, так что все объекты должен будут единственными, кто может знать о своих данных, и единственный способ действовать на них - через косвенный Сообщения, т.е. методы.
Но даже в C# есть ключевое слово видимости внутренний, а в Java есть доступность уровня упаковка по умолчанию для некоторых вещей. C++ фактически приближается к идеалу ООП, минимизируя компромисс видимости в классе, указывая точно, какой другой класс и Только другие классы могут видеть в нем.
Я на самом деле не использую C++, но если бы в C# были друг, я бы использовал это вместо модификатора внутренний, глобального для сборки, который я действительно часто использую. На самом деле это не нарушает инкапсуляцию, потому что единица развертывания в .NET является - это сборка.
Но есть еще атрибут ВнутреннееAttribute (otherAssembly), который действует как механизм друг кросс-сборки. Microsoft использует это для визуальных сборок дизайнер.
Для выполнения TDD я много раз использовал ключевое слово friend в C++.
Может ли друг знать обо мне все?
Обновлено: я нашел этот ценный ответ о ключевом слове "друг" от Сайт Бьярне Страуструпа.
"Friend" is an explicit mechanism for granting access, just like membership.
To do TDD many times I've used 'friend' keyword in C++.
Can a friend know everything about me?
Нет, это только односторонняя дружба: `(
Один конкретный случай, когда я использую friend, - это создание классов Синглтон. Ключевое слово friend позволяет мне создать функцию доступа, которая более лаконична, чем всегда иметь метод GetInstance () в классе.
/////////////////////////
// Header file
class MySingleton
{
private:
// Private c-tor for Singleton pattern
MySingleton() {}
friend MySingleton& GetMySingleton();
}
// Accessor function - less verbose than having a "GetInstance()"
// static function on the class
MySingleton& GetMySingleton();
/////////////////////////
// Implementation file
MySingleton& GetMySingleton()
{
static MySingleton theInstance;
return theInstance;
}
Это может быть дело вкуса, но я не думаю, что экономия нескольких нажатий клавиш оправдывает использование здесь друга. GetMySingleton () должен быть статическим методом класса.
Частный c-tor запрещает функции, не являющейся другом, создавать экземпляр MySingleton, поэтому здесь необходимо ключевое слово friend.
@Gorpik "Это может быть дело вкуса, но я не думаю, что экономия нескольких нажатий клавиш оправдывает использование здесь друга." Так и есть. В любом случае, friend не требует особого «обоснования» для нет, а при добавлении функции-члена - нет.
Синглтоны в любом случае считаются плохой практикой (Google «синглтоны вредны», и вы получите много результатов, таких как это. Я не думаю, что использование функции для реализации антипаттерна может считаться хорошим использованием этой функции.
Что касается оператора>, то нет никаких оснований заводить дружбу с этими операторами. Это правда, что они не должны быть функциями-членами, но им также не нужно дружить.
Лучше всего создать общедоступные функции печати (ostream &) и чтения (istream &). Затем напишите оператор> в терминах этих функций. Это дает дополнительное преимущество, позволяя сделать эти функции виртуальными, что обеспечивает виртуальную сериализацию.
"Что касается оператора>, то нет никаких оснований заводить дружбу с этими операторами." Совершенно верно. "Это дает дополнительное преимущество, позволяя сделать эти функции виртуальными," Если рассматриваемый класс предназначен для деривации, да. Иначе зачем беспокоиться?
Я действительно не понимаю, почему этот ответ был отклонен дважды - и даже без объяснения причин! Это грубо.
virtual добавит перфоманс, который может быть довольно большим при сериализации
Friend пригодится, когда вы создаете контейнер и хотите реализовать итератор для этого класса.
На работе мы использовать друзей для тестирования кода, экстенсивно. Это означает, что мы можем обеспечить надлежащую инкапсуляцию и скрытие информации для основного кода приложения. Но также у нас может быть отдельный тестовый код, который использует друзей для проверки внутреннего состояния и данных для тестирования.
Достаточно сказать, что я бы не стал использовать ключевое слово friend как важный компонент вашего дизайна.
Именно для этого я и использую. Это или просто установите для переменных-членов значение protected. Жаль, что это не работает для C++ / CLI :-(
Лично я бы не одобрил этого. Обычно вы тестируете интерфейс, т.е. дает ли набор входных данных ожидаемый набор выходных данных. Зачем нужно проверять внутренние данные?
@Graeme: Потому что хороший план тестирования включает в себя тестирование как белого ящика, так и черного ящика.
Я склонен согласиться с @Graeme, как прекрасно объяснено в этот ответ.
@Graeme, возможно, это не внутренние данные напрямую. Я могу быть методом, который выполняет определенную операцию или задачу с этими данными, где этот метод является частным для класса и не должен быть общедоступным, в то время как некоторый другой объект может нуждаться в подпитке или заполнении защищенного метода этого класса своими собственными данными.
Я часто использовал трассировки (в основном printf) для такого рода тестов.
И поэтому никто не хочет дружить с программистами.
У нас возникла интересная проблема в компании, в которой я раньше работал, где мы использовали друга для достойного воздействия. Я работал в нашем отделе фреймворков, мы создали базовую систему уровня движка поверх нашей собственной ОС. Внутри у нас была структура классов:
Game
/ \
TwoPlayer SinglePlayer
Все эти классы были частью фреймворка и поддерживались нашей командой. Игры, выпускаемые компанией, были созданы на основе этой платформы, разработанной одним из детей Games. Проблема заключалась в том, что у Game были интерфейсы к различным вещам, к которым требовался доступ SinglePlayer и TwoPlayer, но которые мы не хотели открывать за пределами классов фреймворка. Решение заключалось в том, чтобы сделать эти интерфейсы приватными и разрешить доступ к ним TwoPlayer и SinglePlayer через дружбу.
По правде говоря, всю эту проблему можно было бы решить путем лучшей реализации нашей системы, но мы были заблокированы тем, что у нас было.
Еще одна распространенная версия примера Эндрю - ужасная кодовая пара.
parent.addChild(child);
child.setParent(parent);
Вместо того, чтобы беспокоиться о том, всегда ли обе строки выполняются вместе и в согласованном порядке, вы можете сделать методы закрытыми и иметь функцию друга для обеспечения согласованности:
class Parent;
class Object {
private:
void setParent(Parent&);
friend void addChild(Parent& parent, Object& child);
};
class Parent : public Object {
private:
void addChild(Object& child);
friend void addChild(Parent& parent, Object& child);
};
void addChild(Parent& parent, Object& child) {
if ( &parent == &child ){
wetPants();
}
parent.addChild(child);
child.setParent(parent);
}
Другими словами, вы можете уменьшить размеры общедоступных интерфейсов и обеспечить соблюдение инвариантов, которые пересекаются с классами и объектами в дружественных функциях.
Зачем кому-то для этого нужен друг? Почему бы не позволить функции-члену addChild также установить родительский элемент?
Лучшим примером было бы сделать setParent другом, поскольку вы не хотите позволять клиентам изменять родительский элемент, поскольку вы будете управлять им в категории функций addChild / removeChild.
Друзья также полезны для обратных звонков. Вы можете реализовать обратные вызовы как статические методы
class MyFoo
{
private:
static void callback(void * data, void * clientData);
void localCallback();
...
};
где callback вызывает внутренний вызов localCallback, а в clientData есть ваш экземпляр. Я считаю,
или же...
class MyFoo
{
friend void callback(void * data, void * callData);
void localCallback();
}
Это позволяет другу быть определенным исключительно в cpp как функция в стиле c, а не загромождать класс.
Точно так же шаблон, который я видел очень часто, заключается в том, чтобы поместить все закрытые члены В самом деле класса в другой класс, который объявлен в заголовке, определен в cpp и добавлен в друзья. Это позволяет кодировщику скрыть большую часть сложности и внутренней работы класса от пользователя заголовка.
В шапке:
class MyFooPrivate;
class MyFoo
{
friend class MyFooPrivate;
public:
MyFoo();
// Public stuff
private:
MyFooPrivate _private;
// Other private members as needed
};
В cpp,
class MyFooPrivate
{
public:
MyFoo *owner;
// Your complexity here
};
MyFoo::MyFoo()
{
this->_private->owner = this;
}
Становится легче скрывать вещи, которые нижестоящие компании не должны видеть таким образом.
Разве интерфейсы не были бы более чистым способом добиться этого? Что мешает кому-то искать MyFooPrivate.h?
Что ж, если вы используете частные и общедоступные для хранения секретов, вы легко проиграете. Под «сокрытием» я имею в виду, что пользователю MyFoo на самом деле не нужно видеть закрытых членов. Помимо этого, полезно поддерживать совместимость с ABI. Если вы сделаете _private указателем, частная реализация может меняться сколько угодно, не затрагивая общедоступный интерфейс, тем самым сохраняя совместимость с ABI.
Вы имеете в виду идиому PIMPL; цель не в дополнительной инкапсуляции, как вы, кажется, говорите, а в том, чтобы переместить детали реализации из заголовка, чтобы изменение деталей реализации не приводило к перекомпиляции клиентского кода. Кроме того, нет необходимости использовать друга для реализации этой идиомы.
Ну да. Его основная цель - переместить детали реализации. Друг полезен для обработки частных членов внутри публичного класса из частного или наоборот.
Ключевое слово friend имеет ряд полезных применений. Вот два варианта использования, которые мне сразу видны:
Определение друга позволяет определять функцию в области класса, но функция не будет определяться как функция-член, а как свободная функция включающего пространства имен, и не будет видна обычно, за исключением поиска, зависящего от аргумента. Это делает его особенно полезным при перегрузке операторов:
namespace utils {
class f {
private:
typedef int int_type;
int_type value;
public:
// let's assume it doesn't only need .value, but some
// internal stuff.
friend f operator+(f const& a, f const& b) {
// name resolution finds names in class-scope.
// int_type is visible here.
return f(a.value + b.value);
}
int getValue() const { return value; }
};
}
int main() {
utils::f a, b;
std::cout << (a + b).getValue(); // valid
}
Иногда вы обнаруживаете, что политике нужен доступ к производному классу:
// possible policy used for flexible-class.
template<typename Derived>
struct Policy {
void doSomething() {
// casting this to Derived* requires us to see that we are a
// base-class of Derived.
some_type const& t = static_cast<Derived*>(this)->getSomething();
}
};
// note, derived privately
template<template<typename> class SomePolicy>
struct FlexibleClass : private SomePolicy<FlexibleClass> {
// we derive privately, so the base-class wouldn't notice that,
// (even though it's the base itself!), so we need a friend declaration
// to make the base a friend of us.
friend class SomePolicy<FlexibleClass>;
void doStuff() {
// calls doSomething of the policy
this->doSomething();
}
// will return useful information
some_type getSomething();
};
Вы найдете не надуманный пример в ответе это. Другой код, использующий это, находится в ответе это. База CRTP использует свой указатель this, чтобы иметь возможность доступа к полям данных производного класса с помощью указателей-членов-данных.
Привет, я получаю синтаксическую ошибку (в xcode 4), когда пробую ваш CRTP. Xcode считает, что я пытаюсь унаследовать шаблон класса. Ошибка возникает в P<C> в template<template<typename> class P> class C : P<C> {};, где говорится: «Для использования шаблона класса C требуются аргументы шаблона». У вас были такие же проблемы или, возможно, вы знаете решение?
На первый взгляд @bennedich похож на ошибку, которую вы бы получили при недостаточной поддержке функций C++. Что довольно часто встречается среди компиляторов. Использование FlexibleClass в FlexibleClass должно неявно относиться к его собственному типу.
@bennedich: правила использования имени шаблона класса внутри тела класса изменены с C++ 11. Попробуйте включить режим C++ 11 в вашем компиляторе.
В Visual Studio 2015 добавьте эту общедоступную: f () {}; f (int_type t): значение (t) {}; Чтобы предотвратить эту ошибку компилятора: ошибка C2440: '
Использование gcc 4.8 с -std = C++ 11 amd gcc 5.1 с -std = C++ 11, -std = C++ 14 и -std = C++ 17 каждый раз приводит к этой ошибке: error: type/value mismatch at argument 1 in template parameter list for 'template<class> class SomePolicy' struct FlexibleClass : private SomePolicy<FlexibleClass> ^ crtp.cpp:17:56: note: expected a type, got 'FlexibleClass' Действительно ли Можно ли сделать такой шаблонный CRTP с помощью gcc? (обратите внимание, что я добавил using some_type = int; в начале)
Короткий ответ: используйте друг, когда на самом деле это инкапсуляция улучшается. Улучшение читабельности и удобства использования (операторы> являются каноническим примером) также является хорошей причиной.
Что касается примеров улучшения инкапсуляции, хорошими кандидатами являются классы, специально разработанные для работы с внутренними компонентами других классов (на ум приходят тестовые классы).
"операторы> являются каноническим примером" Нет. Скорее канонический встречные примеры.
@curiousguy: операторы << и >> обычно являются друзьями, а не членами, потому что сделать их членами было бы неудобно использовать. Конечно, я говорю о случае, когда этим операторам нужен доступ к приватным данным; в противном случае дружба бесполезна.
«потому что сделать их членами было бы неудобно их использовать.» Очевидно, что создание operator<< и operator>> членами класса значений вместо нечленов или членов i|ostream не обеспечит желаемый синтаксис, и я нет предлагаю это. "Я говорю о случае, когда этим операторам нужен доступ к приватным данным." Я не совсем понимаю, зачем операторам ввода / вывода нужен доступ к закрытым членам.
Я использую ключевое слово friend только для защищенных функций unittest. Некоторые скажут, что не стоит тестировать защищенный функционал. Однако я считаю этот инструмент очень полезным при добавлении новых функций.
Однако я не использую ключевое слово непосредственно в объявлениях классов, вместо этого я использую изящный шаблонный хак, чтобы добиться этого:
template<typename T>
class FriendIdentity {
public:
typedef T me;
};
/**
* A class to get access to protected stuff in unittests. Don't use
* directly, use friendMe() instead.
*/
template<class ToFriend, typename ParentClass>
class Friender: public ParentClass
{
public:
Friender() {}
virtual ~Friender() {}
private:
// MSVC != GCC
#ifdef _MSC_VER
friend ToFriend;
#else
friend class FriendIdentity<ToFriend>::me;
#endif
};
/**
* Gives access to protected variables/functions in unittests.
* Usage: <code>friendMe(this, someprotectedobject).someProtectedMethod();</code>
*/
template<typename Tester, typename ParentClass>
Friender<Tester, ParentClass> &
friendMe(Tester * me, ParentClass & instance)
{
return (Friender<Tester, ParentClass> &)(instance);
}
Это позволяет мне делать следующее:
friendMe(this, someClassInstance).someProtectedFunction();
Работает по крайней мере на GCC и MSVC.
edit: Reading the faq a bit longer I like the idea of the << >> operator overloading and adding as a friend of those classes, however I am not sure how this doesn't break encapsulation
Как бы это нарушило инкапсуляцию?
Вы нарушаете инкапсуляцию, когда разрешаете неограниченный доступ к члену данных. Рассмотрим следующие классы:
class c1 {
public:
int x;
};
class c2 {
public:
int foo();
private:
int x;
};
class c3 {
friend int foo();
private:
int x;
};
c1 - это очевидно, не инкапсулированный. Любой желающий может прочитать и изменить в нем x. У нас нет возможности обеспечить какой-либо контроль доступа.
c2 явно инкапсулирован. Публичного доступа к x нет. Все, что вы можете сделать, это вызвать функцию foo, которая выполняет некоторая значимая операция над классом.
c3? Это менее инкапсулировано? Разрешает ли неограниченный доступ к x? Разрешает ли доступ неизвестным функциям?
Нет. Это позволяет функции один получить доступ к закрытым членам класса. Как и c2. И, как и c2, единственная функция, которая имеет доступ, - это не «какая-то случайная неизвестная функция», а «функция, указанная в определении класса». Так же, как c2, мы можем увидеть, просто взглянув на определения классов, список полный тех, у кого есть доступ.
Так как же это меньше инкапсулировано? Такое же количество кода имеет доступ к закрытым членам класса. И каждый, у которого есть доступ, указан в определении класса.
friend не нарушает инкапсуляцию. Это заставляет некоторых Java-программистов чувствовать себя некомфортно, потому что, когда они говорят «ООП», они на самом деле иметь в виду «Java». Когда они говорят «Инкапсуляция», они не имеют в виду «частные члены должны быть защищены от произвольного доступа», но «класс Java, в котором единственными функциями, имеющими доступ к частным членам, являются члены класса», хотя это полная чушь по нескольким причинам .
Во-первых, как уже было показано, это слишком ограничительно. Нет причин, по которым методы друзей не должны делать то же самое.
Во-вторых, это не ограничительный довольно. Рассмотрим четвертый класс:
class c4 {
public:
int getx();
void setx(int x);
private:
int x;
};
Это, согласно вышеупомянутому менталитету Java, идеально инкапсулировано. И все же он позволяет абсолютно любому читать и изменять x. Как это вообще имеет смысл? (подсказка: это не так)
Нижняя линия: Инкапсуляция - это возможность контролировать, какие функции могут получить доступ к закрытым членам. Это нет о том, где именно расположены определения этих функций.
Создатель C++ говорит, что не нарушает принципа инкапсуляции, и я процитирую его:
Does "friend" violate encapsulation? No. It does not. "Friend" is an explicit mechanism for granting access, just like membership. You cannot (in a standard conforming program) grant yourself access to a class without modifying its source.
Более чем понятно ...
@curiousguy: Даже в случае шаблонов это правда.
Шаблон дружбы @Nawaz может быть предоставлен, но любой может сделать новую частичную или явную специализацию, не изменяя класс предоставления дружбы. Но будьте осторожны с нарушениями ODR, когда делаете это. И все равно не делайте этого.
Дружественные функции и классы обеспечивают прямой доступ к закрытым и защищенным членам класса, чтобы избежать нарушения инкапсуляции в общем случае. Чаще всего используется ostream: мы хотели бы иметь возможность набирать:
Point p;
cout << p;
Однако для этого может потребоваться доступ к личным данным Point, поэтому мы определяем перегруженный оператор
friend ostream& operator<<(ostream& output, const Point& p);
Однако есть очевидные последствия инкапсуляции. Во-первых, теперь класс или функция друга имеют полный доступ ко ВСЕМ членам класса, даже к тем, которые не относятся к его потребностям. Во-вторых, реализации класса и друга теперь связаны до такой степени, что внутреннее изменение в классе может сломать друга.
Если вы рассматриваете друга как продолжение класса, то, с логической точки зрения, это не проблема. Но в таком случае, зачем вообще нужно было изгонять друга?
Чтобы добиться того же, чего хотят достичь «друзья», но без нарушения инкапсуляции, можно сделать следующее:
class A
{
public:
void need_your_data(B & myBuddy)
{
myBuddy.take_this_name(name_);
}
private:
string name_;
};
class B
{
public:
void print_buddy_name(A & myBuddy)
{
myBuddy.need_your_data(*this);
}
void take_this_name(const string & name)
{
cout << name;
}
};
Инкапсуляция не нарушена, класс B не имеет доступа к внутренней реализации в A, но результат такой же, как если бы мы объявили B другом A. Компилятор оптимизирует вызовы функций, так что это приведет к тем же инструкциям, что и прямой доступ.
Я думаю, что использование слова «друг» - это просто ярлык с очевидной пользой, но с определенной стоимостью.
Вы должны быть очень осторожны с тем, когда и где вы используете ключевое слово friend, и, как и вы, я использовал его очень редко. Ниже приведены некоторые примечания по использованию friend и альтернатив.
Допустим, вы хотите сравнить два объекта, чтобы убедиться, что они равны. Вы могли либо:
Проблема с первым вариантом заключается в том, что это может быть ОЧЕНЬ много средств доступа, что (немного) медленнее, чем прямой доступ к переменным, сложнее для чтения и громоздко. Проблема со вторым подходом заключается в том, что вы полностью нарушаете инкапсуляцию.
Было бы неплохо, если бы мы могли определить внешнюю функцию, которая все еще могла бы получать доступ к закрытым членам класса. Мы можем сделать это с помощью ключевого слова friend:
class Beer {
public:
friend bool equal(Beer a, Beer b);
private:
// ...
};
Метод equal(Beer, Beer) теперь имеет прямой доступ к закрытым членам a и b (которыми могут быть char *brand, float percentAlcohol и т. д. Это довольно надуманный пример, вы бы скорее применили friend к перегруженному == operator, но мы еще вернемся к этому.
Несколько замечаний:
friend НЕ является функцией-членом классаpublic!)Я действительно использую friends только тогда, когда намного сложнее сделать наоборот. В качестве другого примера, многие векторные математические функции часто создаются как friends из-за совместимости Mat2x2, Mat3x3, Mat4x4, Vec2, Vec3, Vec4 и т. д. И гораздо проще дружить, чем использовать везде аксессоры. Как уже указывалось, friend часто применяется к << (действительно удобен для отладки), >> и, возможно, к оператору ==, но также может использоваться для чего-то вроде этого:
class Birds {
public:
friend Birds operator +(Birds, Birds);
private:
int numberInFlock;
};
Birds operator +(Birds b1, Birds b2) {
Birds temp;
temp.numberInFlock = b1.numberInFlock + b2.numberInFlock;
return temp;
}
Как я уже сказал, я не очень часто использую friend, но время от времени это именно то, что вам нужно. Надеюсь это поможет!
Вы можете использовать дружбу, когда разные классы (не наследующие один от другого) используют закрытые или защищенные члены другого класса.
Typical use cases of friend functions are operations that are conducted between two different classes accessing private or protected members of both.
от http://www.cplusplus.com/doc/tutorial/inheritance/.
Вы можете увидеть этот пример, в котором метод, не являющийся членом, обращается к закрытым членам класса. Этот метод должен быть объявлен в этом самом классе как друг класса.
// friend functions
#include <iostream>
using namespace std;
class Rectangle {
int width, height;
public:
Rectangle() {}
Rectangle (int x, int y) : width(x), height(y) {}
int area() {return width * height;}
friend Rectangle duplicate (const Rectangle&);
};
Rectangle duplicate (const Rectangle& param)
{
Rectangle res;
res.width = param.width*2;
res.height = param.height*2;
return res;
}
int main () {
Rectangle foo;
Rectangle bar (2,3);
foo = duplicate (bar);
cout << foo.area() << '\n';
return 0;
}
Я нашел удобное место для использования доступа друзей: Unittest частных функций.
Но можно ли для этого использовать публичную функцию? В чем преимущество использования доступа друзей?
@Maverobot Не могли бы вы уточнить свой вопрос?
В C++ ключевое слово friend полезно при перегрузке оператора и создании моста.
1.) Ключевое слово Friend в перегрузке оператора:
Пример перегрузки оператора: Допустим, у нас есть класс «Point», который имеет две плавающие переменные
«x» (для x-координаты) и «y» (для y-координаты). Теперь нам нужно перегрузить "<<" (оператор извлечения), чтобы при вызове "cout << pointobj" он выводил координаты x и y (где pointobj - объект класса Point). Для этого у нас есть два варианта:
1.Overload "operator <<()" function in "ostream" class. 2.Overload "operator<<()" function in "Point" class.Now First option is not good because if we need to overload again this operator for some different class then we have to again make change in "ostream" class.
"operator <<()" function:1.Using ostream object cout.As: cout.operator<<(Pointobj) (form ostream class).
2.Call without an object.As: operator<<(cout, Pointobj) (from Point class).
Потому что мы реализовали перегрузку в классе Point. Итак, чтобы вызвать эту функцию без объекта, мы должны добавить ключевое слово "friend", потому что мы можем вызвать функцию друга без объекта.
Теперь объявление функции будет As: "friend ostream &operator<<(ostream &cout, Point &pointobj);"
2.) Ключевое слово друга при создании моста:
Предположим, нам нужно создать функцию, в которой мы должны получить доступ к закрытому члену двух или более классов (обычно называемых «мостом»).
Как это сделать:
Чтобы получить доступ к закрытому члену класса, он должен быть членом этого класса. Теперь, чтобы получить доступ к закрытому члену другого класса, каждый класс должен объявить эту функцию как функцию друга. Например :
Предположим, есть два класса A и B. Функция "funcBridge()" хочет получить доступ к закрытому члену обоих классов. Затем оба класса должны объявить "funcBridge()" как: friend return_type funcBridge(A &a_obj, B & b_obj);
Я думаю, это поможет понять ключевое слово друга.
Возможно, я что-то пропустил из приведенных выше ответов, но еще одна важная концепция инкапсуляции - это сокрытие реализации. Уменьшение доступа к частным элементам данных (деталям реализации класса) позволяет значительно упростить модификацию кода позже. Если друг напрямую обращается к личным данным, любые изменения в полях данных реализации (личные данные) нарушают код доступа к этим данным. Использование методов доступа в основном устраняет это. Думаю, довольно важно.
Это может не быть реальной ситуацией варианта использования, но может помочь проиллюстрировать использование друга между классами.
Клубный дом
class ClubHouse {
public:
friend class VIPMember; // VIP Members Have Full Access To Class
private:
unsigned nonMembers_;
unsigned paidMembers_;
unsigned vipMembers;
std::vector<Member> members_;
public:
ClubHouse() : nonMembers_(0), paidMembers_(0), vipMembers(0) {}
addMember( const Member& member ) { // ...code }
void updateMembership( unsigned memberID, Member::MembershipType type ) { // ...code }
Amenity getAmenity( unsigned memberID ) { // ...code }
protected:
void joinVIPEvent( unsigned memberID ) { // ...code }
}; // ClubHouse
Члены класса
class Member {
public:
enum MemberShipType {
NON_MEMBER_PAID_EVENT, // Single Event Paid (At Door)
PAID_MEMBERSHIP, // Monthly - Yearly Subscription
VIP_MEMBERSHIP, // Highest Possible Membership
}; // MemberShipType
protected:
MemberShipType type_;
unsigned id_;
Amenity amenity_;
public:
Member( unsigned id, MemberShipType type ) : id_(id), type_(type) {}
virtual ~Member(){}
unsigned getId() const { return id_; }
MemberShipType getType() const { return type_; }
virtual void getAmenityFromClubHouse() = 0
};
class NonMember : public Member {
public:
explicit NonMember( unsigned id ) : Member( id, MemberShipType::NON_MEMBER_PAID_EVENT ) {}
void getAmenityFromClubHouse() override {
Amenity = ClubHouse::getAmenity( this->id_ );
}
};
class PaidMember : public Member {
public:
explicit PaidMember( unsigned id ) : Member( id, MemberShipType::PAID_MEMBERSHIP ) {}
void getAmenityFromClubHouse() override {
Amenity = ClubHouse::getAmenity( this->id_ );
}
};
class VIPMember : public Member {
public:
friend class ClubHouse;
public:
explicit VIPMember( unsigned id ) : Member( id, MemberShipType::VIP_MEMBERSHIP ) {}
void getAmenityFromClubHouse() override {
Amenity = ClubHouse::getAmenity( this->id_ );
}
void attendVIPEvent() {
ClubHouse::joinVIPEvent( this->id );
}
};
Удобства
class Amenity{};
Если вы посмотрите на отношения этих классов здесь; ClubHouse предлагает различные типы членства и доступа к членству. Все члены являются производными от суперкласса или базового класса, поскольку все они имеют общий идентификатор и перечислимый тип, которые являются общими, а внешние классы могут получить доступ к своим идентификаторам и типам с помощью функций доступа, которые находятся в базовом классе.
Однако благодаря такой иерархии членов и его производных классов и их взаимосвязи с классом ClubHouse единственным производным классом, имеющим «особые привилегии», является класс VIPMember. Базовый класс и два других производных класса не могут получить доступ к методу joinVIPEvent () ClubHouse, но класс VIP-члена имеет эту привилегию, как если бы он имел полный доступ к этому событию.
Таким образом, с VIPMember и ClubHouse это улица с двусторонним доступом, где другие классы участников ограничены.
Как говорится в ссылке на объявление друга:
The friend declaration appears in a class body and grants a function or another class access to private and protected members of the class where the friend declaration appears.
Напоминаем, что в некоторых ответах есть технические ошибки, в которых говорится, что friend может посещать только членов защищенный.
Хотя я согласен с ответом, что класс друзей не обязательно является плохим, я склонен рассматривать его как небольшой код. Это часто, хотя и не всегда, указывает на необходимость пересмотра иерархии классов.