2010-02-16 18 views
18

Estaba probando la validez del private especificador de acceso en C++. Aquí va:C++ ¿Es privado realmente privado?

Interfaz:

// class_A.h 

class A 
{ 
public: 
    void printX(); 
private: 
    void actualPrintX(); 
    int x; 
}; 

Implementación:

// class_A.cpp 
void A::printX() 
{ 
    actualPrintX(); 
} 

void A::actualPrintX() 
{ 
    std::cout << x: 
} 

Construí esto en una biblioteca estática (.a/lib). Ahora tenemos un par class_A.h y classA.a (o classA.lib). Edité class_A.h y eliminé el private: de él.

Ahora en otro classTester.cpp:

#include "class_A.h" // the newly edited header 

int main() 
{ 
    A a; 

    a.x = 12;   // both G++ and VC++ allowed this! 
    a.printX();   // allowed, as expected 
    a.actualPrintX(); // allowed by G++, VC++ gave a unresolved linker error 

    return 0; 
} 

Yo sé que después de la manipulación de cabecera de una biblioteca de todas las apuestas están apagadas (quiero decir, la integridad del sistema, etc.) Aunque el método siendo hacky, ¿es realmente ¿permitido? ¿Hay alguna manera de bloquear esto? ¿O estoy haciendo algo mal aquí?

+0

Sé que el diseño de Cheshire Cat (Pimpl - impl. Privado implícito. Http://en.wikipedia.org/wiki/Cheshire_Cat_Idiom_%28programming_technique%29) es posible aquí y también que los especificadores de acceso son guardias de tiempo de compilación por el compilador . – legends2k

+0

¿Por qué diablos no puedes simplemente reconstruirlo? –

+0

@Dominic: Mi intención era ver qué sucede si intento acceder a partes privadas de una clase. – legends2k

Respuesta

30

private es no un mecanismo de seguridad. Es una forma de comunicar intenciones y ocultar información que otras partes de su programa no necesitan conocer, lo que reduce la complejidad general.

Tener dos diferentes archivos de cabecera no es compatible con los estándares, por lo que técnicamente usted está entrando en territorio comportamiento indefinido, pero en la práctica, ya que ha encontrado, la mayoría de los compiladores no les importa.

7

No. El control de acceso privado está ahí para evitar que USTED haga cosas estúpidas, no como un mecanismo de seguridad para evitar que otros accedan a sus datos o funciones. Hay muchas, muchas formas de evitarlo.

2

No hay implementación A :: actualPrintX en ninguna parte. Ese es su error de enlazador.

+0

Gracias por notificar, es un error tipográfico, lo he arreglado ahora :) – legends2k

+0

Chicos, por favor no lo voten por votación. Anteriormente en la pregunta, acabo de poner 'actualPrintX()' en lugar de 'A :: actualPrintX()'; él me acaba de notificar de ese error tipográfico. – legends2k

9

Usted ha desviado más allá de lo que está permitido en C++, así que lo que estás haciendo no está permitido - pero por supuesto que puede funcionar en algunos compiladores en algunas situaciones.

En concreto, está infringiendo el One Definition Rule.

Este article de Herb Sutter lo explica muy bien, sino que también proporciona una forma legal y portátil de eludir el sistema especificador de acceso.

+0

Pero no he definido nada más de una vez; De acuerdo, he manipulado la declaración, pero ¿cómo es que se convierte en un caso de violación de la ODR? – legends2k

+2

Ha definido 'clase A' dos veces: una vez en la unidad de traducción' classTester.cpp' y una vez en la unidad de traducción 'class_A.cpp'. La violación de ODR es que esas dos definiciones no son idénticas. –

+0

¡Aargh, lo veo ahora! Gracias por el artículo de Sutter sobre lo mismo. – legends2k

5

Lo intenté. Este es un nm de un programa que escribí teniendo una prueba de clase con un método privado y uno privado.

0000000100000dcc T __ZN4Test3barEv 
0000000100000daa T __ZN4Test3fooEv 

Como puede ver, la firma es exactamente la misma. El enlazador no tiene absolutamente nada para distinguir un método privado de uno público.

+0

Sí, como he mencionado, en G ++ no hubo ningún error para acceder a 'actualPrintX()'; pero los símbolos en VC++ parecen tener alguna diferencia y, por lo tanto, el vinculador arrojó un error. ¡Gracias! – legends2k

5

Estoy de acuerdo con la mayoría de las otras respuestas.

Sin embargo, me gustaría señalar que es perfectamente aceptable para un compilador organizar físicamente los miembros de manera diferente cuando elimina ese private. Si funciona, eso es suerte. No puedes contar con eso Si ambos lados no usan la misma declaración, en realidad no están usando la misma clase.

2

Como nadie ha mencionado una forma de bloquear esto ...Una forma posible de bloquear el acceso a miembros privados es declararlos como un tipo interno independiente no visible fuera del archivo. Por supuesto, si desea proporcionar acceso explícito a este interno, entonces deberá proporcionar la declaración interna. Eso también se hace comúnmente usando un encabezado interno que contiene ese tipo.

Nota: Deberá realizar un seguimiento de la asignación/liberación de ese objeto interno en este ejemplo. Hay otras formas de hacerlo que no requieren esto.

// class_A.h 
class A { 
public: 
    void printX(); 
private: 
    void *Private; 
}; 

// class_A.cpp 
class A_private { 
    void actualPrintX(); 
    int x; 
}; 

void A::printX() { 
    reinterpret_cast<A_private *>(Private)->actualPrintX(); 
} 

void A_private::actualPrintX() { 
    std::cout << x: 
} 
+0

Del mismo modo, al lado de la pregunta, en los comentarios, mencioné también el método Cheshire Cat, que es similar a esto, solo sin reinterpret_cast. – legends2k

+0

Disculpa, debo haberme perdido eso; es bueno saber que en realidad tiene un nombre. – Ioan

1

En la mayoría de los casos, ni siquiera tiene que editar el archivo de encabezado para hacer públicos los miembros privados. Puedes hacer esto con el preprocesador. Algo como esto:

//"classWithPrivateMembers.hpp" 
class C 
{ 
private: //this is crucial for this method to work 
    static int m; 
}; 

int C::m = 12; 

y luego, esto va a funcionar:

#define private public 
#include "classWithPrivateMembers.hpp" 
#undef private 

int main() 
{ 
    C::m = 34; // it works! 
} 
+0

También puede agregar '#define class struct' para capturar vars privados no calificados. – mskfisher

1

Tenga en cuenta también que cuando se cambia el acceso de un miembro variable, el compilador puede colocarlo en un desplazamiento diferente dentro de la clase objeto. El estándar permite a los compiladores una gran cantidad de libertad en la reorganización de los miembros (al menos dentro del mismo nivel de acceso, creo).