2010-01-19 27 views
9
class X 
{ 
protected: 
    void protectedFunction() { cout << "I am protected" ; } 
}; 

class Y : public X 
{ 
public: 
    using X::protectedFunction; 
}; 

int main() 
{ 
    Y y1; 
    y1.protectedFunction(); 
} 

De esta manera puedo exponer una de las funciones de la clase base.¿Estas dos clases violan la encapsulación?

  1. ¿Esto no viola el principio de encapsulación?
  2. ¿Hay alguna razón específica por la cual esto se encuentra en la norma?
  3. ¿Hay algún uso de esto, o se va a cambiar en el nuevo estándar?
  4. ¿Hay algún problema abierto relacionado con esto en la norma?
+0

para aclarar: el objetivo de mi pregunta no era hablar sobre formas brutales o engañosas de romper la encapsulación. Sólo quería discutir las decisiones de diseño u otras características de C++ que podrían haber conducido a esta característica. ¿Es solo un efecto secundario de la solución para funcionar el problema de ocultación o es una característica independiente que se introdujo en C++ por alguna razón específica –

+0

Creo que la razón por la que las personas enumeran todas estas formas de romper la encapsulación no es documentar cómo hazlo, pero para explicar que tienes que elegir romper la encapsulación. Cuando hay un error porque intentó acceder a un método o miembro privado, eso podría servir como un recordatorio suave, y si respeta los deseos del diseñador, lo llevará a diseñar una solución cooperativa. Siempre es un hecho que la encapsulación se puede romper a la fuerza. La encapsulación 'protected' no se puede romper sin fuerza, lo cual no creo que sea una falla en parte del lenguaje. –

Respuesta

12

Sí, y por eso protected ha recibido una buena cantidad de críticas.

Bjarne Stroustrup, el creador de C++, lamenta esta en su excelente libro El diseño y evolución de C++:

Una de mis preocupaciones acerca protegida es exactamente que hace que sea muy fácil uso de una base común la forma en que se podría datos globales descuidadamente han utilizado .... En retrospectiva, creo que es protegida un caso en "buenos" y los argumentos la moda han vencido mi mejor juicio y mis reglas de oro para la aceptación de nuevos caracteristicas.

+5

También dijo que pensaba que las funciones protegidas eran una buena idea. El problema aquí es el uso de "usar" (que sugeriría que es malo) en lugar del uso de "protegido". –

+0

No. El problema con la protección es que realmente no proporciona ninguna protección (ese era el argumento de Bjoarne). Para obtener acceso al método, todo lo que necesita hacer es heredar de la clase y así romper la encapsulación. En afecto protegido no es más seguro que usar público. –

+2

@Martin, el libro dice funciones protegidas: bueno, datos protegidos: malo. ¿Lo has leído realmente? –

18

Lo has hecho por ti mismo.
Se puede escribir

class Y : public X 
{ 
public: 
    void doA() 
    { 
     protectedFunction(); 
    } 
}; 

int main() 
{ 
    Y y1; 
    y1.doA(); 
} 

no veo ninguna razón para preocuparse por ello.
Las funciones protegidas son piezas de lógica reutilizable en el árbol de herencia. Puede ocultarlos si hay alguna lógica o restricción interna o, como en su caso, puede exponerlo si está seguro de que esto no dañará a nadie.

11

Creo que fue el propio Stroustrup quien dijo que las características de encapsulación e integridad de datos integradas en C++ están diseñadas para mantener a las personas honestas honestas, no para detener a los delincuentes.

+4

Me encantan algunas de las respuestas en las preguntas frecuentes de C++ para este tipo de preguntas. '¿Cómo puedo prohibir que otras personas ...?' - escriba un comentario que diga 'y cómo realmente les impido hacerlo ...' - escriba un comentario indicando que serán despedidos si lo hacen. 'Pero qué pasa si realmente, realmente quiero impedir ...' - despedir al tipo. –

3

No. protectedFunction() está protegido. Puede llamarlo desde clases derivadas, por lo que podría haberlo llamado directamente desde un miembro público de Y, y llamar a este miembro público desde main().

Si usted podría haber hecho eso con un método privado, no habría sido un problema ... (Editado para ser un poco más claro).

+0

-1: El código en cuestión no llama al método protegido del contexto de la clase derivada. El método protegido se llama desde el alcance de main(). Si elimina el declarante 'que usa', el código ya no se compila. –

+0

@John, ¡esto es obvio! Esto es solo un atajo, en lugar de hacer la función "trampolín" usted mismo. No aumenta ni disminuye el nivel de protección de la función. – Hexagon

+1

@John Dibling: lo que significa que el programa escritor expuso la función deliberadamente. Hubiera sido casi tan fácil dar una bofetada a una función de envoltorio público al respecto. No veo ninguna razón para un voto negativo aquí. –

1

El diseñador de clases debe saber que declarar una función o variable miembro (aunque todas las variables deben ser privadas, realmente) como protegidas es casi lo mismo que declararla pública (como demostró que es fácil acceder a cosas protegidas). Teniendo esto en cuenta, tenga cuidado al implementar esas funciones en la clase base para dar cuenta del uso "inesperado". No es una violación de la encapsulación porque puede acceder a ella y exponerla.

