friendship friends example and c++ design-patterns private friend

friends - friend modifier c++



limpio C++ amigo granular equivalente?(Respuesta: Idioma del abogado-cliente) (6)

¿Por qué C ++ tiene miembros public que cualquier persona puede llamar y declaraciones de friend que exponen a todos private miembros private a clases o métodos extranjeros determinados , pero no ofrece ninguna sintaxis para exponer a determinados miembros a las personas que llaman?

Quiero expresar las interfaces con algunas rutinas para que las invoquen solo las personas que llaman sin tener que darles acceso completo a todas las partes privadas, lo cual es razonable. Lo mejor que pude encontrarme (a continuación) y las sugerencias de otros hasta ahora giran en torno a modismos / patrones de variados aspectos indirectos, donde realmente solo quiero una forma de tener definiciones de clases simples y simples que indiquen explícitamente qué personas llaman (más granularmente que yo , mis hijos , o absolutamente nadie ) pueden acceder a qué miembros. ¿Cuál es la mejor manera de expresar el concepto a continuación?

// Can I grant Y::usesX(...) selective X::restricted(...) access more cleanly? void Y::usesX(int n, X *x, int m) { X::AttorneyY::restricted(*x, n); } struct X { class AttorneyY; // Proxies restricted state to part or all of Y. private: void restricted(int); // Something preferably selectively available. friend class AttorneyY; // Give trusted member class private access. int personal_; // Truly private state ... }; // Single abstract permission. Can add more friends or forwards. class X::AttorneyY { friend void Y::usesX(int, X *, int); inline static void restricted(X &x, int n) { x.restricted(n); } };

No estoy cerca de ser un gurú de la organización de software, pero parece que la simplicidad de la interfaz y el principio de menor privilegio están directamente en desacuerdo en este aspecto del lenguaje. Un ejemplo más claro para mi deseo podría ser una clase Person con métodos declarados como takePill(Medicine *) tellTheTruth() y forfeitDollars(unsigned int) que solo los métodos de miembros / instancia de Physician , Judge o TaxMan , respectivamente, deberían considerar invocar. Necesito una sola proxy o clases de interfaz para cada aspecto importante de la interfaz, pero dígame si sabe que me estoy perdiendo algo.

Respuesta aceptada de Drew Hall : Dr. Dobbs - Amistad y el idioma del abogado-cliente

El código anterior originalmente llamaba a la clase contenedora ''Proxy'' en lugar de ''Abogado'' y utilizaba punteros en lugar de referencias, pero era equivalente a lo que Drew encontró, que luego consideré la mejor solución conocida. (No darme palmaditas en la espalda demasiado fuerte ...) También cambié la firma de ''restringido'' para demostrar el reenvío de parámetros. El costo total de esta expresión idiomática es una declaración de clase y una de amigo por conjunto de permisos, una declaración de amigo por llamada autorizada establecida y una versión de envío por método expuesto por conjunto de permisos. La mayor parte de la mejor discusión a continuación gira en torno a la repetición de la llamada de reenvío que una expresión idiomática ''clave'' muy similar evita a costa de una protección menos directa.


Algo similar al código siguiente le permitirá un control detallado sobre qué partes de su estado privado publicita a través de la palabra clave friend .

class X { class SomewhatPrivate { friend class YProxy1; void restricted(); }; public: ... SomewhatPrivate &get_somewhat_private_parts() { return priv_; } private: int n_; SomewhatPrivate priv_; };

PERO:

  1. No creo que valga la pena el esfuerzo.
  2. La necesidad de utilizar la palabra clave friend puede sugerir que su diseño es defectuoso, quizás haya una forma de que pueda hacer lo que necesita sin él. Intento evitarlo, pero si hace que el código sea más legible, mantenible o reduzca la necesidad de un código repetitivo, lo uso.

EDITAR: Para mí, el código anterior es (por lo general) una abominación que (por lo general) no debería usarse.


El idioma de abogado y cliente puede ser lo que estás buscando. La mecánica no es muy diferente de la solución de clase de proxy de miembro, pero de esta manera es más idiomática.


Hay un patrón muy simple, que se ha denominado PassKey , y que es muy fácil en C ++ 11 :

template <typename T> class Key { friend T; Key() {} Key(Key const&) {} };

Y con eso:

class Foo; class Bar { public: void special(int a, Key<Foo>); };

