Creo que este es el idioma que está buscando. En la especificación ISO C++ 03, en § 10.2/2, tenemos la siguiente:
los siguientes pasos definen el resultado de búsqueda de nombre en un ámbito de clase, C. En primer lugar, cada declaración para el nombre en la clase y en cada uno de sus sub-objetos de clase base se considera. Un nombre de miembro f en un subobjeto B oculta un nombre de miembro f en un subobjeto A si A es un subobjeto de clase base B. Cualquier declaración que esté tan oculta se elimina de consideración. Cada una de estas declaraciones que se introdujo mediante una declaración using se considera que proviene de cada subobjeto de C que es del tipo que contiene la declaración designada por la declaración using. Si el conjunto resultante de declaraciones no provienen todas de los subobjetos del mismo tipo, o el conjunto tiene un miembro no estático e incluye miembros de distintos subobjetos, existe una ambigüedad y el programa está mal formado. De lo contrario, ese conjunto es el resultado de la búsqueda.
En un nivel alto, esto significa que cuando intenta buscar un nombre, busca en todas las clases base y en la clase para encontrar las declaraciones de ese nombre. Luego vas clase por clase y si uno de esos objetos base tiene algo con ese nombre, oculta todos los nombres introducidos en cualquiera de las clases base de ese objeto.
Un detalle importante aquí es la siguiente línea:
Cualquier declaraciones que están tan ocultas se eliminan de la consideración.
importante destacar que este dice que si algo se oculta por nada, se considera oculto y se retira. Así, por ejemplo, si hago esto:
class D {
public:
void f();
}
class B: virtual public D { class C: virtual public D {
public: public:
void f(); /* empty */
}; };
class A: public B, public C {
public:
void doSomething() {
f(); // <--- This line
}
};
en la línea indicada, la llamada a f()
se resuelve de la siguiente manera. Primero, agregamos B::f
y D::f
al conjunto de nombres que se pueden considerar. D::f
no oculta nada porque D
no tiene clases base. Sin embargo, B::f
oculta D::f
, por lo que aunque D::f
se puede contactar desde A
sin ver , se considera oculto y se elimina del conjunto de objetos que podrían llamarse f
. Como solo queda B::f
, ese es el que se llama. La especificación ISO menciona (§ 10,2/7) que
Cuando se utilizan las clases base virtuales, una declaración oculta se puede llegar a lo largo de un camino a través de la sub-objeto celosía que no pasa a través de la declaración escondite. Esto no es una ambigüedad [...]
Creo que esto se debe a la regla anterior.
En C++ 11 (según el borrador de la especificación N3242), las reglas se explican mucho más explícitamente que antes y se proporciona un algoritmo real para calcular el nombre. Aquí está el lenguaje, paso a paso.
comenzamos con § 10,2/3:
El conjunto de búsqueda para f en C, denominado S (f, C), consta de dos conjuntos de componentes: la declaración establece, un conjunto de miembros llamado f; y el conjunto de subobjetos , un conjunto de subobjetos donde se encontraron declaraciones de estos miembros (posiblemente incluyendo declaraciones de uso). En el conjunto de declaraciones, las declaraciones de uso se reemplazan por los miembros que designan , y las declaraciones de tipos (incluidos los nombres de clases inyectadas) se reemplazan por los tipos que designan. S (F, C) se calcula como sigue:
En este contexto, C
se refiere al ámbito en el que se produce la búsqueda. En otras palabras, el conjunto S(f, C)
significa "¿cuáles son las declaraciones que son visibles cuando trato de buscar f
en el alcance de clase C
?" Para responder a esto, la especificación define un algoritmo para determinar esto. El primer paso es la siguiente: (§ 10,2/4)
Si C contiene una declaración del nombre f, el conjunto declaración contiene cada declaración de f declarado en C que satisfaga los requisitos de la lengua construyen en que la búsqueda ocurre. [...] Si el conjunto de declaraciones resultante no está vacío, el conjunto de subobjetos contiene C y el cálculo está completo.
En otras palabras, si la clase en sí tiene algo que se llama f
declarada en él, entonces el conjunto declaración es sólo el conjunto de cosas nombradas f
definido en esa clase (o importados con una declaración using
). Pero, si no podemos encontrar nada llamado f
, o si todo lo que se llama f
es del tipo incorrecto (por ejemplo, una declaración de función cuando queremos un tipo), entonces pasamos al siguiente paso: (§ 10.2/5)
De lo contrario (es decir, C no contiene una declaración de f o el conjunto de declaraciones resultante está vacío), S (f, C) está inicialmente vacío. Si C tiene clases base, calcule el conjunto de búsqueda para f en cada subobjeto de clase de base directa B i, y fusione cada conjunto de búsqueda S (f, B i) a su vez en S (f, C).
En otras palabras, vamos a ver las clases base, calcular a qué se podría referir el nombre en esas clases base, luego fusionar todo junto. La forma real en que realiza la fusión se especifica en el siguiente paso. Esto es realmente complicado (tiene tres partes), así que aquí está el golpe por golpe. Aquí está la redacción original: (§ 10.2/6)
Los siguientes pasos definen el resultado de la fusión de conjunto de búsqueda S (f, B i) en los S intermedios (F, C):
Si cada una de las miembros subobject de S (f, B i) es un subobjeto de clase base de al menos uno de los miembros subobjeto de S (f, C), o si S (f, B i) está vacío, S (f, C) no se modifica y la fusión está completa. Por el contrario, si cada uno de los miembros subobjeto de S (f, C) es un subobjeto de clase base de al menos uno de los miembros del subobjeto de S (f, B i), o si S (f, C) está vacío , el nuevo S (f, C) es una copia de S (f, Bi).
lo contrario, si los conjuntos de declaración de S (f, B i) y S (f, C) son diferentes, la combinación es ambiguo: la nueva S (f, C) es una búsqueda con un conjunto conjunto de declaraciones inválidas y la unión de los conjuntos de subobjetos. En las fusiones posteriores , un conjunto de declaración no válido se considera diferente de cualquier otro.
De lo contrario, el nuevo S (f, C) es un conjunto de búsqueda con el conjunto compartido de declaraciones y la unión de los conjuntos de subobjetos .
bien, vamos a PIEZA esto aparte de uno en uno. La primera regla aquí tiene dos partes. La primera parte dice que si intenta fusionar un conjunto vacío de declaraciones en el conjunto general, no hace nada en absoluto. Eso tiene sentido. También dice que si intenta fusionar algo en una clase base de todo lo que se fusionó hasta ahora, entonces no hace nada en absoluto. Esto es importante, porque significa que si ha escondido algo, no desea reintroducirlo accidentalmente al fusionarlo nuevamente.
La segunda parte de la primera regla dice que si la cosa se está fusionando in se deriva de todo lo que se ha fusionado hasta ahora, reemplaza el conjunto que ha calculado hasta este momento con los datos que ha calculado para el tipo derivado. Esto básicamente dice que si fusionaste muchas clases que parecen desconectadas y luego te fusionas en una clase que las unifica a todas, tira los datos antiguos y simplemente utiliza los datos para ese tipo derivado, que ya has calculado .
Ahora vamos a la segunda regla. Me tomó un tiempo entender esto, así que puedo tener esto mal, pero creo que está diciendo que si realiza la búsqueda en dos clases base diferentes y obtiene cosas diferentes, entonces el nombre es ambiguo y debe informar que algo es mal si trataras de buscar el nombre en este punto.
La última regla dice que si no estamos en ninguno de estos casos especiales, no pasa nada y simplemente debe combinarlos.
¡Uf ... eso fue difícil! Veamos qué sucede cuando trazamos esto para la herencia de diamantes anterior. Queremos buscar el nombre f
comenzando en A
. Como A
no define f
, calculamos los valores de buscar f
comenzando en B
y f
comenzando en C
. Veamos qué pasa. Cuando se calcula el valor de f
en B
, vemos que B::f
está definido, por lo que dejamos de buscar.El valor de mirar hacia arriba f
en B
es el conjunto (B::f
, B
}. Para buscar lo f
significa en C
, nos fijamos en C
y vemos que no define f
, por lo que una vez más de forma recursiva buscar el valor de D
. Si realizamos la búsqueda en D
, obtenemos {D::f
, D
} y cuando fusionamos todo, encontramos que se aplica la segunda mitad de la regla 1 (dado que es vagamente cierto que cada objeto del conjunto de subobjetos es una base de D
), por lo que el el valor para C
está dado por {D::f
, D
}.
F Finalmente, necesitamos unir los valores para B
y C
. Esto intenta combinar {D::f
, D
} y {B::f
, B
}. Aquí es donde se divierte. Supongamos que nos fusionamos en este orden. Fusionar {D::f
, D
} y el conjunto vacío produce {D::f
, D
}. Cuando nos fusionamos ahora en {B::f
, B
}, porque D
es una base de B
, en la segunda mitad de la regla uno, reemplazamos nuestro antiguo conjunto y terminamos con {B::f
, B
}. En consecuencia, la búsqueda de f
es la versión de f
en B
.
Si, por el contrario, nos fusionamos en el orden opuesto, comenzamos con {B::f
, B
} y tratar la fusión en {D::f
, D
}. Pero dado que D
es una base de B
, simplemente lo ignoramos, dejando {B::f
, B
}. Hemos llegado al mismo resultado. Genial, ¿eh? ¡Me sorprende que esto funcione tan bien!
Así que ahí lo tienen: las reglas anteriores son realmente (ish) sencillas, y las nuevas reglas son increíblemente complejas pero de alguna manera se las arreglan para funcionar de todos modos.
Espero que esto ayude!
¿Qué quiere decir con "dominio" con respecto a la herencia virtual? –
@Nicol: el índice del estándar C++ 98 se refiere a la herencia virtual y la página 167. eso es todo lo que el estándar dice directamente (y, por lo que sé, el índice no es normativo). Explicar la dominación tomaría más que este comentario, por lo tanto, solo google it, o [wikipedia] (http://en.wikipedia.org/wiki/Dominance_%28C%2B%2B%29) - si no sabes al respecto, probablemente no pueda responder esta pregunta de todos modos. vítores, –
@Nicol: gracias por preguntar, agregué el enlace a la pregunta. –