2010-04-07 12 views
14

Tengo una clase de puntero automático y en el constructor le estoy pasando un puntero. Quiero poder separar lo nuevo de lo nuevo [] en el constructor para que pueda llamar correctamente eliminar o eliminar [] en el destructor. ¿Se puede hacer esto a través de la especialización de plantillas? No quiero tener que pasar un booleano en el constructor.¿Hay alguna manera de usar la especialización de plantilla para separar lo nuevo de lo nuevo []?

template <typename T> 
    class MyAutoPtr 
    { 
    public: 
     MyAutoPtr(T* aPtr); 
    }; 

// in use: 
MyAutoPtr<int> ptr(new int); 
MyAutoPtr<int> ptr2(new int[10]); 
+0

Buena pregunta de todos modos: eso realmente hace que desees un manejo adecuado de 'array' dentro del lenguaje mismo :( –

+0

No debes usar un parámetro booleano para distinguir los tipos. Como la decisión se toma en tiempo de compilación, deberías ejecútelo en el tipo, lo que significa que crea una clase de puntero inteligente para punteros normales y uno para matrices. –

Respuesta

3

std::unique_ptr en C++ 0x tendrá una especialización para matrices dinámicas, algo como se muestra a continuación. Sin embargo, la tarea del usuario será instanciar una instancia apropiada. En el nivel del lenguaje, no hay forma de distinguir un puntero de otro.

template <class T> 
class pointer 
{ 
    T* p; 
public: 
    pointer(T* ptr = 0): p(ptr) {} 
    ~pointer() { delete p; } 
    //... rest of pointer interface 
}; 

template <class T> 
class pointer<T[]> 
{ 
    T* p; 
public: 
    pointer(T* ptr = 0): p(ptr) {} 
    ~pointer() { delete [] p; } 
    //... rest of pointer and array interface 
}; 

int main() 
{ 
    pointer<int> single(new int); 
    pointer<int[]> array(new int[10]); 
} 

Además, puede que no sea tan bueno cargar una clase con tantas tareas diferentes. Por ejemplo, boost tiene shared_ptr y shared_array.

+0

En realidad, no entiendo por qué Boost tiene ambos, ya que 'shared_ptr' utiliza un parámetro' Deleter' en la construcción (predeterminado si no está presente) para que pueda tener uno para el caso de la matriz. –

+0

Una cosa que querrías de un apuntador a matriz es 'operator []'. Y luego 'shared_array p (new int [10])' puede ser una abreviatura conveniente para 'shared_ptr p (new int [10], array_deleter ());' aunque podría usar 'shared_ptr' con un eliminador bajo el capó. En general, creo que hay una gran diferencia entre tener un solo objeto y una matriz: si una función toma 'shared_ptr ', ¿también manejará matrices dinámicas? – visitor

+0

de esta manera tiene más sentido para mí, simplemente no me gusta la idea de hacer 2 clases separadas: D perezoso – Marlon

7

Por desgracia, no. Ambos devuelven el mismo tipo, T*. Considere el uso de funciones de constructor que llama a un constructor sobrecargado apropiado:

template <typename T> 
class MyAutoPtr 
{ 
public: 
    MyAutoPtr(T* aPtr, bool array = false); 
}; 

template <typename T> 
MyAutoPtr<T> make_ptr() { 
    return MyAutoPtr<T>(new T(), false); 
} 

template <typename T> 
MyAutoPtr<T> make_ptr(size_t size) { 
    return MyAutoPtr<T>(new T[size], true); 
} 

Ahora usted puede crear instancias de objetos de la siguiente manera:

MyAutoPtr<int> ptr = make_ptr<int>(); 
MyAutoPtr<int> ptr2 = make_ptr<int>(10); 
+0

Hay una manera de distinguir punteros y matrices cuando se pasa a una función templetizada, desafortunadamente, en lo que respecta al compilador, nueva y new [] coinciden exactamente con el mismo tipo de devolución. –

+1

@Ramon: es posible distinguir punteros de las matrices, pero 'new []' ** no ** crea una matriz. Las matrices en C++ son solo aquellos que se crean utilizando la sintaxis 'T [N]', con 'N' como una constante en tiempo de compilación. –

+1

' new [] 'crea una matriz.Simplemente no devuelve un puntero a matriz, devuelve un puntero al primer elemento de la matriz que ha creado. 5.3.4/1: "Si [la entidad] es una matriz, la * nueva-expresión * devuelve un puntero al elemento inicial de la matriz". –

2

Por otra parte, se puede utilizar una función específica make.

template <class T> 
MyAutoPtr<T> make(); 

template <class T> 
MyAutoPtr<T> make(size_t n); 

Por supuesto, esto significa que tiene la lógica adecuada detrás, pero está encapsulada. También puede agregar una sobrecarga tomando un T para copiar el objeto pasado al puntero recién creado, etc. ...

Finalmente, también se puede hacer con sobrecargas del constructor ... el punto es no llamar al new fuera .

2

Creo que la verdadera solución es deshacerse de su propia clase autopointer y deshacerse del uso de matrices de estilo C. Sé que esto se ha dicho muchas, muchas veces antes, pero realmente no tiene mucho sentido usar matrices de estilo C más. Casi todo lo que puede hacer con ellos se puede hacer usando std::vector o con boost::array. Y ambos crean distintos tipos, por lo que puede sobrecargarlos.

1

new[] está específicamente definido para tener un valor de puntero a pesar de la conversión implícita de matriz a puntero que se activaría de todos modos.

Pero no creo que estés de suerte. Después de todo, su ejemplo no está administrando un puntero a un int, está administrando un puntero a un int[10]. Así que la forma ideal es

MyAutoPtr<int[10]> ptr2(new int[10]); 

Como se menciona la nariz roja del unicornio, new int[10] no crea una matriz C-estilo. Lo hará si su compilador también cumple con el estándar C, pero C++ permite que las matrices de estilo C sean más que las matrices de estilo C en C. De todos modos, new que va a crear una matriz de estilo C si preguntas como esta:

MyAutoPtr<int[10]> ptr2(new int [1] [10]); 

Desafortunadamente, delete contents; no funcionará incluso con int (*contents)[10];.El compilador puede hacer lo correcto: el estándar no especifica que la matriz se convierta en un puntero como con new, y creo que recuerdo que GCC sustituyó delete[] y emitió una advertencia. Pero es un comportamiento indefinido.

Por lo tanto, necesitará dos destructores, uno para llamar al delete y otro para llamar al delete[]. Puesto que no puede especializarse en parte una función, la funcionalidad exige un ayudante parcialmente especializados

template< class T > struct smartptr_dtor { 
    void operator()(T *ptr) { delete ptr; } 
}; 

template< class T, size_t N > struct smartptr_dtor<T[N]> { 
    void operator()(T (*ptr) [N]) { delete [] ptr; } 
}; 

template< class T > 
void proper_delete(T *p) { 
    smartptr_dtor<T>()(p); 
} 

que por alguna razón yo sólo me sometí a; v)

