Sei sulla pagina 1di 18

¿Qué es la "Programación orientada a objetos"?

(Versión revisada de 1991)


Bjarne Stroustrup
AT & T Bell Laboratories
Murray Hill, Nueva Jersey 07974
ABSTRACTO
'' Programación orientada a objetos '' y '' Abstracción de datos '' se han vuelto muy comunes
términos mon. Desafortunadamente, pocas personas están de acuerdo en lo que significan. Ofreceré informal
definiciones que parecen tener sentido en el contexto de lenguajes como Ada, C ++, Modula-
2, Simula y Smalltalk. La idea general es equiparar '' soporte para abstracción de datos ''
con la capacidad de definir y usar nuevos tipos y equiparar '' soporte para programas orientados a objetos
Gramática con la capacidad de expresar jerarquías de tipos. Características necesarias para apoyar
estos estilos de programación en un lenguaje de programación de propósito general serán discutidos.
La presentación se centra en C ++, pero no se limita a las instalaciones proporcionadas por esa lengua.
guage
1. Introducción
No todos los lenguajes de programación pueden estar '' orientados a objetos ''. Sin embargo, se han hecho afirmaciones en el sentido de
que
APL, Ada, Clu, C ++, LOOPS y Smalltalk son lenguajes de programación orientados a objetos. He escuchado dis-
cusiones de diseño orientado a objetos en C, Pascal, Modula-2 y CHILL. ¿Podría algún lugar ser propicio
nentes de la programación orientada a objetos de Fortran y Cobol? Creo que debe haberlo. '' Orientada a objetos '' tiene
muchos círculos se convierten en un sinónimo de alta tecnología para "bueno", y cuando se examinan las discusiones en el comercio
presione, puede encontrar argumentos que parecen reducirse a silogismos como:
Ada es buena
Orientado a objetos es bueno
-----------------------------------
Ada está orientada a objetos
Este artículo presenta una visión de lo que debe significar "orientado a objetos" en el contexto de un propósito general
lenguaje de programación.
§2 Distingue '' programación orientada a objetos '' y '' abstracción de datos '' entre sí y de
otros estilos de programación y presenta los mecanismos que son esenciales para apoyar
ous estilos de programación.
§3 Presenta las características necesarias para que la abstracción de datos sea efectiva.
§4 Discute las instalaciones necesarias para apoyar la programación orientada a objetos.
§5 Presenta algunos límites impuestos a la abstracción de datos y programación orientada a objetos por
arquitecturas de hardware y sistemas operativos.
Los ejemplos se presentarán en C ++. La razón de esto es en parte para introducir C ++ y en parte porque C ++ es
uno de los pocos idiomas que admite la abstracción de datos y la programación orientada a objetos, además de
técnicas de programación tradicionales. Problemas de simultaneidad y soporte de hardware para aplicaciones específicas
construcciones de lenguaje de nivel se ignoran en este documento.

Página 2
-2-
2 Paradigmas de programación
La programación orientada a objetos es una técnica para la programación: un paradigma para escribir '' bueno ''
gramos para un conjunto de problemas. Si el término "lenguaje de programación orientado a objetos" significa algo que debe
significa un lenguaje de programación que proporciona mecanismos que admiten el estilo de programa orientado a objetos bien.
Hay una distinción importante aquí. Se dice que un lenguaje admite un estilo de programación si proporciona instalaciones que lo
hacen conveniente (razonablemente fácil, seguro y eficiente) para usar ese estilo. Un idioma
no admite una técnica si requiere un esfuerzo excepcional o una habilidad excepcional para escribir tales programas; eso
simplemente permite que la técnica sea utilizada. Por ejemplo, puede escribir programas estructurados en Fortran, escribir programas
seguros en C, y use abstracción de datos en Modula-2, pero es innecesariamente difícil de hacer porque
estos lenguajes no son compatibles con esas técnicas.
El apoyo a un paradigma no solo se presenta en la forma obvia de las instalaciones de lenguaje que permiten el uso directo
del paradigma, pero también en la forma más sutil de controles en tiempo de compilación y / o tiempo de ejecución contra
desviación del paradigma. La comprobación de tipos es el ejemplo más obvio de esto; detección de ambigüedad
Las comprobaciones de tiempo y tiempo de ejecución se pueden usar para extender el soporte lingüístico para los
paradigmas. Instalaciones extralingüísticas
como las bibliotecas estándar y los entornos de programación también pueden proporcionar un apoyo significativo paradigmas.
Un lenguaje no es necesariamente mejor que otro porque posee una característica que el otro no posee.
Hay muchos ejemplos de lo contrario. La cuestión importante no es tanto qué características tiene un lenguaje
pero las características que posee son suficientes para soportar los estilos de programación deseados en el
áreas de aplicación deseadas:
[1] Todas las características deben estar integradas limpia y elegantemente en el idioma.
[2] Debe ser posible usar las características en combinación para lograr soluciones que de otro modo tendrían
Se requieren características adicionales separadas.
[3] Debería haber tan pocas características falsas y de '' propósito especial '' como sea posible.
[4] Una característica debería ser tal que su implementación no imponga gastos indirectos significativos en los programas
que no lo requieren
[5] Un usuario solo necesita conocer el subconjunto del lenguaje utilizado explícitamente para escribir un programa.
Los dos últimos principios se pueden resumir como "lo que no sabes no te hará daño". Si hay alguno,
dudas sobre la utilidad de una característica, es mejor dejarla fuera. Es mucho más fácil agregar una función a un idioma
que eliminar o modificar uno que ha encontrado su camino en los compiladores o la literatura.
Ahora presentaré algunos estilos de programación y los mecanismos de lenguaje clave necesarios para apoyarlos . La presentación de
las características del lenguaje no pretende ser exhaustiva.
2.1 Programación procedimental
El paradigma de programación original (y probablemente el más utilizado) es:
Decide qué procedimientos quieres;
utiliza los mejores algoritmos que puedas encontrar
El foco está en el diseño del procesamiento, el algoritmo necesario para realizar el cálculo deseado.
Los idiomas soportan este paradigma por medios para pasar argumentos a funciones y devolver valores de
funciones. La literatura relacionada con esta forma de pensar está llena de discusión sobre las formas de distinguir diferentes tipos de
argumentos, diferentes tipos de funciones (procedimientos,
dientes, macros, ...), etc. Fortran es el lenguaje de procedimiento original; Algol60, Algol68, C y Pascal son
inventos posteriores en la misma tradición.
Un ejemplo típico de "buen estilo" es una función de raíz cuadrada. Produce claramente un resultado dado un argumento. Para hacer
esto, realiza un cálculo matemático bien entendido:
doble sqrt (doble arg)
{
// el código para calcular una raíz cuadrada
}

