overload operator make interfaces how example c++ function-overloading overloading overload-resolution member-functions

operator - overload c++ example



¿Por qué no se llama a un método const público cuando el no const es privado? (11)

Considera este código:

struct A { void foo() const { std::cout << "const" << std::endl; } private: void foo() { std::cout << "non - const" << std::endl; } }; int main() { A a; a.foo(); }

El error del compilador es:

error: ''void A :: foo ()'' es privado`.

Pero cuando elimino el privado, simplemente funciona. ¿Por qué no se llama al método const público cuando el no const es privado?

En otras palabras, ¿por qué la resolución de sobrecarga viene antes que el control de acceso? Esto es extraño. ¿Crees que es consistente? Mi código funciona y luego agrego un método, y mi código de trabajo no se compila en absoluto.


Cuando llamas a a.foo(); , el compilador pasa por la resolución de sobrecarga para encontrar la mejor función para usar. Cuando construye el conjunto de sobrecarga que encuentra

void foo() const

y

void foo()

Ahora, dado que a no es const , la versión sin const es la mejor coincidencia, por lo que el compilador elige void foo() . Luego, se establecen las restricciones de acceso y se obtiene un error de compilación, ya que void foo() es privado.

Recuerde, en la resolución de sobrecarga no es "encontrar la mejor función utilizable". Es ''encontrar la mejor función e intentar usarla''. Si no puede debido a restricciones de acceso o por ser eliminado, entonces aparece un error del compilador.

En otras palabras, ¿por qué la resolución de sobrecarga viene antes del control de acceso?

Bueno, veamos:

struct Base { void foo() { std::cout << "Base/n"; } }; struct Derived : Base { void foo() { std::cout << "Derived/n"; } }; struct Foo { void foo(Base * b) { b->foo(); } private: void foo(Derived * d) { d->foo(); } }; int main() { Derived d; Foo f; f.foo(&d); }

Ahora digamos que en realidad no quise hacer que el void foo(Derived * d) privado. Si el control de acceso es lo primero, este programa se compilará y se ejecutará y Base se imprimirá. Esto podría ser muy difícil de rastrear en una base de código grande. Dado que el control de acceso se produce después de la resolución de sobrecarga, recibo un buen error del compilador que me dice que no se puede llamar a la función que quiero que llame, y puedo encontrar el error mucho más fácil.


Dado que el puntero implícito es no const , el compilador primero verificará la presencia de una versión no const de la función antes de una versión const .

Si marca explícitamente el no const private , la resolución fallará y el compilador no continuará buscando.


En última instancia, esto se reduce a la afirmación en el estándar de que la accesibilidad no debe tenerse en cuenta al realizar la resolución de sobrecarga . Esta afirmación se puede encontrar en [over.match] cláusula 3:

... Cuando la resolución de sobrecarga tiene éxito y la mejor función viable no es accesible (Cláusula [class.access]) en el contexto en el que se utiliza, el programa está mal formado.

y también la Nota en la cláusula 1 de la misma sección:

[Nota: no se garantiza que la función seleccionada por resolución de sobrecarga sea adecuada para el contexto. Otras restricciones, como la accesibilidad de la función, pueden hacer que su uso en el contexto de la llamada esté mal formado. - nota final]

En cuanto a por qué, puedo pensar en un par de posibles motivaciones:

  1. Evita cambios inesperados de comportamiento como resultado de cambiar la accesibilidad de un candidato de sobrecarga (en su lugar, se producirá un error de compilación).
  2. Elimina la dependencia del contexto del proceso de resolución de sobrecarga (es decir, la resolución de sobrecarga tendría el mismo resultado dentro o fuera de la clase).

En esta convocatoria:

a.foo();

Siempre hay un puntero implícito disponible en cada función miembro. Y la calificación const de this se toma de la referencia de llamada / objeto. El compilador trata la llamada anterior como:

A::foo(a);

Pero tiene dos declaraciones de A::foo que se tratan como :

A::foo(A* ); A::foo(A const* );

Por resolución de sobrecarga, el primero se seleccionará para no const this , el segundo se seleccionará para un const this . Si elimina el primero, el segundo se unirá a ambos const y non-const .

Después de la resolución de sobrecarga para seleccionar la mejor función viable, viene el control de acceso. Como especificó el acceso a la sobrecarga elegida como private , el compilador se quejará.

El estándar lo dice así:

[class.access/4] : ... En el caso de nombres de funciones sobrecargados, el control de acceso se aplica a la función seleccionada por resolución de sobrecarga ...

Pero si haces esto:

A a; const A& ac = a; ac.foo();

Entonces, solo se ajustará la sobrecarga const .


Es importante tener en cuenta el orden de las cosas que suceden, que es:

  1. Encuentra todas las funciones viables.
  2. Elija la mejor función viable.
  3. Si no hay exactamente una mejor opción viable, o si realmente no puede llamar a la mejor función viable (debido a infracciones de acceso o la función se delete d), falle.

(3) sucede después de (2). Lo cual es realmente importante, porque de lo contrario, hacer que las funciones delete d o private sería algo sin sentido y mucho más difícil de razonar.

En este caso:

  1. Las funciones viables son A::foo() y A::foo() const .
  2. La mejor función viable es A::foo() porque esta última implica una conversión de calificación implícita en this argumento.
  3. Pero A::foo() es private y no tiene acceso a él, por lo tanto, el código está mal formado.

Esto se reduce a una decisión de diseño bastante básica en C ++.

Al buscar la función para satisfacer una llamada, el compilador realiza una búsqueda como esta:

  1. Busca para encontrar el primer alcance 1 en el que hay algo con ese nombre.

  2. El compilador encuentra todas las funciones (o functors, etc.) con ese nombre en ese ámbito.

  3. Luego, el compilador sobrecarga la resolución para encontrar el mejor candidato entre los que encontró (sean accesibles o no).

  4. Finalmente, el compilador verifica si esa función elegida es accesible.

Debido a ese orden, sí, es posible que el compilador elija una sobrecarga que no sea accesible, aunque haya otra sobrecarga que sea accesible (pero no elegida durante la resolución de sobrecarga).

En cuanto a si sería posible hacer las cosas de manera diferente: sí, sin duda es posible. Sin embargo, definitivamente conduciría a un lenguaje bastante diferente al de C ++. Resulta que muchas decisiones aparentemente menores pueden tener ramificaciones que afectan mucho más de lo que inicialmente podría ser obvio.

  1. "Primero" puede ser un poco complejo en sí mismo, especialmente cuando / si las plantillas se involucran, ya que pueden conducir a una búsqueda en dos fases, lo que significa que hay dos "raíces" completamente separadas para comenzar al hacer la búsqueda. Sin embargo, la idea básica es bastante simple: comience desde el alcance envolvente más pequeño y avance hacia ámbitos cada vez más amplios.

Los controles de acceso ( public , protected , private ) no afectan la resolución de sobrecarga. El compilador elige void foo() porque es la mejor combinación. El hecho de que no sea accesible no cambia eso. Al quitarlo, se deja solo void foo() const , que es la mejor (es decir, la única) coincidencia.


Los especificadores de acceso no afectan la búsqueda de nombres y la resolución de llamadas a funciones, nunca. La función se selecciona antes de que el compilador verifique si la llamada debe desencadenar una infracción de acceso.

De esta manera, si cambia un especificador de acceso, recibirá una alerta en tiempo de compilación si hay una violación en el código existente; Si se tuviera en cuenta la privacidad para la resolución de la llamada de función, el comportamiento de su programa podría cambiar silenciosamente.


Porque la variable a en la función main no se declara como const .

Las funciones miembro constantes se invocan en objetos constantes.


Supongamos que el control de acceso llegó antes de la resolución de sobrecarga. Efectivamente, esto significaría visibilidad public/protected/private controlada en lugar de accesibilidad.

La sección 2.10 de Diseño y evolución de C ++ de Stroustrup tiene un pasaje sobre esto donde analiza el siguiente ejemplo

int a; // global a class X { private: int a; // member X::a }; class XX : public X { void f() { a = 1; } // which a? };

Stroustrup menciona que un beneficio de las reglas actuales (visibilidad antes de la accesibilidad) es que (temporalmente) cambiar lo private dentro de la class X en public (por ejemplo, con fines de depuración) es que no hay un cambio silencioso en el significado del programa anterior ( es decir, se intenta acceder a X::a a en ambos casos, lo que da un error de acceso en el ejemplo anterior). Si public/protected/private controlara la visibilidad, el significado del programa cambiaría (global a se llamaría con private , de lo contrario X::a ).

Luego declara que no recuerda si fue por diseño explícito o un efecto secundario de la tecnología de preprocesador utilizada para implementar el C con predecesor de Classess a Standard C ++.

¿Cómo se relaciona esto con tu ejemplo? Básicamente, debido a que la resolución de sobrecarga hecha estándar se ajusta a la regla general de que la búsqueda de nombres viene antes del control de acceso.

10.2 Búsqueda de nombre de miembro [class.member.lookup]

1 La búsqueda de nombre de miembro determina el significado de un nombre (expresión-id) en un ámbito de clase (3.3.7). La búsqueda de nombres puede dar lugar a una ambigüedad, en cuyo caso el programa está mal formado. Para una expresión de identificación, la búsqueda de nombres comienza en el ámbito de clase de este; para una identificación calificada, la búsqueda de nombre comienza en el ámbito del especificador de nombre anidado. La búsqueda de nombres tiene lugar antes del control de acceso (3.4, Cláusula 11).

8 Si se encuentra sin ambigüedad el nombre de una función sobrecargada, la resolución de sobrecarga (13.3) también se realiza antes del control de acceso . Las ambigüedades a menudo se pueden resolver al calificar un nombre con su nombre de clase.


La razón técnica ha sido respondida por otras respuestas. Solo me enfocaré en esta pregunta:

En otras palabras, ¿por qué la resolución de sobrecarga viene antes del control de acceso? Esto es extraño. ¿Crees que es consistente? Mi código funciona y luego agrego un método y mi código de trabajo no se compila en absoluto.

Así fue diseñado el lenguaje. La intención es tratar de llamar la mejor sobrecarga viable, en la medida de lo posible. Si falla, se activará un error para recordarle que debe considerar el diseño nuevamente.

Por otro lado, suponga que su código se compiló y funcionó bien con la función miembro const que se invoca. Algún día, alguien (tal vez usted mismo) decide cambiar la accesibilidad de la función de miembro no const de private a public . ¡Entonces, el comportamiento cambiaría sin ningún error de compilación! Esto sería una sorpresa