2010-07-22 12 views
5

Estoy trabajando en una base de código C++ heredada muy grande que permanecerá sin nombre. Al ser una base de código heredado, pasa punteros sin procesar por todas partes. Pero estamos tratando de modernizarlo gradualmente, por lo que también hay algunas plantillas de punteros inteligentes. Estos punteros inteligentes (a diferencia, por ejemplo, Boost's scoped_ptr) tienen una conversión implícita al puntero sin formato, por lo que puede pasar uno de ellos a una rutina que toma un puntero sin formato sin tener que escribir .get(). Un gran inconveniente de esto es que también puedes usar accidentalmente uno en una declaración delete, y luego tienes un error libre doble, que puede ser un verdadero problema para localizar.Plantilla de "puntero inteligente" de C++ que se convierte automáticamente en puntero descubierto pero no se puede eliminar explícitamente

¿Hay alguna manera de modificar la plantilla para que todavía tenga la conversión implícita al puntero sin formato, pero provoca un error de compilación si se utiliza en una instrucción de eliminación? De esta manera:

#include <my_scoped_ptr> 

struct A {}; 
extern void f(A*); 

struct B 
{ 
    scoped_ptr<A> a; 

    B(); 
    ~B(); 
}; 

B::B() 
    : a(new A) 
{ 
    f(a); // this should compile 
} 

B::~B() 
{ 
    delete a; // this should NOT compile 
} 

Respuesta

7

La norma dice

El operando tendrán un tipo de puntero, o un tipo de clase tiene una función única de conversión (12.3.2) a un tipo de puntero. Si el operando tiene un tipo de clase, el operando se convierte a un tipo de puntero llamando a la función de conversión mencionada anteriormente, y el operando convertido se usa en lugar del operando original para el resto de esta sección.

Puede (ab) - utilizar la ausencia de resolución de sobrecarga declarando una versión constante de la función de conversión. En un compilador conforme que es suficiente para que sea ya no funciona con delete:

struct A { 
    operator int*() { return 0; } 
    operator int*() const { return 0; } 
}; 

int main() { 
    A a; 
    int *p = a; // works 
    delete a; // doesn't work 
} 

resultados en las siguientes

[[email protected] cpp]$ clang++ main1.cpp 
main1.cpp:9:3: error: ambiguous conversion of delete expression of type 'A' to a pointer 
    delete a; // doesn't work 
^ ~ 
main1.cpp:2:3: note: candidate function    
    operator int*() { return 0; } 
^
main1.cpp:3:3: note: candidate function    
    operator int*() const { return 0; } 
^
1 error generated. 

En los compiladores que son menos conformando en ese sentido (EDG/Comeau, GCC) puede hacer que la función de conversión sea una plantilla. delete no espera que un tipo particular, , así que esto funcionaría:

template<typename T> 
operator T*() { return /* ... */ } 

Sin embargo, esto tiene el inconveniente de que su SmartPointer ahora es convertible en cualquier puntero de tipo. Aunque la conversión real todavía se comprueba por tipo, pero esto no excluirá las conversiones por adelantado, sino que dará un error de tiempo de compilación mucho más tarde. Lamentablemente, SFINAE no parece ser posible con funciones de conversión en 03 :) Una manera diferente de C++ es devolver un puntero de tipo anidado privada de la otra función

struct A { 
    operator int*() { return 0; } 

private: 
    struct nested { }; 
    operator nested*() { return 0; } 
}; 

El único problema ahora es con una conversión a void*, en cuyo caso ambas funciones de conversión son igualmente viables. Una solución alternativa sugerida por @Luther es devolver un tipo de puntero a función de la otra función de conversión, que funciona tanto con GCC como con Comeau y elimina el problema void* sin tener otros problemas en las rutas de conversión habituales, a diferencia de la solución de plantilla

struct A { 
    operator int*() { return 0; } 

private: 
    typedef void fty(); 
    operator fty*() { return 0; } 
}; 

Tenga en cuenta que estas soluciones solo son necesarias para los compiladores que no se ajustan.

+0

Haría eso, ¿verdad? – GManNickG

+0

g ++ está indeciso, 4.4 no compila, 4.5 lo hace. Si cambio el tipo de la segunda conversión a un tipo de puntero a función, entonces g ++ lo hace bien y se detiene en 'eliminar a'. –

+0

@Luther gracias, agregué :) –

1