Página 3
-3-
void some_function ()
{
double root2 = sqrt (2);
// ...
}
Desde el punto de vista de la organización de un programa, las funciones se usan para crear orden en un laberinto de algoritmos.
2.2 Ocultación de datos
Con los años, el énfasis en el diseño de los programas se ha alejado del diseño de procedimientos
hacia la organización de datos. Entre otras cosas, esto refleja un aumento en el tamaño del programa. Un conjunto
de los procedimientos relacionados con los datos que manipulan a menudo se llama un módulo . El paradigma de programación
se convierte en:
Decide qué módulos quieres;
particionar el programa para que los datos estén ocultos en los módulos.
Este paradigma también se conoce como el "principio de ocultación de datos". Donde no hay una agrupación de procedimientos
con datos relacionados, el estilo de programación de procedimiento es suficiente. En particular, las técnicas para diseñar
'' buenos procedimientos '' ahora se aplican para cada procedimiento en un módulo. El ejemplo más común es una definición de un
módulo de pila. Los principales problemas que deben resolverse para una buena solución son:
[1] Proporcione una interfaz de usuario para la pila (por ejemplo, funciones push () y pop () ).
[2] Asegúrate de que solo se pueda acceder a la representación de la pila (por ejemplo, un vector de elementos)
a través de esta interfaz de usuario.
[3] Asegúrese de que la pila esté inicializada antes de su primer uso.
Aquí hay una interfaz externa plausible para un módulo de pila:
// declaración de la interfaz de la pila de caracteres del módulo
char pop ();
void push (char);
const stack_size = 100;
Suponiendo que esta interfaz se encuentre en un archivo llamado stack.h , las '' partes internas '' se pueden definir así:
#include "stack.h"
static char v [stack_size];
// '' static '' significa local para este archivo / módulo
char estático * p = v;
// la pila está inicialmente vacía
char pop ()
{
// verificar si hay desbordamiento y pop
}
void push (char c)
{
// verifica el desbordamiento y el empuje
}
Sería bastante factible cambiar la representación de esta pila a una lista vinculada. Un usuario no
tener acceso a la representación de todos modos (ya que v y p se declararon estáticos , que es local para el
archivo / módulo en el que fueron declarados). Tal pila se puede usar así:

Página 4
-4-
#include "stack.h"
void some_function ()
{
char c = pop (push ('c'));
if (c! = 'c') error ("imposible");
}
Pascal (como se definió originalmente) no proporciona ninguna instalación satisfactoria para dicha agrupación: la única
mecanismo para ocultar un nombre de '' el resto del programa '' es hacerlo local para un procedimiento. Esto lleva
a anidamientos de procedimientos extraños y a la dependencia excesiva de datos globales.
C es un poco mejor. Como se muestra en el ejemplo anterior, puede definir un "módulo" agrupando
funciones relacionadas y definiciones de datos juntas en un solo archivo fuente. El programador puede controlar
qué nombres ven el resto del programa (un nombre puede ser visto por el resto del programa a menos que
ha sido declarado estático ). En consecuencia, en C puede lograr un grado de modularidad. Sin embargo, hay
no es un paradigma generalmente aceptado para usar esta instalación y la técnica de confiar en declaraciones estáticas
ciones es bastante bajo nivel.
Uno de los sucesores de Pascal, Modula-2, va un poco más allá. Se formaliza el concepto de un módulo, haciendo
es una construcción de lenguaje fundamental con declaraciones de módulo bien definidas, control explícito de los ámbitos de
nombres (importación / exportación), un mecanismo de inicialización del módulo, y un conjunto de generalmente conocido y aceptado
estilos de uso.
Las diferencias entre C y Modula-2 en esta área se pueden resumir diciendo que C solo permite
la descomposición de un programa en módulos, mientras que Modula-2 es compatible con esa técnica.
2.3 Abstracción de datos
La programación con módulos conduce a la centralización de todos los datos de un tipo bajo el control de un tipo
módulo de gerente Si uno quisiera dos pilas, una definiría un módulo administrador de pila con una interfaz
Me gusta esto:
clase stack_id; // stack_id es un tipo
// no se conocen detalles sobre stacks o stack_ids aquí
stack_id create_stack (int size); // crea una pila y devuelve su identificador
destroy_stack (stack_id);
// llamar cuando la pila ya no es necesaria
void push (stack_id, char);
char pop (stack_id);
Esto es sin duda una gran mejora sobre el desorden no estructurado tradicional, pero los '' tipos '' implementaron este
manera son claramente muy diferentes de los tipos incorporados en un idioma. Cada módulo de administrador de tipos debe definir
un mecanismo separado para crear '' variables '' de su tipo, no hay una norma establecida para asignar el objeto
identificadores, una '' variable '' de ese tipo no tiene un nombre conocido por el compilador o el entorno de programación,
ni esas '' variables '' no obedecen las reglas de alcance habituales o las reglas de aprobación de argumentos.
Un tipo creado a través de un mecanismo de módulo es en aspectos más importantes diferentes de un tipo incorporado
y disfruta de un soporte inferior al soporte provisto para los tipos incorporados. Por ejemplo:
void f ()
{
stack_id s1;
stack_id s2;
s1 = create_stack (200);
// Vaya: se olvidó de crear s2
char c1 = pop (s1, push (s1, 'a'));
if (c1! = 'c') error ("imposible");
Página 5
-5-
char c2 = pop (s2, push (s2, 'a'));
if (c2! = 'c') error ("imposible");
destruir (s2);
// Oops: se olvidó de destruir s1
}
En otras palabras, el concepto de módulo que admite el paradigma de ocultación de datos permite este estilo de programa.
ming, pero no lo admite.
Los lenguajes como Ada, Clu y C ++ atacan este problema al permitir que un usuario defina los tipos que se comportan
en (casi) la misma manera que los tipos incorporados. Tal tipo se denomina a menudo tipo de datos abstractos †. El programa-
el paradigma ming se convierte en:
Decide qué tipos quieres;
proporcionar un conjunto completo de operaciones para cada tipo.
Donde no hay necesidad de más de un objeto de un tipo, los datos ocultan el estilo de programación usando
Ules es suficiente Los tipos aritméticos como los números racionales y complejos son ejemplos comunes de usuarios
tipos definidos:

class complex {
double re, im;
public:
complex(double r, double i) { re=r; im=i; }
complex(double r) { re=r; im=0; } // float->complex conversion
friend complex operator+(complex, complex);
friend complex operator-(complex, complex); // binary minus
friend complex operator-(complex); // unary minus
friend complex operator*(complex, complex);
friend complex operator/(complex, complex);
// ...
};
El complejo de declaración de clase (es decir, tipo definido por el usuario) especifica la representación de un complejo
número y el conjunto de operaciones en un número complejo. La representación es privada ; es decir, re e im
solo son accesibles a las funciones especificadas en la declaración del complejo de clases . Tales funciones pueden ser
definido así:
operador complejo + (complejo a1, complejo a2)
{
complejo de retorno (a1.re + a2.re, a1.im + a2.im);
}
y usado así:
complejo a = 2.3;
complejo b = 1 / a;
complejo c = a + b * complejo (1,2.3);
// ...
c = - (a / b) +2;
La mayoría de los módulos, pero no todos, se expresan mejor como tipos definidos por el usuario. Para conceptos donde el '' módulo
la representación '' es deseable incluso cuando está disponible una instalación adecuada para definir tipos, el programador puede
declare un tipo y solo un solo objeto de ese tipo. Alternativamente, un idioma puede proporcionar un módulo
__________________
† Prefiero el término "tipo definido por el usuario": " esos tipos no son" abstractos "; son tan reales como int y float . '' - Doug
McIlroy. Un
La definición alternativa de tipos de datos abstractos requeriría una especificación matemática '' abstracta '' de todos los tipos (tanto
incorporados como usuarios).
definido). Lo que se conoce como tipos en este documento, dadas tales especificaciones, serían especificaciones concretas de tales
conceptos verdaderamente abstractos.
entidades.

Página 6
-6-
concepto además y diferente del concepto de clase.
2.4 Problemas con la abstracción de datos
Un tipo de datos abstracto define una especie de cuadro negro. Una vez que ha sido definido, realmente no interactúa
con el resto del programa No hay forma de adaptarlo a nuevos usos, excepto modificando su definición.
Esto puede conducir a rigidez severa. Considere definir una forma de tipo para usar en un sistema de gráficos.
Supongamos por el momento que el sistema debe admitir círculos, triángulos y cuadrados. Asume también que tú
tener algunas clases:
class point{ /* ... */ };
class color{ /* ... */ };
Puede definir una forma como esta:

enum kind { circle, triangle, square };


class shape {
point center;
color col;
kind k;
// representation of shape
public:
point where() { return center; }
void move(point to) { center = to; draw(); }
void draw();
void rotate(int);
// more operations
};