Y el sitio de llamadas, en cualquier método de Foo , se ve así:

Bar().special(1, {});

Nota: si está atrapado en C ++ 03, omita hasta el final de la publicación.

El código es engañosamente simple, incorpora algunos puntos clave que vale la pena elaborar.

El quid del patrón es que:

  • Llamar a Bar::special requiere copiar una Key<Foo> en el contexto de la persona que llama
  • solo Foo puede construir o copiar una Key<Foo>

Es notable que:

  • las clases derivadas de Foo no pueden construir o copiar Key<Foo> porque la amistad no es transitiva
  • Foo no puede entregar un Key<Foo> para que cualquiera pueda llamar a Bar::special porque llamarlo requiere no solo aferrarse a una instancia, sino hacer una copia

Debido a que C ++ es C ++, hay algunas cosas que evitar:

  • el constructor de copia debe ser definido por el usuario; de lo contrario, es public de manera predeterminada
  • el constructor predeterminado tiene que ser definido por el usuario; de lo contrario, es public de manera predeterminada
  • el constructor predeterminado debe definirse manualmente , porque = default permitiría que la inicialización agregada omita el constructor predeterminado manual definido por el usuario (y así permitir que cualquier tipo obtenga una instancia)

Esto es lo suficientemente sutil como para que, por una vez, le recomiende copiar / pegar textualmente la definición anterior de Key lugar de intentar reproducirla de memoria.

Una variación que permite la delegación:

class Bar { public: void special(int a, Key<Foo> const&); };

En esta variante, cualquier persona que tenga una instancia de Key<Foo> puede llamar a Bar::special , por lo que aunque solo Foo pueda crear una Key<Foo> , puede difundir las credenciales a los tenientes de confianza.

En esta variante, para evitar que un teniente deshonesto filtre la clave, es posible eliminar por completo el constructor de copia, lo que permite vincular la duración de la clave a un alcance léxico particular.

Y en C ++ 03?

Bueno, la idea es similar, excepto ese friend T; no es una cosa, entonces uno tiene que crear un nuevo tipo de clave para cada titular:

class KeyFoo { friend class Foo; KeyFoo () {} KeyFoo (KeyFoo const&) {} }; class Bar { public: void special(int a, KeyFoo); };

El patrón es lo suficientemente repetitivo como para que valga la pena una macro para evitar errores tipográficos.

La inicialización agregada no es un problema, pero de nuevo la = default sintaxis = default tampoco está disponible.

Un agradecimiento especial a las personas que ayudaron a mejorar esta respuesta a lo largo de los años:

  • Luc Touraille , por señalarme en los comentarios de esa class KeyFoo: boost::noncopyable { friend class Foo; KeyFoo() {} }; class KeyFoo: boost::noncopyable { friend class Foo; KeyFoo() {} }; desactiva completamente el constructor de copia y, por lo tanto, solo funciona en la variante de delegación (lo que impide el almacenamiento de la instancia).
  • K-ballo , para señalar cómo C ++ 11 mejoró la situación con el friend T;

He escrito una pequeña mejora en la solución dada por Matthieu M. La limitación de su solución es que solo puedes otorgar acceso a una sola clase. ¿Qué sucede si quiero permitir que una de las tres clases tenga acceso?

#include <type_traits> #include <utility> struct force_non_aggregate {}; template<typename... Ts> struct restrict_access_to : private force_non_aggregate { template<typename T, typename = typename std::enable_if<(... or std::is_same<std::decay_t<T>, std::decay_t<Ts>>{})>::type> constexpr restrict_access_to(restrict_access_to<T>) noexcept {} restrict_access_to() = delete; restrict_access_to(restrict_access_to const &) = delete; restrict_access_to(restrict_access_to &&) = delete; }; template<typename T> struct access_requester; template<typename T> struct restrict_access_to<T> : private force_non_aggregate { private: friend T; friend access_requester<T>; restrict_access_to() = default; restrict_access_to(restrict_access_to const &) = default; restrict_access_to(restrict_access_to &&) = default; }; // This intermediate class gives us nice names for both sides of the access template<typename T> struct access_requester { static constexpr auto request_access_as = restrict_access_to<T>{}; }; template<typename T> constexpr auto const & request_access_as = access_requester<T>::request_access_as; struct S; struct T; auto f(restrict_access_to<S, T>) {} auto g(restrict_access_to<S> x) { static_cast<void>(x); // f(x); // Does not compile } struct S { S() { g(request_access_as<S>); g({}); f(request_access_as<S>); // f(request_access_as<T>); // Does not compile // f({request_access_as<T>}); // Does not compile } }; struct T { T() { f({request_access_as<T>}); // g({request_access_as<T>}); // Does not compile // g({}); // Does not compile } };

