c++ - tipos - RAII de Stroustrup y el operador de reparto ARCHIVO*()=contradicción?
uber noticias mexico (4)
Estaba leyendo el C ++ de Stroustrup (3ed, 1997) para ver cómo implementó el RAII, y en la página 365 encontré esto:
class File_ptr{
FILE* p;
public:
File_ptr(const char* n, const char* a){p = fopen(n, a);}
File_ptr(FILE* pp) { p = pp; }
~File_ptr() {fclose(p);}
operator FILE* () {return p;}
};
La implementación de constructores y destructores es obvia y cumple con el lenguaje RAII, pero no entiendo por qué usa el operator FILE* () {return p;}
.
Esto resultaría en usar File_ptr
de la siguiente manera:
FILE* p = File_ptr("myfile.txt", "r");
El resultado en una p
cerrada, que es semánticamente inadecuado en este caso. Además, si File_ptr
está destinado a ser utilizado como RAII, este operador permite que se utilice incorrectamente como en el ejemplo. ¿O me estoy perdiendo algo?
No entiendo por qué usa el operador ARCHIVO * () {return p;}.
El motivo del operador es proporcionar acceso / compatibilidad para las API que usan un ARCHIVO *. El problema con la implementación es que permite el código de cliente similar a lo que dio como ejemplo.
Esto resultaría en usar File_ptr de la siguiente manera:
FILE* p = File_ptr("myfile.txt", "r");
No actualmente. Si bien puedes hacer esto, la definición de la clase no da como resultado un código como este. Depende de usted evitar escribir este tipo de código (de la misma manera que normalmente depende de usted escribir código que evite problemas de administración de por vida).
El ejemplo de RAII en su pregunta es un ejemplo de diseño deficiente. El operador de conversión podría ser evitado.
Lo reemplazaría con un FILE *const File_ptr::get() const
accessor. Ese cambio no elimina el problema, pero hace que sea más fácil ver en el código del cliente que está devolviendo un puntero constante (es decir, "Código de cliente, no borre esto"), de una clase.
El pensamiento ha avanzado bastante desde 1997 como resultado de la experiencia, y una de las principales recomendaciones ahora es no tener operadores de reparto implícitos debido a problemas como este. Anteriormente se pensaba que era mejor tener un operador de conversión implícito para facilitar la adaptación al código existente, pero esto ocasionaba problemas cuando la función destruye el recurso, ya que la clase de envoltura RAII no lo sabe.
La convención moderna es proporcionar acceso al puntero subyacente pero darle un nombre para que al menos sea explícito. No detectará todos los problemas posibles, pero facilita el grep para posibles violaciones. Por ejemplo, con std::string
es c_str()
:
std::string myString("hello");
callFunctionTakingACharPointer(myString.c_str());
// however...
delete myString.c_str(); // there''s no way of preventing this
Parece que es inevitable el mal precio por comodidad. Una vez que desee una forma en la que FILE*
pueda extraer de su elegante clase RAII, puede ser mal utilizado. ¿Será el método operator FILE*()
o FILE* getRawPtr()
, o lo que sea, se puede llamar en un objeto temporarty, haciendo que el resultado no sea válido justo después de que se devuelva.
Sin embargo, en C ++ 11, puede hacer esto un poco más seguro, al rechazar esta llamada en temporarios, como este:
operator FILE* () & { return p; }
// Note this -----^
Enlace útil sobre cómo funciona provisto por Morwenn en los comentarios: ¿Qué es "rvalue reference for * this"?
eso no está siguiendo la regla de 3 (por no hablar de 5),
así que declarar una función como Bar* createBarFromFile(File_ptr ptr)
hará cosas inesperadas (el archivo se cerrará después de llamar a esta función)
necesita definir un constructor de copia y un constructor de asignación de copia. Para la regla de 5 también necesita las variantes de movimiento.
sin embargo, si me veo obligado a usar un FILE*
como campo miembro, prefiero usar un std::unique_ptr<FILE, int (__cdecl *)(FILE *)>
y pasar &fclose
en el constructor