El '' campo de tipo '' k es necesario para permitir operaciones tales como draw () y rotate () para determinar qué
tipo de forma que están tratando (en un lenguaje similar a Pascal, uno podría usar un registro de variante con la etiqueta k ).
La función draw () podría definirse así:

void shape::draw()
{
switch (k) {
case circle:
// draw a circle
break;
case triangle:
// draw a triangle
break;
case square:
// draw a square
}
}

Esto es un desastre. Funciones como draw () deben '' conocer sobre '' todos los tipos de formas que existen. Ahí-
el código para cada función crece cada vez que se agrega una nueva forma al sistema. Si defines un
nueva forma, cada operación en una forma debe ser examinada y (posiblemente) modificada. No puedes agregar
una nueva forma para un sistema a menos que tenga acceso al código fuente para cada operación. Desde que agregué un nuevo
la forma implica '' tocar '' el código de cada operación importante en las formas, requiere gran habilidad y
potencialmente introduce errores en el código manejando otras formas (más antiguas). La elección de la representación de
formas particulares pueden verse severamente obstaculizadas por el requisito de que (al menos parte de) su representación
debe encajar en el marco de tamaño típicamente fijo presentado por la definición de la forma de tipo general .
2.5 Programación orientada a objetos
El problema es que no hay distinción entre las propiedades generales de cualquier forma (una forma tiene un
color, se puede dibujar, etc.) y las propiedades de una forma específica (un círculo es una forma que tiene un radio, es
dibujado por una función de dibujo circular, etc.). Expresar esta distinción y aprovecharla define
programación orientada a objetos. Un lenguaje con construcciones que permite que esta distinción se exprese y

Página 7
-7-
usado soporta programación orientada a objetos. Otros idiomas no.
El mecanismo de herencia Simula proporciona una solución. Primero, especifique una clase que defina el general
propiedades de todas las formas:
class shape {
point center;
color col;
// ...
public:
point where() { return center; }
void move(point to) { center = to; draw(); }
virtual void draw();
virtual void rotate(int);
// ...
};

Las funciones para las cuales se puede definir la interfaz de llamada, pero donde la implementación no puede ser
definidos a excepción de una forma específica, han sido marcados como "virtuales" (el término Simula y C ++ para "puede ser
redefinido más tarde en una clase derivada de este ''). Dada esta definición, podemos escribir funciones generales
manipulando formas:

void rotate_all(shape* v, int size, int angle)


// rotate all members of vector "v" of size "size" "angle" degrees
{
for (int i = 0; i < size; i++) v[i].rotate(angle);
}

Para definir una forma particular, debemos decir que es una forma y especificar sus propiedades particulares (incluyendo
ing las funciones virtuales).

class circle : public shape {


int radius;
public:
void draw() { /* ... */ };
void rotate(int) {} // yes, the null function
};

En C ++, se dice que el círculo de clase se deriva de la forma de clase, y se dice que la forma de clase es una base de
círculo de clase Una terminología alternativa llama subclase y superclase de círculo y forma , respec
tively
El paradigma de programación es:
Decide qué clases quieres;
proporcionar un conjunto completo de operaciones para cada clase;
hacer explícita la comunidad mediante el uso de la herencia.
Donde no existe tal comunalidad, la abstracción de datos es suficiente. La cantidad de comunalidad entre
tipos que pueden explotarse mediante el uso de la herencia y las funciones virtuales es la prueba de fuego de la aplicabilidad de
programación orientada a objetos a un área de aplicación. En algunas áreas, como los gráficos interactivos, hay
un alcance claramente enorme para la programación orientada a objetos. Para otras áreas, como la aritmética clásica
tipos y cálculos basados en ellos, parece que apenas hay espacio para algo más que la abstracción de datos
y las instalaciones necesarias para el soporte de la programación orientada a objetos parecen innecesarias †.
Encontrar similitudes entre los tipos de un sistema no es un proceso trivial. La cantidad de comunalidad para
ser explotado se ve afectado por la forma en que se diseña el sistema. Al diseñar un sistema, la comunalidad debe
buscar activamente, tanto mediante el diseño de clases específicamente como bloques de construcción para otros tipos, y mediante el
examen
las clases para ver si exhiben similitudes que pueden explotarse en una clase base común.
__________________
† Sin embargo, las matemáticas más avanzadas pueden beneficiarse del uso de la herencia: los campos son especializaciones de
anillos; espacios vectoriales a
caso especial de módulos.

Página 8
-8-
Para intentar explicar qué programación orientada a objetos es sin recurrir a programación específica
construcciones del lenguaje ver Nygaard [13] y Kerr [9]. Para un estudio de caso en programación orientada a objetos ver
Cargill [4].
3 Soporte para la abstracción de datos
El soporte básico para la programación con abstracción de datos consiste en facilidades para definir un conjunto de operaciones
aciones para un tipo y para restringir el acceso a objetos del tipo a ese conjunto de operaciones. Una vez que eso es
hecho, sin embargo, el programador pronto encuentra que los refinamientos del lenguaje son necesarios para una definición
conveniente
y uso de los nuevos tipos. La sobrecarga del operador es un buen ejemplo de esto.
3.1 Inicialización y limpieza
Cuando la representación de un tipo está oculta, se debe proporcionar un mecanismo para que el usuario inicialice
variables de ese tipo. Una solución simple es requerir que un usuario llame a alguna función para inicializar una variable
antes de usarlo Por ejemplo:
clase vector {
int sz;
En televisión;
público:
void init (int tamaño); // call init para inicializar sz y v
// antes del primer uso de un vector
// ...
};
vector v;
// no use v aquí
v.init (10);
// usar v aquí
Esto es propenso a errores y poco elegante. Una mejor solución es permitir que el diseñador de un tipo brinde un distintivo
Función deseada para hacer la inicialización. Dada tal función, asignación e inicialización de una variable
se convierte en una operación única (a menudo llamada creación de instancias) en lugar de dos operaciones separadas. Tal inicial
La función de información a menudo se llama un constructor. En los casos donde la construcción de objetos de un tipo no es trivial,
uno a menudo necesita una operación complementaria para limpiar objetos después de su último uso. En C ++, tal limpieza
la función se llama un destructor. Considere un tipo de vector:
clase vector {
int sz;
// cantidad de elementos
En televisión;
// puntero a enteros
público:
vector (int);
// constructor
~ vector ();
// destructor
int & operator [] (índice int); // operador de subíndice
};
El constructor vectorial se puede definir para asignar espacio como este:
vector :: vector (int s)
{
if (s <= 0) error ("tamaño de vector malo");
sz = s;
v = new int [s]; // asignar una matriz de enteros "s"
}
El vector destructor libera el almacenamiento utilizado:
vector :: ~ vector ()
{
eliminar v;
// desasignar la memoria apuntada por v
}
C ++ no es compatible con la recolección de basura. Esto se compensa, sin embargo, al habilitar un tipo para mantener