Se puede usar una técnica presentada por Boost, pero mi preocupación es que se va a permitir conversiones implícitas de un puntero inteligente para un puntero prima, que es generalmente mal visto en. Además, los usuarios pueden llamar al delete en un puntero obtenido por el operador ->, por lo que no hay nada que puedas hacer para evitar que un idiota determinado solucione el mecanismo que se te ocurra.

Realmente debe implementar un método get() en lugar de proporcionar operator T*() para que al menos las llamadas a delete smartptr no se compilen. Los no idiotas deberían ser capaces de descubrir que probablemente haya una razón por la cual eso no funcionará.

sí, es más trabajo que escribir LegacyFunc(smartptr.get()) de LegacyFunc(smartptr), pero se prefiere el primero, ya que hace que sea explícita y evita las conversiones inesperadas suceda, como delete smartptr.

¿Qué pasa si usted tiene funciones como este:

void LegacyOwnPointer(SomeType* ptr); 

donde la función almacenará el puntero en alguna parte? Esto arruinará el puntero inteligente, porque ahora no es consciente de que otra cosa posee el puntero sin procesar.

De cualquier manera, usted tiene un trabajo que hacer. Los punteros inteligentes son como punteros crudos, pero no son lo mismo, por lo que no puede simplemente buscar y reemplazar todas las instancias de T* y reemplazarlo por my_scoped_ptr<T> y esperar que funcione igual de bien que antes.

+0

Ese es un problema diferente. – GManNickG

+0

Si está mal visto o no, sería inútil tratar de calzar los punteros inteligentes en esta base de código heredado sin una conversión implícita al puntero sin formato. Hay demasiados lugares donde necesita proporcionar el puntero sin formato. - Tu sugerencia solo ayuda si puedo modificar la clase apuntada, que no puedo, en general. Necesito una técnica que funcione completamente dentro de la clase de puntero inteligente. – zwol

+1

Realmente no creo que aprecies las realidades de trabajar en una base de código de 3 millones de líneas y 15 años. Si tomo la conversión implícita de la plantilla del puntero inteligente (que ya está muy usada), tendría que poner * miles * de llamadas a get() y mis compañeros de trabajo me lincharían. – zwol

4

No hay forma de detener una y no la otra. En cualquier lugar donde se pueda convertir implícitamente a un puntero para una llamada a función, se puede convertir implícitamente para una expresión de eliminación.

Su mejor opción es eliminar la función de conversión. Su situación es exactamente la razón por la cual los operadores de conversión definidos por el usuario son peligrosos y no deben usarse con frecuencia.


I'm wrong. :(

+0

Ver mi respuesta a "In silicio". Soy consciente de los argumentos en contra de las conversiones implícitas, pero simplemente no es práctico prescindir de ellas en este contexto. – zwol

+0

@Zack: Entonces simplemente no tendrá que eliminarlos. Esa es su elección: elimine la conversión implícita y use explícitamente 'get()' para las funciones, o deje las conversiones implícitas y espere que no se invoque la eliminación. – GManNickG

+0

Temo que tengas razón, pero voy a dejar la pregunta abierta por un día más o menos en caso de que a alguien se le ocurra algo inteligente. – zwol

0

no he pensado mucho sobre esto pero ... ¿Puede proporcionar una sobrecarga para la eliminación del operador que está fuertemente tipada para las instancias de su clase de plantilla de modo que cuando se incluye el código falla la compilación? si esto está en su archivo de encabezado, se debe evitar la conversión implícita en las llamadas a eliminar a favor de una llamada a su sobrecarga.

operador delete (my_scoped_ptr) {// ... código uncompilable va aquí}

Disculpas si esto resulta ser una idea estúpida.

+0

No, eso se invocaría para expresiones de "eliminar puntero-a-puntero-inteligente", pero no para "borrar puntero inteligente", que está causando el problema. –

0

Veo dónde no desea hacer una aplicación masiva de .get() 's. ¿Alguna vez ha considerado un reemplazo mucho más pequeño de eliminar?

struct A 
{ 
    friend static void Delete(A* p) { delete p; } 

private: 
    ~A(){} 
}; 

struct B 
{ 
}; 

int main() 
    { 

    delete new B(); //ok 

    Delete(new A); //ok 

    delete new A; //compiler error 

    return (0); 
    } 
+0

Esto solo ayuda si puedo modificar la clase apuntada, lo cual no puedo en general. – zwol

Cuestiones relacionadas