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


Considere 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 borro el privado simplemente funciona. ¿Por qué no se llama al método const público cuando el método no const es privado?

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 compila en absoluto.

Author: NathanOliver, 2016-08-19

11 answers

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

void foo() const

Y

void foo()

Ahora, dado que a no es const, la versión no-const es la mejor coincidencia, por lo que el compilador elige void foo(). Luego se ponen en marcha las restricciones de acceso y se obtiene un error del compilador, 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 y tratar de usarlo'. Si no se puede debido a las restricciones de acceso o ser eliminado, entonces se obtiene un error del compilador.

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

Bueno, veamos: {[11]]}

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 quería hacer void foo(Derived * d) privado. Si el control de acceso vino primero entonces este programa compilaría y correría y Base sería impreso. Esto podría ser muy difícil de rastrear en un gran código base. Dado que el control de acceso viene después de la resolución de sobrecarga, recibo un buen error del compilador que me dice que la función que quiero que llame no se puede llamar, y puedo encontrar el error mucho más fácil.

 123
Author: NathanOliver,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-19 16:24:35

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 [clase.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 apropiada para el contexto. Otras restricciones, como la accesibilidad de la función, pueden hacer que su uso en el contexto de llamada sea mal formado. - nota final ]

En cuanto al 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, un error de compilación ocurrirá).
  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, ya sea dentro o fuera de la clase).
 33
Author: atkins,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-19 15:58:32

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

Sección 2.10 de Diseño y evolución de C++ por Stroustrup tiene un pasaje sobre esto donde discute 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 accesibilidad) es que (temporalmente) canalizar el private dentro de class X en public (por ejemplo, para el propósito de la depuración) es que no haya un cambio silencioso en el significado del programa anterior (es decir, se intenta acceder a X::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 afirma que no recuerda si fue por diseño explícito o un efecto secundario de la tecnología de preprocesador utilizada para implementar la C con Classess predecesor de C++estándar.

¿Cómo se relaciona esto con tu ejemplo? Básicamente porque la resolución de sobrecarga 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 [clase .miembro.búsqueda]

1 La búsqueda de nombres de miembros determina el significado de un nombre (id-expression) en un ámbito de clase (3.3.7). Búsqueda de nombre puede resultar en una ambigüedad, en en qué caso el programa es mal formado. Para una expresión id, nombre la búsqueda comienza en el ámbito de clase de this; para un-id calificado, name la búsqueda comienza en el ámbito del especificador nestedname. Búsqueda de nombres tiene lugar antes del control de acceso (3.4, Cláusula 11).

8 Si se encuentra inequívocamente el nombre de una función sobrecargada, la resolución de sobrecarga (13.3) también tiene lugar antes del control de acceso. Las ambigüedades a menudo se pueden resolver calificando un nombre con su clase nombre.

 30
Author: TemplateRex,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-19 19:36:48

Dado que el puntero implícito this no es-const, el compilador comprobará primero 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 uno private entonces la resolución fallará, y el compilador no continuará buscando.

 23
Author: Bathsheba,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-19 15:05:48

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

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

(3) sucede después de (2). Lo cual es realmente importante, porque de lo contrario hacer funciones delete d o private se convertiría en 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 en el argumento implícito this.
  3. Pero A::foo() es private y no tienes acceso a él, por lo tanto, el código está mal formado.
 20
Author: Barry,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-19 15:20:28

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 la primera1 alcance en el que hay algo con ese nombre.

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

  3. A continuación, el compilador hace la resolución de sobrecarga para encontrar el mejor candidato entre los que encontró (si son accesibles o no).

  4. Finalmente, el compilador comprueba 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í, es sin duda posible. Sin embargo, definitivamente conduciría a un lenguaje bastante diferente al de C++. Resulta que muchas decisiones aparentemente más bien menores pueden tener ramificaciones que afectan mucho más de lo que podría ser obvio inicialmente.


  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 de dos fases, lo que significa que hay dos "raíces" completamente separadas para comenzar cuando se hace la búsqueda. La idea básica es sin embargo, es bastante simple: comience desde el ámbito de encierro más pequeño y trabaje hacia afuera hacia ámbitos de encierro más y más grandes.
 14
Author: Jerry Coffin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-19 16:14:32

Controles de acceso (public, protected, private) no afecta a la resolución de sobrecarga. El compilador elige void foo() porque es la mejor coincidencia. El hecho de que no sea accesible no cambia eso. Eliminarlo deja solo void foo() const, que es entonces el mejor (es decir, el único) partido.

 12
Author: Pete Becker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-19 15:20:18

En esta llamada:

a.foo();

Siempre hay un puntero implícito this disponible en cada función miembro. Y la calificación const de this se toma de la referencia/objeto que llama. La llamada anterior es tratada por el compilador como:

A::foo(a);

Pero tienes dos declaraciones de A::foo que es tratado como :

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

Por resolución de sobrecarga, la primera será seleccionada no const this, el segundo será seleccionado por un const this. Si eliminar el primero, el segundo se unirá a ambos const y non-const this.

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

El estándar lo dice: {[16]]}

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

Pero si haces esto:

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

Entonces, solo se ajustará la sobrecarga const.

 11
Author: WhiZTiM,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-19 15:33:54

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

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 compila en absoluto.

Así es como se diseñó el lenguaje. La intención es tratar de llamar a la mejor sobrecarga viable, en la medida de lo posible. Si falla, se activará un error para recordarle que considere el diseño de nuevo.

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

 9
Author: songyuanyao,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-19 18:12:30

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

Las funciones miembro constantes se llaman en objetos constantes.

 8
Author: Some programmer dude,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-19 15:05:05

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

De esta manera, si cambia un especificador de acceso, se le alertará 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 llamadas a funciones, el comportamiento de su programa podría cambiar silenciosamente.

 8
Author: Kyle Strand,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-19 15:24:00