Página 9
-9-
su propia administración de almacenamiento sin requerir la intervención de un usuario. Este es un uso común para el
mecanismo constructor / destructor, pero muchos usos de este mecanismo no están relacionados con la administración de
almacenamiento.
3.2 Asignación e Inicialización
Controlar la construcción y destrucción de objetos es suficiente para muchos tipos, pero no para todos. Puede
también será necesario controlar todas las operaciones de copia. Considere el vector de clase :
vector v1 (100);
vector v2 = v1; // hacer un nuevo vector v2 inicializado a v1
v1 = v2;
// asigna v2 a v1
Debe ser posible definir el significado de la inicialización de v2 y la asignación a v1 . Alterna-
debe ser posible prohibir tales operaciones de copia; preferiblemente ambas alternativas deben estar disponibles
poder. Por ejemplo:
clase vector {
En televisión;
int sz;
público:
// ...
void operator = (vector &); // asignación
vector (vector &);
// inicialización
};
especifica que las operaciones definidas por el usuario se deben usar para interpretar la asignación e inicialización del vector .
La asignación se puede definir así:
vector :: operator = (vector & a) // verificar tamaño y copiar elementos
{
if (sz! = a.sz) error ("tamaño de vector malo para =");
para (int i = 0; i <sz; i ++) v [i] = av [i];
}
Dado que la operación de asignación se basa en el '' valor anterior '' del vector al que se está asignando, la inicialización
la operación debe ser diferente. Por ejemplo:
vector :: vector (vector & a)
// inicializa un vector de otro vector
{
sz = a.sz;
// mismo tamaño
v = new int [sz];
// asignar matriz de elementos
para (int i = 0; i <sz; i ++) v [i] = av [i];
// copiar elementos
}
En C ++, un constructor de la forma X (X &) define toda inicialización de objetos de tipo X con otro objeto
de tipo X Además de la inicialización explícita, los constructores de la forma X (X &) se utilizan para manejar argumentos
pasados '' por valor '' y valores de retorno de función.
En C ++, la asignación de un objeto de clase X puede prohibirse declarando la asignación como privada:
clase X {
operador vacío = (X &); // solo los miembros de X pueden
X (X &);
// copia una X
// ...
público:
// ...
};
Ada no admite constructores, destructores, sobrecarga de asignación o control de
paso de argumento y retorno de función. Esto limita severamente la clase de tipos que se pueden definir y las fuerzas
el programador vuelve a las "técnicas de ocultación de datos"; es decir, el usuario debe diseñar y usar mods de administrador de tipos
tipos en lugar de los tipos adecuados.