Esto utiliza un enfoque ligeramente diferente para hacer que el objeto no sea un agregado. En lugar de tener un constructor proporcionado por el usuario, tenemos una clase base privada vacía. En la práctica, probablemente no importe, pero significa que esta implementación es una clase POD porque sigue siendo trivial. El efecto debe seguir siendo el mismo, sin embargo, porque nadie va a almacenar estos objetos de todos modos.


Puede usar un patrón descrito en el book Jeff Aldger ''C ++ para programadores reales''. No tiene un nombre especial, pero allí se lo conoce como ''piedras preciosas y facetas''. La idea básica es la siguiente: entre su clase principal que contiene toda la lógica, usted define varias interfaces (no interfaces reales, como ellas) que implementa sub-partes de esa lógica. Cada una de esas interfaces (faceta en términos de libro) proporciona acceso a algunos de la lógica de la clase principal (gema). Además, cada faceta contiene el puntero a la instancia de piedra preciosa.

¿Qué significa esto para ti?

  1. Puede usar cualquier faceta en todas partes en lugar de piedras preciosas.
  2. Los usuarios de facetas no tienen que saber sobre la estructura de la piedra preciosa, ya que podría ser declarada y utilizada a través del patrón PIMPL.
  3. Otras clases pueden referirse a la faceta más bien a la piedra preciosa: esta es la respuesta a su pregunta sobre cómo exponer una cantidad limitada de métodos a la clase especificada.

Espero que esto ayude. Si lo desea, podría publicar ejemplos de código aquí para ilustrar este patrón más claramente.

EDITAR: Aquí está el código:

class Foo1; // This is all the client knows about Foo1 class PFoo1 { private: Foo1* foo; public: PFoo1(); PFoo1(const PFoo1& pf); ~PFoo(); PFoo1& operator=(const PFoo1& pf); void DoSomething(); void DoSomethingElse(); }; class Foo1 { friend class PFoo1; protected: Foo1(); public: void DoSomething(); void DoSomethingElse(); }; PFoo1::PFoo1() : foo(new Foo1) {} PFoo1::PFoo(const PFoo1& pf) : foo(new Foo1(*(pf {} PFoo1::~PFoo() { delete foo; } PFoo1& PFoo1::operator=(const PFoo1& pf) { if (this != &pf) { delete foo; foo = new Foo1(*(pf.foo)); } return *this; } void PFoo1::DoSomething() { foo->DoSomething(); } void PFoo1::DoSomethingElse() { foo->DoSomethingElse(); } Foo1::Foo1() { } void Foo1::DoSomething() { cout << “Foo::DoSomething()” << endl; } void Foo1::DoSomethingElse() { cout << “Foo::DoSomethingElse()” << endl; }

EDIT2: Tu clase Foo1 podría ser más compleja, por ejemplo, contiene dos métodos más:

void Foo1::DoAnotherThing() { cout << “Foo::DoAnotherThing()” << endl; } void Foo1::AndYetAnother() { cout << “Foo::AndYetAnother()” << endl; }

Y están accesibles a través de la class PFoo2

class PFoo2 { private: Foo1* foo; public: PFoo2(); PFoo2(const PFoo1& pf); ~PFoo(); PFoo2& operator=(const PFoo2& pf); void DoAnotherThing(); void AndYetAnother(); }; void PFoo1::DoAnotherThing() { foo->DoAnotherThing(); } void PFoo1::AndYetAnother() { foo->AndYetAnother(); }

Esos métodos no están en la clase PFoo1 , por lo que no puede acceder a ellos a través de él. De esta forma, puede dividir el comportamiento de Foo1 en dos (o más) facetas PFoo1 y PFoo2. Esas clases de facetas podrían usarse en diferentes lugares, y su interlocutor no debería estar al tanto de la implementación de Foo1. Tal vez no es lo que realmente quieres, pero lo que quieres es imposible para C ++, y este es un aroud de trabajo, pero tal vez demasiado detallado ...


