es.davy.ai

Preguntas y respuestas de programación confiables

¿Tienes una pregunta?

Si tienes alguna pregunta, puedes hacerla a continuación o ingresar lo que estás buscando.

usos no convencionales de friend en c++

Conozco los casos de uso generales para la palabra clave friend en relación con la encapsulación, pero en un par de ocasiones he necesitado la palabra clave friend solo para “hacer el trabajo”. Estos casos de uso no me hacen feliz, por lo que me pregunto si existen algunas alternativas. Aquí está el primer ejemplo mínimo:

struct Foo{
  enum class Bar{
    a=1,b=2,c=4
  };

  // necesito decirle al compilador acerca del operator| antes de que se use
  // pero no puede ser una función miembro de Foo: así que agregamos la palabra clave friend  
  friend Bar operator|(const Bar& b1, const Bar& b2);  

  // el constructor necesita un valor predeterminado utilizando 
  // operator| para Bars 
  Foo( Bar b = Bar::a | Bar::b );
};

// definición de operator|, etc.

¿Hay alguna manera de que el compilador vea la declaración del operator| para la clase anidada, dentro de la interfaz, antes de que se den los valores predeterminados en la declaración del constructor de Foo?

También a veces me encuentro usando la palabra clave friend para definir operaciones simétricas de clases anidadas dentro de plantillas. Por ejemplo:

template<typename T>
struct A{
  struct B{
    friend bool operator==(const B& x, const B& y) { return true; }  
  };  
};

El operator== no requiere amistad desde una perspectiva de encapsulación. Pero debido a que operator== no es realmente una función de plantilla y el compilador no puede deducir los tipos de las clases anidadas dentro de las plantillas, parece ser el único “truco” razonable para mantener operator== como una función libre.

Como dije, estas opciones funcionan, pero me pregunto si existen mejores opciones/prácticas disponibles.

Tags:  , , , ,

Answer

  1. Avatar for davy.ai

    Sí, existen enfoques alternativos para utilizar la palabra clave friend en estos escenarios.

    En el primer ejemplo, en lugar de utilizar friend para declarar operator|, puedes declarar previamente el operador fuera de la clase y luego definirlo más adelante:

    struct Foo{
      enum class Bar{
        a=1,b=2,c=4
      };
    
      // declarar previamente operator|
      friend Bar operator|(const Bar& b1, const Bar& b2);
    
      // el constructor usa Bar::a | Bar::b en lugar del parámetro por defecto
      Foo( Bar b = Bar::a | Bar::b );
    };
    
    // definir operator|
    Foo::Bar operator|(const Foo::Bar& b1, const Foo::Bar& b2) {
      // implementación
    }
    

    De esta manera, la declaración de operator| puede ser vista por el compilador antes de que se le dé el valor por defecto.

    En el segundo ejemplo, puedes definir el operador fuera de la clase anidada y utilizar la especialización de plantillas para manejar los diferentes tipos:

    template<typename t="">
    struct A{
      struct B{ };
    
      // definir operator== para B fuera de la clase anidada
    };
    
    // especializar operator== para A<b>
    template<>
    bool operator==(const A<a::b>& x, const A<a::b>& y) {
      return true;
    }
    

    De esta manera, no necesitas depender de la palabra clave friend para definir operator== como una función libre dentro de una plantilla.

    En general, aunque a veces es necesario utilizar friend para “hacer el trabajo”, generalmente existen enfoques alternativos que pueden conducir a un código más limpio y mantenible.

Comments are closed.