Página 10
- 10 -
3.3 tipos parametrizados
¿Por qué querrías definir un vector de números enteros de todos modos? Un usuario típicamente necesita un vector de ele-
de algún tipo desconocido para el escritor del tipo de vector . En consecuencia, el tipo de vector debe ser
expresado de tal manera que toma el tipo de elemento como argumento:
clase vector <clase T> {
// vector de elementos del tipo T
Televisión;
int sz;
público:
vector (int s)
{
if (s <= 0) error ("tamaño de vector malo");
v = new T [sz = s];
// asigna una matriz de "s" "T" s
}
T & operator [] (int i);
int size () {return sz; }
// ...
};
Ahora se pueden definir y usar vectores de tipos específicos:
vector <int> v1 (100);
// v1 es un vector de 100 enteros
vector <complejo> v2 (200); // v2 es un vector de 200 números complejos
v2 [i] = complejo (v1 [x], v1 [y]);
Ada, Clu y ML admiten tipos parametrizados. Desafortunadamente, C ++ no lo hace; la notación utilizada aquí es sim-
capa diseñada para la ilustración. Donde sea necesario, las clases parametrizadas son '' falsificadas '' usando macros. Hay necesidad
no se trata de los gastos generales de tiempo de ejecución en comparación con una clase donde todos los tipos involucrados se
especifican directamente.
Normalmente, un tipo parametrizado tendrá que depender de al menos algún aspecto de un parámetro de tipo. por
Por ejemplo, algunas de las operaciones vectoriales deben suponer que la asignación está definida para objetos del parámetro
tipo. ¿Cómo se puede asegurar eso? Una solución a este problema es requerir al diseñador del parámetro
clase clasificada para indicar la dependencia. Por ejemplo, '' T debe ser un tipo para el cual = está definido ''. Una mejor solución
ción no es ni para tomar una especificación de un tipo de argumento como una especificación parcial. Un compilador puede detectar
una "operación faltante" si se aplica y muestra un mensaje de error como. Por ejemplo:
no se puede definir vector (non_copy) :: operator [] (non_copy &):
type non_copy no tiene operator =
Esta técnica permite la definición de tipos donde la dependencia de atributos de un tipo de parámetro es
a nivel de la operación individual del tipo. Por ejemplo, uno podría definir un vector con un género
operación. La operación de ordenamiento puede usar < , == y = en objetos del tipo de parámetro. Todavía sería
posible definir vectores de un tipo para el que '<' no se definió mientras la operación de clasificación vectorial
no fue invocado en realidad.
Un problema con los tipos parametrizados es que cada creación de instancias crea un tipo independiente. Para examen-
Por ejemplo, el vector de tipo <char> no está relacionado con el vector de tipo <complex> . Idealmente, a uno le
gustaría ser
capaz de expresar y utilizar las características comunes de los tipos generados a partir del mismo tipo parametrizado. por
ejemplo, tanto el vector <char> como el vector <complex> tienen una función size () que es independiente de
el tipo de parámetro. Es posible, pero no trivial, deducir esto de la definición de vector de clase y
luego, permite que size () se aplique a cualquier vector . Un lenguaje interpretado o un idioma que soporte ambos
tipos parametrizados y la herencia tiene una ventaja aquí.
3.4 Manejo de excepciones
A medida que los programas crecen, y especialmente cuando las bibliotecas se usan ampliamente, se establecen estándares para el
manejo de errores (o
más en general: '' circunstancias excepcionales '' se vuelven importantes. Ada, Algol68 y Clu admiten cada uno un
forma estándar de manejo de excepciones. Desafortunadamente, C ++ no. Donde sea necesario, las excepciones son '' falsas ''
usando punteros a funciones, '' objetos de excepción '', '' estados de error '', y la señal de biblioteca C y longjmp
instalaciones. Esto no es satisfactorio en general y ni siquiera proporciona un marco estándar para el error.
dling.
Considera nuevamente el ejemplo vectorial . Lo que debe hacerse cuando un valor de índice fuera de rango es

Página 11
- 11 -
pasado al operador de subíndice? El diseñador de la clase vectorial debería ser capaz de proporcionar un valor predeterminado
comportamiento para esto. Por ejemplo:
clase vector {
...
excepto vector_range {
// define una excepción llamada vector_range
// y especifique el código predeterminado para manejarlo
error ("global: vector range error");
salida (99);
}
}
En lugar de llamar a una función de error, vector :: operator [] () puede invocar el código de manejo de excepciones,
'' elevar la excepción '':
int & vector :: operator [] (int i)
{
if (0 <i || sz <= i) raise vector_range;
devolver v [i];
}
Esto hará que la pila de llamadas se desenrede hasta que se encuentre un manejador de excepciones para vector_range ; esta
controlador que será ejecutado.
Se puede definir un manejador de excepciones para un bloque específico:
void f () {
vector v (10);
tratar {
// los errores aquí son manejados por el local
// manejador de excepciones definido a continuación
// ...
int i = g ();
// g podría causar un error de rango usando algún vector
v [i] = 7;
// error de rango potencial
}
excepto {
vector :: vector_range:
error ("f (): vector range error");
regreso;
}
// los errores aquí son manejados por el global
// manejador de excepciones definido en vector
int i = g ();
// g podría causar un error de rango usando algún vector
v [i] = 7;
// error de rango potencial
}
Hay muchas formas de definir excepciones y el comportamiento de los manejadores de excepciones. La instalación esbozada
aquí se parece a los que se encuentran en Clu y Modula-2 +. Este estilo de manejo de excepciones se puede implementar
para que el código no se ejecute a menos que se genere una excepción † o de forma portátil en la mayoría de las implementaciones C
usando setjmp () y longjmp () . ††
¿Podrían las excepciones, como se definió anteriormente, ser completamente '' falsificadas '' en un lenguaje como C
++? Desafortunadamente,
no. El inconveniente es que cuando se produce una excepción, la pila en tiempo de ejecución debe desenredarse hasta un punto donde
manejador está definido. Hacer esto correctamente en C ++ implica invocar destructores definidos en los ámbitos
involucrado. Esto no lo hace C longjmp () y, en general, no puede hacerlo el usuario.
__________________
† excepto posiblemente por algún código de inicialización al inicio de un programa.
†† vea el manual de la biblioteca C para su sistema.

Pagina 12
- 12 -
3.5 Coerciones
Coacciones definidas por el usuario, como la de números de punto flotante a números complejos implicados por
el complejo constructor (doble) , ha demostrado ser inesperadamente útil en C ++. Tales coacciones pueden ser
aplicado explícitamente o el programador puede confiar en el compilador para agregarlos implícitamente cuando sea necesario y
inequívoco:
complejo a = complejo (1);
complejo b = 1;
// implícito: 1 -> complejo (1)
a = complejo b + (2);
a = b + 2;
// implícito: 2 -> complejo (2)
Las coerciones se introdujeron en C ++ porque la aritmética de modo mixto es la norma en los lenguajes numéricos
trabajo y porque la mayoría de los tipos definidos por el usuario se usan para '' cálculo '' (por ejemplo, matrices, cadenas de caracteres,
y direcciones de máquina) tienen asignaciones naturales ay / o de otros tipos.
Un uso de las coerciones ha demostrado ser especialmente útil desde el punto de vista de la organización de un programa:
complejo a = 2;
complejo b = a + 2; // interpretado como operador + (a, complejo (2))
b = 2 + a;
// interpretado como operador + (complejo (2), a)
Solo se necesita una función para interpretar operaciones '' + '' y los dos operandos se manejan de manera idéntica por
el sistema de tipo. Además, el complejo de clases se escribe sin necesidad de modificar el concepto de
gers para permitir la integración suave y natural de los dos conceptos. Esto está en contraste con un "objeto puro"
sistema orientado '' donde las operaciones se interpretarían así:
a + 2;
// a.operator + (2)
2 + a;
// 2.operador + (a)
por lo que es necesario modificar el entero de la clase para que 2 + a sea legal. La modificación del código existente debe
ser
evitado en la medida de lo posible al agregar nuevas instalaciones a un sistema. Normalmente, la programación orientada a objetos
ofrece instalaciones superiores para agregar a un sistema sin modificar el código existente. En este caso, sin embargo,
las instalaciones de extracción de datos proporcionan una mejor solución.
3.6 Iteradores
Se ha afirmado que un lenguaje que respalde la abstracción de datos debe proporcionar una forma de definir el control
estructuras [11]. En particular, un mecanismo que permite al usuario definir un ciclo sobre los elementos de algunos
El tipo que contiene elementos a menudo es necesario. Esto debe lograrse sin forzar a un usuario a depender de
detalles de la implementación del tipo definido por el usuario. Dado un mecanismo suficientemente poderoso para definir
nuevos tipos y la capacidad de sobrecargar a los operadores, esto se puede manejar sin un mecanismo separado para
definir estructuras de control.
Para un vector, la definición de un iterador no es necesaria ya que un pedido está disponible para un usuario a través del
índices. Definiré uno de todos modos para demostrar la técnica. Hay varios estilos posibles de iteradores.
Mi favorito depende de la sobrecarga del operador de la aplicación de función () †††:
clase vector_iterator {
vector & v;
int i;
público:
vector_iterator (vector & r) {i = 0; v = r; }
int operator () () {return i <v.size ()? v.elem (i ++): 0; }
};
Un vector_iterator ahora se puede declarar y usar para un vector como este:
__________________
††† Este estilo también se basa en la existencia de un valor distinguido para representar el "fin de la iteración". A menudo, en
particular para el puntero C ++
tipos, 0 pueden ser utilizados.

Página 13
- 13 -
vector v (sz);
vector_iterator next (v);
int i;
while (i = next ()) print (i);
Puede haber más de un iterador activo para un solo objeto a la vez, y un tipo puede tener varios diferentes
tipos de iterador definidos para que se puedan realizar diferentes tipos de iteración. Un iterador es más bien
estructura de control simple. Se pueden definir mecanismos más generales. Por ejemplo, el estándar C ++
la biblioteca proporciona una clase de co-rutina [15].
Para muchos tipos de 'contenedor' ', como vector, uno puede evitar la introducción de un tipo de iterador separado por
definir un mecanismo de iteración como parte del tipo en sí. Un vector puede definirse para tener un '' actual
elemento'':
clase vector {
En televisión;
int sz;
int actual;
público:
// ...
int next () {return (current ++ <sz)? v [actual]: 0; }
int prev () {return (0 <- current)? v [actual]: 0; }
};
Entonces la iteración se puede realizar así:
vector v (sz);
int i;
while (i = v.next ()) print (i);
Esta solución no es tan general como la solución del iterador, pero evita la sobrecarga en el importante caso especial
donde solo se necesita un tipo de iteración y donde solo se necesita una iteración a la vez para un vector. Si
necesario, se puede aplicar una solución más general además de esta simple. Tenga en cuenta que el "simple"
La solución requiere más previsión por parte del diseñador de la clase contenedor que la solución del iterador.
La técnica del tipo de iterador también se puede usar para definir iteradores que pueden vincularse a varios contextos diferentes.
tipos de tainer proporcionando así un mecanismo para iterar sobre diferentes tipos de contenedores con un solo iterador
tipo.
3.7 Problemas de implementación
El soporte necesario para la abstracción de datos se proporciona principalmente en forma de funciones de lenguaje implementadas
mencionado por un compilador. Sin embargo, los tipos parametrizados se implementan mejor con el soporte de un enlazador con
cierto conocimiento de la semántica del lenguaje y manejo de excepciones requiere soporte del tiempo de ejecución
ambiente. Ambos pueden implementarse para cumplir con los criterios más estrictos tanto para la velocidad de tiempo de compilación
como para la eficiencia.
ciencia sin comprometer la generalidad o la conveniencia del programador.
A medida que aumenta el poder para definir tipos, los programas en mayor grado dependen de los tipos de bibliotecas (y
no solo los descritos en el manual de idiomas). Esto, naturalmente, exige más a las instalaciones que
expresar lo que se inserta o se recupera de una biblioteca, las instalaciones para descubrir qué contiene una biblioteca,
instalaciones para determinar qué partes de una biblioteca son realmente utilizadas por un programa, etc.
Para un lenguaje compilado, las instalaciones para calcular la compilación mínima necesaria después de un cambio
ser importante Es esencial que el vinculador / cargador sea capaz de traer un programa a la memoria para
ejecución sin traer también grandes cantidades de código relacionado, pero sin usar. En particular, un
sistema de biblioteca / enlazador / cargador que trae el código para cada operación de un tipo al núcleo solo porque el
programador usó una o dos operaciones en el tipo es peor que inútil.
4 Soporte para programación orientada a objetos
El soporte básico que un programador necesita para escribir programas orientados a objetos consiste en un mecanismo de clase
con herencia y un mecanismo que permite que las llamadas de las funciones miembro dependan del tipo real de un
objeto (en los casos en que el tipo real es desconocido en el momento de la compilación). El diseño de la función miembro
el mecanismo de llamada es crítico. Además, las instalaciones que apoyan las técnicas de abstracción de datos (tal como se describe

Página 14
- 14 -
arriba) son importantes porque los argumentos para la abstracción de datos y sus refinamientos para apoyar
el uso de tipos es igualmente válido cuando está disponible el soporte para programación orientada a objetos. El éxito de
Ambas técnicas dependen del diseño de los tipos y de la facilidad, flexibilidad y eficiencia de dichos tipos.
La programación orientada a objetos simplemente permite que los tipos definidos por el usuario sean mucho más flexibles y generales
que el
unos diseñados utilizando solo técnicas de abstracción de datos.
4.1 Mecanismos de llamada
El servicio de idioma clave que soporta la programación orientada a objetos es el mecanismo por el cual un miembro
La función ber se invoca para un objeto dado. Por ejemplo, dado un puntero p , cómo es una llamada p-> f (arg) Han-
¿dled? Hay una variedad de opciones.
En lenguajes como C ++ y Simula, donde la verificación de tipo estática se usa ampliamente, el sistema de tipo
puede ser empleado para seleccionar entre diferentes mecanismos de llamada. En C ++, hay dos alternativas disponibles:
[1] Una llamada de función normal: la función de miembro a ser llamada se determina en tiempo de compilación (a través de un
búsqueda en las tablas de símbolos del compilador) y llamado usando el mecanismo de llamada de función estándar con
un argumento agregado para identificar el objeto para el que se llama a la función. Donde el '' estándar
llamada de función '' no se considera lo suficientemente eficiente, el programador puede declarar una función en línea
y el compilador intentará expandir su cuerpo en línea. De esta manera, uno puede lograr la eficiencia
de una macro expansión sin comprometer la semántica de la función estándar. Esta optimización es
igualmente valioso como soporte para la abstracción de datos.
[2] Una llamada de función virtual: la función a llamar depende del tipo de objeto para el que está
llamado. Este tipo no puede determinarse en general hasta el tiempo de ejecución. Típicamente, el puntero p será de
una clase base B y el objeto será un objeto de alguna clase derivada D (como fue el caso con el
forma de la clase base y el círculo de clase derivado arriba). El mecanismo de llamada debe mirar dentro del
objetar y encontrar alguna información colocada allí por el compilador para determinar qué función es f
ser llamado. Una vez que se encuentra esa función, digamos D :: f , se puede llamar usando el mecanismo descrito
encima. El nombre f está en tiempo de compilación convertido en un índice en una tabla de punteros a funciones.
Este mecanismo de llamada virtual se puede hacer esencialmente tan eficiente como la '' llamada a función normal ''
mecanismo. En la implementación estándar de C ++, solo se utilizan cinco referencias de memoria adicionales.
En idiomas con comprobación de tipo estática débil, se debe emplear un mecanismo más elaborado. Que es
hecho en un lenguaje como Smalltalk es almacenar una lista de los nombres de todas las funciones miembro (métodos) de una clase
para que puedan encontrarse en tiempo de ejecución:
[3] Invocación de un método: Primero se encuentra la tabla de nombres de métodos apropiada al examinar el objeto
apuntado por p . En esta tabla (o conjunto de tablas) se busca la cadena "f" para ver si el objeto tiene una
f () . Si se encuentra un f () , se llama; de lo contrario, se produce algún error de manejo. Esta búsqueda difiere
desde la búsqueda realizada en el momento del compilador en un lenguaje estáticamente verificado en el que el método invoca-
tion usa una tabla de métodos para el objeto real.
Una invocación de método es ineficiente en comparación con una llamada de función virtual, pero más flexible. Desde estática
Por lo general, no se puede hacer una verificación de tipos de argumentos para una invocación de método, el uso de métodos debe ser
apoyado por comprobación dinámica de tipo.
4.2 Comprobación de tipo
El ejemplo de forma mostró el poder de las funciones virtuales. ¿Qué, además de esto, tiene un método
mecanismo de invocación hacer por usted? Puede intentar invocar cualquier método para cualquier objeto.
La posibilidad de invocar cualquier método para cualquier objeto permite al diseñador de bibliotecas de uso general
Impulsar la responsabilidad del manejo de tipos en el usuario. Naturalmente, esto simplifica el diseño de las bibliotecas.
Por ejemplo:

Página 15
- 15 -
class stack {// supongamos que class any tiene un miembro next
cualquier * v;
empuje vacío (cualquier * p)
{
p-> siguiente = v;
v = p;
}
cualquier * pop ()
{
if (v == 0) devuelve error_obj;
cualquier * r = v;
v = v-> siguiente;
devolver r;
}
};
Se convierte en responsabilidad del usuario evitar desajustes de tipos como este:
apilar <any *> cs;
cs.push (nuevo Saab900);
cs.push (nuevo Saab37B);
plano * p = (plano *) cs.pop ();
p-> despegue ();
p = (plano *) cs.pop ();
p-> despegue ();
// ¡Oops! Error de tiempo de ejecución: un Saab 900 es un automóvil
// un automóvil no tiene un método de despegue.
El controlador de mensajes detectará un intento de utilizar un automóvil como avión y un error apropiado
se llamará al manejador. Sin embargo, eso es solo un consuelo cuando el usuario también es el programador. los
La ausencia de verificación de tipo estática dificulta garantizar que los errores de esta clase no estén presentes en el sistema.
tems entregados a los usuarios finales. Naturalmente, un lenguaje diseñado con métodos y sin tipos estáticos puede
expresa este ejemplo con menos pulsaciones de teclas.
Las combinaciones de clases parametrizadas y el uso de funciones virtuales pueden acercarse a la flexibilidad, facilidad
del diseño y la facilidad de uso de las bibliotecas diseñadas con la búsqueda de métodos sin relajar la verificación del tipo estático
Incurrir o incurrir en gastos generales de tiempo mensurables (en tiempo o espacio). Por ejemplo:
apilar <plano *> cs;
cs.push (nuevo Saab900);
// error de tiempo de compilación:
// no coinciden: coche * pasado, plano * esperado
cs.push (nuevo Saab37B);
plano * p = cs.pop ();
p-> despegue ();
// bien: un Saab 37B es un avión
p = cs.pop ();
p-> despegue ();
El uso de comprobación de tipos estáticos y llamadas a funciones virtuales conduce a un estilo de programa algo diferente.
ming que la comprobación dinámica de tipos y la invocación de métodos. Por ejemplo, una especificación de clase Simula o C ++
fies una interfaz fija a un conjunto de objetos (de cualquier clase derivada) mientras que una clase Smalltalk especifica una inicial
conjunto de operaciones para objetos (de cualquier subclase). En otras palabras, una clase Smalltalk es una especificación mínima
y el usuario puede probar operaciones no especificadas, mientras que una clase C ++ es una especificación exacta y el usuario
se garantiza que solo las operaciones especificadas en la declaración de clase serán aceptadas por el compilador.

Página 16
- dieciséis -
4.3 Herencia
Considere un lenguaje que tenga alguna forma de búsqueda de métodos sin tener un mecanismo de herencia.
¿Podría decirse que ese lenguaje admite la programación orientada a objetos? Yo creo que no. Claramente, podrías hacer
cosas interesantes con la tabla de métodos para adaptar el comportamiento de los objetos a las condiciones. Sin embargo, para
evitar el caos, debe haber alguna forma sistemática de asociar los métodos y las estructuras de datos que ellos suponen
para su representación de objeto. Para permitir que un usuario de un objeto sepa qué tipo de comportamiento esperar, hay
también debería ser una forma estándar de expresar lo que es común a los diferentes comportamientos
objeto podría adoptar. Esta '' manera sistemática y estándar '' sería un mecanismo de herencia.
Considere un lenguaje que tenga un mecanismo de herencia sin funciones o métodos virtuales. Podría eso
Se dice que el lenguaje es compatible con la programación orientada a objetos? Creo que no: el ejemplo de forma no tiene una
buena solución en tal lenguaje. Sin embargo, tal lenguaje sería notablemente más poderoso que un
Lenguaje de abstracción de datos "simple". Esta afirmación es apoyada por la observación de que muchos Simula y
Los programas de C ++ se estructuran utilizando jerarquías de clase sin funciones virtuales. La capacidad de expresar
la monality (factoring) es una herramienta extremadamente poderosa. Por ejemplo, los problemas asociados con la necesidad de
tener una representación común de todas las formas podría ser resuelto. No se necesitaría ninguna unión. Sin embargo, en el
la ausencia de funciones virtuales, el programador tendría que recurrir al uso de '' campos tipo '' para determinar
tipos reales de objetos, por lo que los problemas con la falta de modularidad del código permanecerían †.
Esto implica que la derivación de clase (subclases) es una herramienta de programación importante por derecho propio. Eso
se puede usar para soportar programación orientada a objetos, pero tiene usos más amplios. Esto es particularmente cierto si uno
identifica el uso de la herencia en la programación orientada a objetos con la idea de que una clase base expresa una
concepto general de que todas las clases derivadas son especializaciones. Esta idea captura solo una parte de la expresión
sive poder de herencia, pero es fuertemente alentada por los idiomas donde cada función miembro es vir-
tual (o un método). Con controles adecuados de lo que se hereda (ver Snyder [17] y Stroustrup [18]), clase
la derivación puede ser una herramienta poderosa para crear nuevos tipos. Dada una clase, la derivación se puede usar para agregar
y / o restar características. La relación de la clase resultante con su base no siempre puede ser completamente
descrito en términos de especialización; factoring puede ser un mejor término.
La derivación es otra herramienta en manos de un programador y no hay una forma infalible de predecir cómo
se va a utilizar, y es demasiado pronto (incluso después de 20 años de Simula) para decir qué usos son simplemente erróneos.
usos.
4.4 Herencia Múltiple
Cuando una clase A es una base de clase B , a B hereda los atributos de A ; es decir, una B es una A además de
cualquier otra cosa que sea Dada esta explicación, parece obvio que podría ser útil tener una clase B
heredar de dos clases base A1 y A2 . Esto se llama herencia múltiple [22].
Un ejemplo bastante estándar del uso de la herencia múltiple sería proporcionar dos clases de biblioteca
visualizado y tarea para representar objetos bajo el control de un administrador de visualización y co-rutinas
bajo el control de un programador, respectivamente. Un programador podría crear clases como
clase my_displayed_task: public display, public task {
// mis cosas
};
clase my_task: tarea pública {// no se muestra
// mis cosas
};
class my_displayed: public displayed {// no es una tarea
// mis cosas
};
Usando (solo) herencia individual, solo dos de estas tres opciones estarían abiertas para el programador. Esta
conduce a la replicación del código o a la pérdida de flexibilidad, y generalmente ambos. En C ++ este ejemplo puede ser
como se muestra arriba con sin gastos indirectos significativos (en tiempo o espacio) en comparación con la herencia individual y
sin sacrificar la comprobación del tipo estático [19].
__________________
† Este es el problema con la declaración de inspección de Simula y la razón por la cual no tiene una contraparte en C ++.

Página 17
- 17 -
Las ambigüedades se manejan en tiempo de compilación:
clase A {public: f (); ...};
clase B {public: f (); ...};
clase C: pública A, pública B {...};
void g () {
C * p;
p-> f (); // error: ambiguo
}
En esto, C ++ difiere de los dialectos Lisp orientados a objetos que admiten herencia múltiple. En estos Lisp
las ambigüedades de los dialectos se resuelven considerando el orden de las declaraciones como significativo, al considerar los objetos
del mismo nombre en diferentes clases base idénticas, o combinando métodos del mismo nombre en la base
clases en un método más complejo de la clase más alta.
En C ++, normalmente se resolvería la ambigüedad agregando una función:
clase C: pública A, pública B {
público:
F()
{
// Las propias cosas de C
A :: f ();
B :: f ();
}
...
}
Además de este concepto bastante sencillo de herencia múltiple independiente, parece haber
una necesidad de un mecanismo más general para expresar dependencias entre clases en una herencia múltiple
enrejado. En C ++, el requisito de que un subobjeto deba ser compartido por todos los demás sub-objetos en un objeto de clase
se expresa a través del mecanismo de una clase base virtual:
clase W {...};
clase Bwindow
// ventana con borde
: público virtual W
{...};
clase Mwindow
// ventana con menú
: público virtual W
{...};
clase BMW
// ventana con borde y menú
: público Bwindow, público Mwindow
{...};
Aquí, el subobjeto de la ventana (única) es compartido por los subobjetos Bwindow y Bwindow de un BMW . los
Los dialectos Lisp proporcionan conceptos de combinación de métodos para facilitar la programación utilizando una clase tan
complicada
jerarquías C ++ no.
4.5 Encapsulación
Considere la posibilidad de proteger a un miembro de la clase (ya sea un miembro de datos o un miembro de la función)
''Acceso no autorizado''. Qué opciones pueden ser razonables para delimitar el conjunto de funciones que pueden acceder
ese miembro? La respuesta "obvia" para un lenguaje que apoya la programación orientada a objetos es "todo operacional".
aciones definidas para este objeto ''; es decir, todas las funciones miembro. Una implicación no obvia de esta respuesta es
que no puede haber una lista completa y final de todas las funciones que pueden acceder al miembro protegido desde
uno siempre puede agregar otro derivando una nueva clase de la clase del miembro protegido y definir un miembro
función de esa clase derivada. Este enfoque combina un alto grado de protección contra accidentes
(dado que no se define fácilmente una nueva clase derivada '' por accidente '') con la flexibilidad necesaria para la '' herramienta
construyendo '' usando jerarquías de clases (ya que puedes '' otorgarte acceso '' a los miembros protegidos derivando

Página 18
- 18 -
una clase).
Desafortunadamente, la respuesta '' obvia '' para un lenguaje orientado a la abstracción de datos es diferente: '' lista
las funciones que necesitan acceso en la declaración de clase ''. No hay nada especial acerca de estas funciones. En
en particular, no necesitan ser funciones miembro. Una función no miembro con acceso a clase privada
Bers se llama amigo en C ++. El complejo de clase anterior se definió utilizando funciones de amigo . Es algo-
veces importante que una función se puede especificar como un amigo en más de una clase. Tener la lista completa
de miembros y amigos disponibles es una gran ventaja cuando intenta comprender el comportamiento de un
escriba y especialmente cuando desee modificarlo.
Aquí hay un ejemplo que demuestra algunas de las opciones de encapsulación en C ++:
clase B {
// los miembros de la clase son privados por defecto
int i1;
void f1 ();
protegido:
int i2;
void f2 ();
público:
int i3;
void f3 ();
friend void g (B *); // cualquier función puede ser designada como amiga
};
Los miembros privados y protegidos no son accesibles en general:
void h (B * p)
{
p-> f1 ();
// error: B :: f1 es privado
p-> f2 ();
// error: B :: f2 está protegido
p-> f3 ();
// bien: B :: f1 es público
}
Los miembros protegidos, pero no los miembros privados, son accesibles para los miembros de una clase derivada:
clase D: público B {
público:
void g ()
{
f1 ();
// error: B :: f1 es privado
f2 ();
// bien: B :: f2 está protegido, pero D se deriva de B
f3 ();
// bien: B :: f1 es público
}
};
Las funciones de amigo tienen acceso a miembros privados y protegidos al igual que las funciones de miembro:
void g (B * p)
{
p-> f1 ();
// bien: B :: f1 es privado, pero g () es amigo de B
p-> f2 ();
// bien: B :: f2 está protegido, pero g () es amigo de B
p-> f3 ();
// bien: B :: f1 es público
}
Los problemas de encapsulación aumentan dramáticamente en importancia con el tamaño del programa y con el número
y dispersión geográfica de sus usuarios. Ver Snyder [17] y Stroustrup [18] para discusiones más detalladas
de soporte de idioma para encapsulación.
4.6 Problemas de implementación
El soporte necesario para la programación orientada a objetos es proporcionado principalmente por el sistema de tiempo de ejecución
y
por el entorno de programación. Parte de la razón es que la programación orientada a objetos se basa en la lengua
Las mejoras de indicadores ya han llegado a su límite para respaldar la abstracción de datos de modo que relativamente pocas
son necesarias †.
__________________
† Esto supone que un lenguaje orientado a objetos sí admite la abstracción de datos. Sin embargo, el soporte para la abstracción de
datos es
a menudo deficiente en tales idiomas. Por el contrario, los lenguajes que soportan la abstracción de datos son típicamente deficientes
en su soporte de objetos

Página 19
- 19 -
El uso de programación orientada a objetos desdibuja la distinción entre un lenguaje de programación y su
medio ambiente más. Dado que se pueden definir tipos más poderosos de definición especial definidos por el usuario y de propósito
general
su uso impregna los programas de usuario. Esto requiere un mayor desarrollo del sistema de tiempo de ejecución, biblioteca
instalaciones, depuradores, medición del rendimiento, herramientas de monitoreo, etc. Idealmente, estos están integrados en un
entorno de programación ficticia. Smalltalk es el mejor ejemplo de esto.
5 límites a la perfección
Un problema importante con un lenguaje definido para explotar las técnicas de ocultación de datos, abstracción de datos y
programación orientada a objetos es que para afirmar que es un lenguaje de programación de propósito general debe
[1] Ejecutar en máquinas tradicionales.
[2] Coexisten con los sistemas operativos tradicionales.
[3] Compite con los lenguajes de programación tradicionales en términos de eficiencia de tiempo de ejecución.
[4] Haga frente a cada área de aplicación principal.
Esto implica que las instalaciones deben estar disponibles para un trabajo numérico efectivo (aritmética de punto flotante sin
gastos generales que harían que Fortran parezca atractivo), y que las instalaciones deben estar disponibles para acceder a
la memoria de una manera que permite escribir los controladores del dispositivo. También debe ser posible escribir llamadas que
forma a los estándares a menudo bastante extraños requeridos para las interfaces de sistemas operativos tradicionales. Además,
debería ser posible llamar funciones escritas en otros lenguajes desde una lengua de programación orientada a objetos
calibre y para las funciones escritas en el lenguaje de programación orientado a objetos a llamar desde un programa
escrito en otro idioma
Otra implicación es que un lenguaje de programación orientado a objetos no puede confiar completamente en los mecanismos
nismos que no se pueden implementar de manera eficiente en una arquitectura tradicional y que aún esperan ser utilizados como
lenguaje de propósito general. Una implementación muy general de la invocación del método puede ser una responsabilidad a menos
que
hay formas alternativas de solicitar un servicio.
Del mismo modo, la recolección de basura puede convertirse en un cuello de botella de rendimiento y portabilidad. La mayoría de los
objetos
los lenguajes de programación orientados emplean la recolección de basura para simplificar la tarea del programador y para
reducir la complejidad del lenguaje y su compilador. Sin embargo, debería ser posible usar basura
recolección en áreas no críticas mientras se retiene el control del uso de almacenamiento en áreas donde importa. Como una
alternativa
nativo, es posible tener un lenguaje sin recolección de basura y luego proporcionar suficiente expresivo
poder para habilitar el diseño de tipos que mantienen su propio almacenamiento. C ++ es un ejemplo de esto.
El manejo de excepciones y las características de concurrencia son otras áreas problemáticas potenciales. Cualquier característica que
sea mejor
implementado con la ayuda de un enlazador es probable que se convierta en un problema de portabilidad.
La alternativa a tener características de "bajo nivel" en un idioma es manejar las principales áreas de aplicación usando
separar los idiomas de "bajo nivel".
6. Conclusiones
La programación orientada a objetos es programación usando herencia. La abstracción de datos es la programación
usando tipos definidos por el usuario. Con pocas excepciones, la programación orientada a objetos puede y debe ser una
superproducción
conjunto de abstracción de datos. Estas técnicas necesitan un soporte adecuado para ser efectivas. Abstracción de datos principalmente
necesita soporte en forma de características de lenguaje y programación orientada a objetos necesita más apoyo de
un entorno de programación. Para ser de propósito general, un lenguaje que admite la abstracción de datos u objetos
la programación orientada debe permitir el uso efectivo del hardware tradicional.
7 Agradecimientos
Se presentó una versión anterior de este documento a la reunión de la Asociación de usuarios de Simula en Stock-
Holm. Las discusiones allí causaron muchas mejoras tanto en estilo como en contenido. Brian Kernighan y
Ravi Sethi hizo muchos comentarios constructivos. También gracias a todos los que ayudaron a formar C ++.
__________________
programación orientada.

Página 20
- 20 -
8 referencias
[1] Birtwistle, Graham et.al .: SIMULA BEGIN . Studentlitteratur, Lund, Suecia. 1971. Chartwell-Bratt
ltd, Reino Unido. 1980.
[2] Bobrow, D. y Stefik, M .: The LOOPS Manual . Xerox Parc 1983.
[3] Dahl, OJ. y Hoare, CAR: Estructuras jerárquicas del programa . En programación estructurada . Aca-
demic Press 1972.
[4] Cargill, Tom A . : PI: Un estudio de caso en programación orientada a objetos . Avisos SIGPLAN, Novembre
Ber 1986, pp 350-360.
[5] Grupo de estudio XI del CCITT: Manual de usuario de CHILL . CHILL Bulletin no 1. vol 4. March 1984.
[6] Goldberg, A. y Robson, D .: Smalltalk-80: El lenguaje y su implementación . Addison-
Wesley 1983.
[7] Ichbiah, JD et.al .: Justificación para el diseño del lenguaje de programación Ada . SIGPLAN
Avisos, junio de 1979.
[8] Kernighan, BW y Ritchie, DM: The C Programming Language . Prentice-Hall 1978.
[9] Kerr, Ron: Programación basada en objetos : una base para software confiable . Procedimientos de la
14 ° SIMULA Users 'Conference. Agosto de 1986, pp 159-165. Una versión abreviada de este documento
se puede encontrar bajo el título Una vista materialista de la Analogía de "Ingeniería" del software en SIG-
Avisos del PLAN, marzo de 1987, páginas 123-125.
[10] Liskov, Barbara et. al .: Manual de referencia de Clu . MIT / LCS / TR-225, octubre de 1979.
[11] Liskov, Barbara et. al .: Mecanismos de abstracción en Clu . CACM vol 20, no 8, agosto de 1977, pp
564-576.
[12] Milner, Robert: Una propuesta para el estándar ML . Simposio de ACM sobre Lisp y Programa Funcional-
Ming. 1984, pp 184-197.
[13] Nygaard, Kristen: Conceptos básicos en programación orientada a objetos . Avisos SIGPLAN, octubre
1986, pp 128-132.
[14] Rovner, Paul: Extendiendo Modula-2 para construir sistemas grandes e integrados . IEEE Software, vol. 3. No.
6. Noviembre de 1986, pp 46-57.
[15] Shopiro, Jonathan: Ampliación del sistema de tareas C ++ para aplicaciones en tiempo real . Proc. USENIX
Taller C ++, Santa Fe, noviembre de 1987.
[16] SIMULA Standards Group, 1984: SIMULA Standard . Secretaría de ASU, Simula como Post Box 150
Refstad, 0513 Oslo 5, Noruega.
[17] Snyder, Alan: encapsulación y herencia en lenguajes de programación orientados a objetos . SIG-
Avisos del PLAN, noviembre de 1986, págs. 38-45.
[18] Stroustrup, Bjarne: El lenguaje de programación C ++ . Addison-Wesley, 1986.
[19] Stroustrup, Bjarne: Herencia múltiple para C ++ . Actas de la primavera'87 EUUG Confer-
ence. Helsinki, mayo de 1987.

Potrebbero piacerti anche