2010-06-16 12 views
5

En el siguiente código, hay dos llamadas "equivalentes" a std::for_each utilizando expresiones boost:bind. La línea indicada compila, la línea de falla indicada falla. La mejor explicación que puedo encontrar en el estándar equivale a "porque así lo dijimos". Estoy buscando "por qué el estándar indica este comportamiento". Mis suposiciones están abajo.boost :: bind with protected members & context

Mi pregunta es sencilla: ¿Por qué la línea indicada no se compila y la siguiente línea no se puede compilar (y no quiero porque "el estándar lo dice", ya sé que - No aceptaré ninguna respuesta que dar esto como una explicación; me gustaría una explicación en cuanto a por qué el estándar lo dice).

Notas: Aunque uso boost, boost es irrelevante para esta pregunta, y el error en varios formatos se ha reproducido usando g ++ 4.1. * Y VC7.1.

#include <boost/bind.hpp> 
#include <iostream> 
#include <map> 
#include <algorithm> 

class Base 
{ 
protected: 
     void foo(int i) 
     { std::cout << "Base: " << i << std::endl; } 
}; 

struct Derived : public Base 
{ 
     Derived() 
     { 
       data[0] = 5; 
       data[1] = 6; 
       data[2] = 7; 
     } 

     void test() 
     { 
       // Compiles 
       std::for_each(data.begin(), data.end(), 
         boost::bind(&Derived::foo, this, 
           boost::bind(&std::map<int, int>::value_type::second, _1))); 

       // Fails to compile - why? 
       std::for_each(data.begin(), data.end(), 
         boost::bind(&Base::foo, this, 
           boost::bind(&std::map<int, int>::value_type::second, _1))); 
     } 

     std::map<int, int> data; 
}; 

int main(int, const char**) 
{ 
     Derived().test(); 

     return 0; 
} 

la línea indicada falla con este error: MAIN.C: En función miembro 'vacío Derivado :: test()': MAIN.C: 9: error: 'vacío Base :: foo (int) 'está protegido main.C: 31: error: dentro de este contexto

Como se indicó anteriormente, la declaración supuestamente equivalente anterior se compila limpiamente (y si la declaración ofensiva está comentada, se ejecuta con el resultado esperado de imprimir "5" , "6", "7" en líneas separadas).

bien la búsqueda de una explicación, me encontré con 11.5.1 de la norma (en concreto, estoy mirando el proyecto 2006-11-06):

An additional access check beyond those described earlier in clause 11 is applied when a non-static data member or nonstatic member function is a protected member of its naming class (11.2)105) As described earlier, access to a protected member is granted because the reference occurs in a friend or member of some class C. If the access is to form a pointer to member (5.3.1), the nested-name-specifier shall name C or a class derived from C. All other accesses involve a (possibly implicit) object expression (5.2.5). In this case, the class of the object expression shall be C or a class derived from C.

Después de leer esto, se hizo evidente por qué la segunda declaración falló mientras que la primera tuvo éxito, pero luego surgió la pregunta: ¿Cuál es la razón de ser de esto?

Mi idea inicial fue que el compilador estaba expandiendo las plantillas boost :: bind, descubriendo que Base :: foo estaba protegido y pateándolo porque boost :: bind < ...> no era un amigo. Pero, cuanto más pensaba en esta explicación, menos tenía sentido, porque si recuerdo correctamente, tan pronto como toma el puntero a un miembro (suponiendo que esté inicialmente dentro del control de acceso del miembro), toda la información de control de acceso es perdido (es decir, podría definir una función que devuelve un puntero arbitrario a un miembro que alternativamente devuelve un miembro público, protegido o privado, dependiendo de alguna entrada, y el que regresa no sabría nada).

Más lo pensé, y la única explicación plausible que pude dar con por qué debería marcar la diferencia fue en el caso de la herencia múltiple. Específicamente, dependiendo del diseño de la clase, el puntero del miembro cuando se calcule desde Base sería diferente al calculado desde Derivado.

+0

Notaré de inmediato: es probable que necesite un mejor tema. Si alguien puede darme uno mejor, lo cambiaré. Gracias por adelantado. –

+1

Porque el estándar lo dice. –

+1

Parece que 'Base :: foo' debe tener' virtual'. ¿Funciona si agregas eso? – zildjohn01

Respuesta

5

Todo se trata de "contexto". En la primera llamada, el contexto de la llamada es Derived que tiene acceso a los miembros protegidos de Base y, por lo tanto, puede tomar direcciones de ellos. En el segundo, el contexto está "fuera de" Derived y, por lo tanto, fuera de Base, por lo que no se permite el acceso del miembro protegido.

1

En realidad, esto parece lógico. La herencia le da acceso a Derived :: foo y no a Base :: foo. Permítanme ilustrarlo con un ejemplo de código:

struct Derived : public Base 
{ 
    void callPrivateMethod(Base &b) 
    { 
     // this should obviously fail 
     b.foo(5); 

     // pointer-to-member call should also fail 
     void (Base::*pBaseFoo) (int) = &Base::foo; // the same error as yours here 
     (b.*pBaseFoo)(5); 
    } 
}; 
0

El motivo de esta restricción es la aplicación del control de acceso en diferentes clases que comparten una base común.

Esto se ve reforzado por las notas en Core Language Defects Report defect #385, la parte correspondiente copiado aquí por referencia:

[...] the reason we have this rule is that C 's use of inherited protected members might be different from their use in a sibling class, say D . Thus members and friends of C can only use B::p in a manner consistent with C 's usage, i.e., in C or derived-from- C objects.

Como un ejemplo de algo esta regla impide:

class B { 
protected: 
    void p() { }; 
}; 

class C : public B { 
public: 
    typedef void (B::*fn_t)(); 
    fn_t get_p() { 
     return &B::p; // compilation error here, B::p is protected 
    } 
}; 

class D : public B { }; 

int main() { 
    C c; 
    C::fn_t pbp = c.get_p(); 
    B * pb = new D(); 
    (pb->*pbp)(); 
} 

El estado de protección de D::p es algo queremos que el compilador haga cumplir, pero si se compila lo anterior, ese no sería el caso.

Cuestiones relacionadas