Desafortunadamente, esto no funciona con dinámica- arreglos de tamaño, así que voy a escribir otra respuesta.

+0

@Potatocorn: 'new int [10]' does * not * create an 'int [10]'. Si bien el código que ha escrito se puede compilar, no tiene sentido. No hay conexión entre el tipo de plantilla y el valor del constructor. –

+0

@Red: ¿Cómo es el resultado de 'new T [10]' no un puntero a 'T [10]'? El tipo de retorno es 'T *' pero eso simplemente no refleja el tipo de objeto en la memoria. – Potatoswatter

+0

5.3.4/1: "Si la entidad es un objeto que no es de matriz, la nueva expresión devuelve un puntero al objeto creado. Si es una matriz, la nueva expresión devuelve un puntero al elemento inicial de la matriz " – Potatoswatter

1

Segundo intento ...

Es muy fácil hacer una clase de puntero inteligente inteligente acerca de las matrices. Como sospechabas, no necesitas un indicador de tiempo de ejecución o un argumento para el constructor si sabes que es una matriz para empezar. El único problema es que new y new[] tienen tipos de devolución idénticos, por lo que no pueden pasar esta información a la clase de puntero inteligente.

template< class T, bool is_array = false > 
struct smartptr { 
    T *storage; 

    smartptr(T *in_st) : storage(in_st) {} 

    ~smartptr() { 
     if (is_array) delete [] storage; // one of these 
     else delete storage; // is dead code, optimized out 
    } 
}; 

smartptr<int> sp(new int); 
smartptr< int, true > sp2(new int[5]); 

Una alternativa a la bandera bool es sobrecargar el significado de T[] como menciona Visitante std::unique_ptr ocurre en C++ 0x.

template< class T > 
struct smartptr { 
    T *storage; 

    smartptr(T *in_st) : storage(in_st) {} 

    ~smartptr() { delete storage; } 
}; 

template< class T > // partial specialization 
struct smartptr< T [] > { 
    T *storage; // "T[]" has nothing to do with storage or anything else 

    smartptr(T *in_st) : storage(in_st) {} 

    ~smartptr() { delete [] storage; } 
}; 

smartptr<int> sp(new int); 
smartptr<int[]> sp2(new int[5]); 
2

No es posible ya que new int[X] produce un puntero al elemento inicial de la matriz. Tiene el mismo tipo que int*.

Una de las soluciones más comunes es utilizar eliminadores. Agregue un argumento de plantilla más a su clase para que pueda pasar un eliminador personalizado para su puntero. Hará que tu clase sea más universal. Se puede crear Deleter por defecto como la siguiente:

struct default_deleter 
{ 
    template<typename T> 
    void operator()(T* aPtr) { delete aPtr; } 
}; 

Y para las matrices que podría pasar Deleter personalizado:

struct array_deleter 
{ 
    template<typename T> 
    void operator()(T* aPtr) { delete[] aPtr; } 
}; 

La implementación más sencilla será:

template <typename T, typename D> 
class MyAutoPtr 
{ 
public: 
    MyAutoPtr(T* aPtr, D deleter = default_deleter()) : ptr_(aPtr), deleter_(deleter) {}; 
    ~MyAutoPtr() { deleter_(ptr_); } 
protected: 
    D deleter_; 
    T* ptr_; 
}; 

entonces usted podría utilizarlo de la siguiente manera:

MyAutoPtr<int, array_deleter> ptr2(new int[10], array_deleter()); 

Puede hacer que su clase sea más compleja para que pueda deducir el tipo para eliminar.

Cuestiones relacionadas