protected es solo una manera más complicada de declarar algo público.

+0

Más como una forma de hacer posible que se lo declare público, diría yo. –

2

Desde el punto de vista del lenguaje, que no es más de una violación de la encapsulación de crear un método delegado en el objeto derivado:

class Y : public X 
{ 
public: 
    void protectedFunction() { 
     X::protectedFunction(); 
    } 
}; 

Una vez que se protege un método, se ofrece a las clases que se derivan para hacer con él como lo deseen. La intención de la declaración using no cambiaba el acceso a métodos heredados, sino que evitaba problemas con la ocultación de métodos (donde un método público en X estaría oculto en el tipo Y si se define una sobrecarga diferente).

Ahora, es un hecho que debido a las reglas del lenguaje se puede usar para cambiar el nivel de acceso para los métodos 'usados' como en su ejemplo, pero eso realmente no abre ningún agujero en el sistema. Al final, las clases derivadas no pueden hacer nada más que lo que podían hacer antes.

Si la pregunta real es si el uso particular es una violación de la encapsulación desde un punto de vista de diseño (donde el diseño aquí es diseño de aplicación, no de lenguaje), entonces muy probablemente sí. Exactamente de la misma manera que hacer públicos los datos o métodos internos. Si ha sido diseñado para ser protegida por el diseñador inicial de X, entonces es probable que Y implementador no quería que fuera público ... (u ofrecer un método de delegación para el mismo propósito)

+1

sí, buena explicación, totalmente de acuerdo –

2

Para utilizar el método público que debe tener una instancia de Y esto no funcionará:

int main() 
{ 
    X y1; 
    y1.protectedFunction(); 
} 

Si Y en su nueva definición expone una nueva interfaz que depende de la Y. X sigue estando protegida.

2

En C++ incluso el modificador privado no garantiza la encapsulación. Nadie puede evitar que te pegues un tiro en el pie.

Herb Sutter en su artículo "Uses and Abuses of Access Rights" muestra diferentes maneras de cómo se puede "romper la encapsulación".

Aquí un ejemplo divertido del artículo de Herb.

Mal macro magia

#define protected public // illegal 
#include "X.h" 
//... 
X y1; 
y1.protectedFunction(); 
+1

Eso es un comportamiento indefinido, en parte porque rompe la regla de una definición. Incluso si la redefinición de "protegido" fuera legal, no podría contar con que los miembros de la clase estén en el mismo orden. –

+1

Sí, sé sobre el comportamiento indefinido. Quiero mostrar que ESTO es un truco, que "rompa el encapsulatino", pero que la reducción de la protección no lo es. –

1

No, realmente no veo el problema.

En lugar de using, que podría haber hecho esto:

class X 
{ 
protected: 
    void protectedFunction() { cout << "i am protected" ; } 
}; 

class Y : public X 
{ 
public: 
    void protectedFunction() { X::protectedFunction(); } 
}; 

Cualquier clase puede tomar cualquier miembro que es visible a ella, y decide exponerlo públicamente. Puede ser un mal diseño de clase para hacerlo, pero ciertamente no es un defecto en el lenguaje. Todo el punto en los miembros privados o protegidos es que la clase misma debe decidir quién debe tener acceso al miembro. Y si la clase decide "Voy a dar acceso a todo el mundo", entonces así es como está diseñada la clase.

Si seguimos su lógica, entonces getter y setters violan la encapsulación también. Y a veces lo hacen. Pero no porque el lenguaje esté roto. Simplemente porque estás eligiendo diseñar clases rotas.

Al hacer que un miembro sea protegido, le da a las clases derivadas la libertad de hacer haciendo que les guste con el miembro. Pueden verlo, para poder modificarlo o exponerlo públicamente. Eligió hacer esto posible cuando hizo que el miembro estuviera protegido. Si no quieres eso, deberías haberlo hecho en privado.

1

Personalmente, creo que esta pregunta debería responderse más como una cuestión de diseño que como una cuestión tecnológica.

Me gustaría preguntar: "¿Cuál era el objetivo del método protegido en primer lugar?" ¿Era un método que solo deberían llamar las subclases, o es un método que las subclases deben anular? Sin embargo, puede ser un método que no se espera en una clase base genérica, pero que quizás se espera en una subclase. Para el cliente de la clase base nunca supieron acerca de ese método protegido. El creador de la subclase eligió expandir el contrato y ahora ese método protegido es público. La pregunta realmente no debería ser si C++ lo permite, pero es correcto para su clase, contrato y futuros mantenedores. Claro que podría ser un mal olor, pero realmente necesita hacerlo bien para el caso de uso involucrado.

Si hace que el método protegido sea público, asegúrese de proporcionar adecuadamente la documentación interna para el responsable que explica la razón por la cual se toma esta decisión en particular.

En general, por razones de seguridad como se mencionó anteriormente, es probable que desee utilizar una función de delegado (con un nombre diferente) en la subclase. Entonces, en lugar de "get (int)", podrías tener "getAtIndex (int)" o algo así. Esto le permite refactorizar/proteger/abstraer/documentar más fácilmente eso en el futuro.

Cuestiones relacionadas