¿Por qué se describen las variables privadas en el archivo de encabezado de acceso público?

11

Bien, espero que esta sea una pregunta suficientemente subjetiva para los programadores, pero aquí va. Continuamente estoy ampliando mi conocimiento de idiomas y prácticas de ingeniería de software ... y me he encontrado con algo que no tiene ningún sentido para mí.

En C ++, las declaraciones de clase incluyen private:métodos y parámetros en el archivo de encabezado, que, en teoría, es lo que le pasa al usuario para que lo incluya si lo convierte en una biblioteca.

En Objective-C, @interfacehacemos prácticamente lo mismo, obligándote a enumerar tus miembros privados (al menos, hay una manera de obtener métodos privados en el archivo de implementación).

Por lo que puedo decir, Java y C # le permiten proporcionar una interfaz / protocolo que puede declarar todas las propiedades / métodos de acceso público y le da al codificador la capacidad de ocultar todos los detalles de implementación en el archivo de implementación.

¿Por qué? La encapsulación es uno de los principios principales de OOP, ¿por qué C ++ y Obj-C carecen de esta capacidad básica? ¿Existe algún tipo de mejor práctica para Obj-C o C ++ que oculte toda la implementación?

Gracias,

Stephen Furlani
fuente
44
Siento tu dolor. Me escapé gritando desde C ++ la primera vez que agregué un campo privado a una clase y tuve que volver a compilar todo lo que lo usaba.
Larry Coleman
@ Larry, no me importa demasiado, pero parece ser anunciado como un gran lenguaje OO, pero ni siquiera puede encapsular "correctamente".
Stephen Furlani
2
No te creas la exageración. Hay mejores formas de hacer OO, tanto en los campos de mecanografía estáticos como dinámicos.
Larry Coleman
Es un gran lenguaje en algunos aspectos, pero no por sus habilidades de OO, que son un injerto de Simula 67 en C.
David Thornley
Deberías hacer algo de programación en C. Entonces comprenderá mejor por qué estos lenguajes son como son, incluido Java C #, etc.
Henry

Respuestas:

6

La pregunta es si el compilador necesita saber qué tan grande es un objeto. Si es así, entonces el compilador tiene que saber acerca de los miembros privados para contarlos.

En Java, hay tipos y objetos primitivos, y todos los objetos se asignan por separado, y las variables que los contienen son realmente punteros. Por lo tanto, dado que un puntero es un objeto de tamaño fijo, el compilador sabe cuán grande representa una variable, sin conocer el tamaño real del objeto señalado. El constructor maneja todo eso.

En C ++, es posible tener objetos representados localmente o en el montón. Por lo tanto, el compilador necesita saber qué tan grande es un objeto, para poder asignar una variable o matriz local.

A veces es deseable dividir la funcionalidad de la clase en una interfaz pública y una privada todo lo demás, y ahí es donde entra en juego la técnica PIMPL (puntero a IMPLementation). La clase tendrá un puntero a una clase de implementación privada, y los métodos de la clase pública pueden llamar ese.

David Thornley
fuente
¿No puede el compilador mirar a la biblioteca de objetos para obtener esta información? ¿Por qué esta información no puede ser legible por máquina en lugar de legible por humanos?
Stephen Furlani
@Stephen: en el momento de la compilación, no hay necesariamente una biblioteca de objetos. Dado que el tamaño de los objetos afecta el código compilado, debe conocerse en el momento de la compilación, no solo en el momento del enlace. Además, Ah puede definir la clase A, Bh para definir la clase B y las definiciones de función en A.cpp para utilizar objetos de la clase B y viceversa. En ese caso, sería necesario compilar cada uno de A.cpp y B.cpp antes que el otro, si toma el tamaño del objeto del código del objeto.
David Thornley
¿No se puede vincular durante la compilación? Confieso que no conozco los detalles a esa profundidad, pero si la interfaz de un objeto expone su contenido, ¿no pueden exponerse esos mismos contenidos desde una biblioteca u otro componente legible por máquina? deben definirse en un archivo de encabezado de acceso público? Entiendo que esto es parte de la mayoría de los lenguajes C, pero ¿ tiene que ser así?
Stephen Furlani
1
@Stephen: la vinculación solo se puede hacer durante la compilación si todos los componentes apropiados están allí. Incluso entonces, sería un proceso complicado, que implica una compilación parcial (todo menos los tamaños de los objetos), enlaces parciales (para obtener los tamaños de los objetos), luego la compilación final y los enlaces. Dado que C y C ++ son lenguajes relativamente antiguos, eso simplemente no iba a suceder. Presumiblemente, alguien podría diseñar un nuevo lenguaje sin archivos de encabezado, pero no sería C ++.
David Thornley
14