Sé que esta es una vieja pregunta, pero el problema aún es relevante. Si bien me gusta la idea de la expresión idiomática de abogado-cliente, quería una interfaz transparente para las clases de clientes a las que se les había otorgado acceso privado (o protegido).

Imagino que ya se ha hecho algo similar a esto, pero una mirada superficial no arrojó nada. El siguiente método (C ++ 11 arriba) funciona por clase (no por objeto) y utiliza una clase base CRTP que es utilizada por la ''clase privada'' para exponer un functor público. Solo aquellas clases a las que se les ha dado acceso específicamente pueden llamar al operador del functor (), que luego invoca directamente el método privado asociado a través de una referencia almacenada.

No hay una sobrecarga de llamada de función y la única sobrecarga de memoria es una referencia por método privado que requiere exposición. El sistema es muy versátil; cualquier firma de función y tipo de devolución está permitida, como lo es llamar funciones virtuales en la clase privada.

Para mí, el principal beneficio es uno de sintaxis. Si bien se requiere una declaración ciertamente fea de los objetos del funtor en la clase privada, esto es completamente transparente para las clases de los clientes. Aquí hay un ejemplo tomado de la pregunta original:

struct Doctor; struct Judge; struct TaxMan; struct TheState; struct Medicine {} meds; class Person : private GranularPrivacy<Person> { private: int32_t money_; void _takePill (Medicine *meds) {std::cout << "yum..."<<std::endl;} std::string _tellTruth () {return "will do";} int32_t _payDollars (uint32_t amount) {money_ -= amount; return money_;} public: Person () : takePill (*this), tellTruth (*this), payDollars(*this) {} Signature <void, Medicine *> ::Function <&Person::_takePill> ::Allow <Doctor, TheState> takePill; Signature <std::string> ::Function <&Person::_tellTruth> ::Allow <Judge, TheState> tellTruth; Signature <int32_t, uint32_t> ::Function <&Person::_payDollars> ::Allow <TaxMan, TheState> payDollars; }; struct Doctor { Doctor (Person &patient) { patient.takePill(&meds); // std::cout << patient.tellTruth(); //Not allowed } }; struct Judge { Judge (Person &defendant) { // defendant.payDollars (20); //Not allowed std::cout << defendant.tellTruth() <<std::endl; } }; struct TheState { TheState (Person &citizen) //Can access everything! { citizen.takePill(&meds); std::cout << citizen.tellTruth()<<std::endl; citizen.payDollars(50000); }; };

La clase base GranularPrivacy funciona al definir 3 clases de plantilla anidadas. El primero de ellos, ''Firma'', toma el tipo de retorno de función y la firma de función como parámetros de plantilla, y los reenvía al método operator () del functor y a la segunda clase de plantilla de anidación, ''Function''. Esto se parametriza mediante un puntero a una función miembro privada de la clase Host, que debe tener la firma provista por la clase Signature. En la práctica, se usan dos clases de ''Función'' separadas; el que se da aquí, y el otro para las funciones const, se omite por brevedad.

Finalmente, la clase Allow recursivamente hereda de una clase base explícitamente instanciada utilizando el mecanismo de plantilla variadic, dependiendo del número de clases especificadas en su lista de argumentos de plantilla. Cada nivel de herencia de Permitir tiene un amigo de la lista de plantillas, y las instrucciones de uso elevan el constructor de la clase base y el operador () a la jerarquía de herencia en el ámbito más derivado.

template <class Host> class GranularPrivacy { friend Host; template <typename ReturnType, typename ...Args> class Signature { friend Host; typedef ReturnType (Host::*FunctionPtr) (Args... args); template <FunctionPtr function> class Function { friend Host; template <class ...Friends> class Allow { Host &host_; protected: Allow (Host &host) : host_ (host) {} ReturnType operator () (Args... args) {return (host_.*function)(args...);} }; template <class Friend, class ...Friends> class Allow <Friend, Friends...> : public Allow <Friends...> { friend Friend; friend Host; protected: using Allow <Friends...>::Allow; using Allow <Friends...>::operator (); }; }; }; };

Espero que alguien lo encuentre útil, cualquier comentario o sugerencia sería bienvenido. Esto definitivamente todavía está en progreso. Me gustaría combinar las clases de Firma y Función en una sola clase de plantilla, pero he estado luchando por encontrar una manera de hacerlo. Se pueden encontrar ejemplos más completos y ejecutables en cpp.sh/6ev45 y cpp.sh/2rtrj .