Actualmente, estoy leyendo el código fuente de Protocol Buffer
, y encontré un enum
código extraño definido aquí
~scoped_ptr() {
enum { type_must_be_complete = sizeof(C) };
delete ptr_;
}
void reset(C* p = NULL) {
if (p != ptr_) {
enum { type_must_be_complete = sizeof(C) };
delete ptr_;
ptr_ = p;
}
}
¿Por qué enum { type_must_be_complete = sizeof(C) };
se define aquí? ¿Para qué se usa esto?
ptr_
en elsizeof
como ensizeof(*ptr_)
lugar desizeof(C)
.Respuestas:
Este truco evita UB al garantizar que la definición de C esté disponible cuando se compile este destructor. De lo contrario, la compilación fallaría ya que el
sizeof
tipo incompleto (tipos declarados hacia adelante) no se puede determinar, pero se pueden usar los punteros.En binario compilado, este código se optimizaría y no tendría ningún efecto.
Tenga en cuenta que: Eliminar el tipo incompleto puede ser un comportamiento indefinido de 5.3.5 / 5 :.
g++
incluso emite la siguiente advertencia:fuente
sizeof(C)
fallará en tiempo de compilación siC
no es un tipo completo. Establecer un ámbito localenum
hace que la declaración sea benigna en tiempo de ejecución.Es una forma en que el programador se protege de sí mismo: el comportamiento de un tipo subsiguiente
delete ptr_
en un tipo incompleto no está definido si tiene un destructor no trivial.fuente
delete
? Y si es así, ¿por qué el compilador no lo detecta de todos modos?C = void
? SiC
fuera simplemente un tipo indefinido, ¿nodelete
fallaría ya la declaración?Para entender el
enum
, comience considerando el destructor sin él:~scoped_ptr() { delete ptr_; }
donde
ptr_
esta aC*
. Si el tipoC
es incompleta en este punto, es decir, todo lo que el compilador sabe que esstruct C;
, a continuación, (1) un defecto generado no hacer nada destructor se utiliza para la instancia C señaló. Es poco probable que eso sea lo correcto para un objeto administrado por un puntero inteligente.Si la eliminación mediante un puntero a tipo incompleto siempre tuvo un comportamiento indefinido, entonces el estándar podría requerir que el compilador lo diagnostique y falle. Pero está bien definido cuando el destructor real es trivial: conocimiento que el programador puede tener, pero el compilador no. No entiendo por qué el lenguaje define y permite esto, pero C ++ admite muchas prácticas que hoy en día no se consideran mejores prácticas.
Un tipo completo tiene un tamaño conocido y, por lo tanto,
sizeof(C)
se compilará si y solo siC
es un tipo completo, con un destructor conocido. Por lo que se puede utilizar como guardia. Una forma sería simplemente(void) sizeof(C); // Type must be complete
Me supongo que con un poco compilador y las opciones del compilador optimiza la basura antes de que pudiera notar que no debería compilar, y que el
enum
es una manera de evitar este tipo de comportamiento no conforme compilador:enum { type_must_be_complete = sizeof(C) };
Una explicación alternativa para la elección de
enum
una expresión en lugar de simplemente descartada, es simplemente una preferencia personal.O como sugiere James T. Hugget en un comentario a esta respuesta, "La enumeración puede ser una forma de crear un mensaje de error pseudoportátil en tiempo de compilación".
(1) El destructor de hacer nada generado por defecto para un tipo incompleto era un problema con el antiguo
std::auto_ptr
. Fue tan insidioso que se abrió paso en un artículo de GOTW sobre el idioma PIMPL , escrito por el presidente del comité internacional de estandarización de C ++ Herb Sutter. Por supuesto, hoy en día esostd::auto_ptr
está desaprobado, en su lugar se utilizará algún otro mecanismo.fuente
sizeof(T)
evaluado a 0 para tipos incompletos en lugar de fallar en la compilación. Sin embargo, este es un comportamiento no conforme. (2) Desde C ++ 11, el usostatic_assert((sizeof(T) > 0), "T must be a complete type");
sería una solución superior (e idiomática).sizeof(T)
evaluar a 0 para tipos incompletos".static_assert(sizeof(T) > 0, "…");
en sus respectivas implementaciones destd::unique_ptr
para asegurarse de que el tipo esté completo…sizeof(T)
en un contexto booleano es exactamente equivalente a probarsizeof(T) > 0
, en realidad no importa, excepto quizás por razones estéticas.Tal vez
C
se defina un truco para estar seguro .fuente