Debido al diseño de C ++, para crear un objeto en la pila, el compilador debe saber qué tan grande es. Para hacer esto, debe tener todos los campos presentes en el archivo de encabezado, ya que eso es todo lo que el compilador puede ver cuando se incluye el encabezado.

Por ejemplo, si define una clase

class Foo {
    public int a;
    private int b;
};

entonces sizeof(Foo)es sizeof(a) + sizeof(b). Si hubiera algún mecanismo para separar los campos privados, entonces el encabezado podría contener

class Foo {
    public int a;
};

con sizeof(Foo) = sizeof(a) + ???.

Si realmente quieres ocultar datos privados, prueba el modismo de pimpl, con

class FooImpl;
class Foo {
    private FooImpl* impl;
}

en el encabezado y una definición para FooImplsolo en Fooel archivo de implementación.

Scott Wales
fuente
Oh. No tenía idea de que este fuera el caso. ¿Es por eso que lenguajes como Java, C # y Obj-C tienen "objetos de clase" para que puedan preguntarle a la clase qué tan grande debe ser el objeto?
Stephen Furlani
44
Intente usar un pimpl, un puntero a una implementación privada. Como todos los punteros de clase son del mismo tamaño, no tiene que definir la clase de implementación, solo declare.
Scott Wales
Creo que debería ser una respuesta en lugar de un comentario.
Larry Coleman
1

Todo esto se reduce a la elección del diseño.

Si realmente desea ocultar los detalles de la implementación privada de la clase C ++ u Objective-C, puede proporcionar una o más interfaces que la clase admita (clase virtual pura C ++, Objective-C @protocol) y / o crear la clase capaz de construirse proporcionando un método de fábrica estático o un objeto de fábrica de clase.

La razón por la que las variables privadas están expuestas en el archivo de encabezado / declaración de clase / interfaz @ es que un consumidor de su clase podría necesitar crear una nueva instancia de la misma y a new MyClass()o [[MyClass alloc]init]en el código del cliente necesita el compilador para comprender qué tan grande es MyClass objeto es para hacer la asignación.

Java y C # también tienen sus variables privadas detalladas en la clase, no son una excepción a esto, pero en mi opinión, el paradigma de la interfaz es mucho más común con esos lenguajes. Es posible que no tenga el código fuente en cada caso, pero hay suficientes metadatos en el código compilado / byte para deducir esta información. Como C ++ y Objective-C no tienen estos metadatos, la única opción son los detalles reales de la interfaz class / @. En el mundo COM de C ++, no expone las variables privadas de ninguna clase, pero puede proporcionar el archivo de encabezado, porque la clase es puramente virtual. También se registra un objeto de fábrica de clase para crear las instancias reales, y hay algunos metadatos en varias formas.

En C ++ y Objective-C, es menos trabajo distribuir el archivo de encabezado en comparación con escribir y mantener una interfaz adicional / @ archivo de protocolo. Esta es una razón por la que ve la implementación privada expuesta tan a menudo.

Otra razón en C ++ son las plantillas: el compilador necesita conocer los detalles de la clase para generar una versión de esa clase especializada para los parámetros proporcionados. El tamaño de los miembros de la clase variaría con la parametrización, por lo que debe tener esta información.

JBRWilkinson
fuente