2012-04-19 16 views
5

He estado trabajando en la creación de un asignador personalizado como ejercicio/práctica divertida y me encontré con dos posibles problemas para crear matrices. Para una llamada típica de asignación, usaré malloc y placement new. Sin embargo, cuando voy a crear una matriz, estoy confundido sobre cómo se debe hacer. Por una vez, he notado en algunos lugares que parece que placement new puede no ser seguro para matrices como here. También estoy incurriendo en un error al intentar usar placement new para una matriz. I obtendrá el error deCómo crear una matriz mientras utilizo la ubicación nueva

`C2679 error: binario '=': ningún operador encontró que toma un operando de la derecha del tipo 'SomeClass *' (o no hay conversión aceptable)

Entiendo el error (creo) pero preferiría que el error se solucione a través de mi método de construcción de matriz. Tengo dos preguntas

1) ¿Cómo puede un asignador crear una matriz sin usar new[]? ¿Es con placement new? Si es así, ¿qué pasa con los posibles peligros mencionados en el enlace que publiqué anteriormente?

2) Si se supone que debo usar placement new y llamarlo a cada uno de los elementos en la matriz, ¿por qué recibo el error mencionado anteriormente?

#include <stdio.h> 
#include <new> 

class SomeClass{ 
public: 
    SomeClass() { 
     printf("Constructed\n"); 
    } 

    ~SomeClass() { 
     printf("Destructed\n"); 
    } 
}; 

void* SomeAllocationFunction(size_t size) { 
    return malloc(size); 
} 

template<typename Type> 
Type* SomeArrayAllocationFunction(size_t count){ 
    Type* mem = (Type*)SomeAllocationFunction(sizeof(Type) * count); 

    for(unsigned int i = 0; i < count; ++i) 
    { 
     mem[i] = new(mem + i) Type(); 
    } 

    return mem; 
} 

int main(void){ 
    SomeClass* t = SomeArrayAllocationFunction<SomeClass>(2); 
} 

Respuesta

1

1) ¿Cómo puede un asignador crear una matriz sin usar new []? ¿Es con la colocación nueva? Si es así, ¿qué pasa con los posibles peligros mencionados en el enlace que publiqué anteriormente?

El problema en el enlace es no entender cómo funcionan las cosas. Cada implementación tiene una implementación definida para registrar información sobre una matriz asignada. Esta información no se requiere con objetos únicos, ya que es administrada por el cliente a través de la implementación de delete.

Con una matriz, la implementación debe registrar elementos tales como recuento de elementos, destructor para llamar (si corresponde), tamaño del elemento ... esto a menudo se almacena al inicio de la asignación devuelta, y la implementación obviamente compensa el tamaño de la solicitud de asignación apropiadamente. Por lo tanto, el tamaño real se compensa para acomodar estos valores ocultos. Esta es la razón por la cual malloc(sizeof...) no funcionará a menos que su asignador haga la contabilidad adicional (que, por cierto, std::allocator y las interfaces de colecciones traen).

Para registrar esta información correctamente, puede definir static void* operator new[]. Para incorporar su propio asignador en este esquema a través de la colocación, se puede utilizar el siguiente enfoque:

// quick/dirty/incomplete illustration: 
#include <stdio.h> 
#include <new> 
#include <cstdlib> 

class t_allocator { 
public: 
    t_allocator() { 
    } 

    ~t_allocator() { 
    } 

public: 
    void* allocate(const size_t& size) { 
     return malloc(size); 
    } 
}; 

class SomeClass { 
public: 
    SomeClass() { 
     printf("Constructed\n"); 
    } 

    ~SomeClass() { 
     printf("Destructed\n"); 
    } 

public: 
    static void* operator new[](size_t size, t_allocator& allocator) { 
     return allocator.allocate(size); 
    } 

    /* in case static void* operator new[](size_t size, t_allocator& allocator) throws: */ 
    static void operator delete[](void* object, t_allocator& allocator) { 
     /* ... */ 
    } 

    static void operator delete[](void* object) { 
     /* matches t_allocator::allocate */ 
     free(object); 
    } 
}; 

int main(void) { 
    t_allocator allocator; 
    SomeClass* t(new (allocator) SomeClass[2]); 

    delete[] t; 
    t = 0; 

    return 0; 
} 

nota que llevaría a cabo de manera similar la colocación operator delete[] si su asignador puede lanzar.

si desea que su asignador haga cierta contabilidad, se vuelve complicado. Personalmente, no creo que esta situación sea implementada bien por el lenguaje, especialmente dado que la inicialización de la matriz no se implementó bien. siempre habrá un paso adicional para realizar cerca de la construcción o destrucción, o algunos datos accesibles a nivel mundial para usar en este contexto.

2) Si se supone que debo utilizar la colocación nueva y llamarla a cada uno de los elementos de la matriz, ¿por qué recibo el error mencionado anteriormente?

que había necesidad de construir de forma explícita los elementos si va a crear un asignador que no pasa a través operator new/operator new[]. extendiendo el ejemplo anterior, querrá un método de destrucción que llamó a delete[], luego le dijo this para liberar/reutilizar la memoria (en lugar del uso de free anterior).

si solo quiere una solución rápida, necesitará arrastrar el destructor, el tamaño y el recuento de elementos con la asignación o el asignador. en ese escenario, no usa new[]/delete[].

Editar

y si desea administrar los libros usted mismo, aquí es uno de los enfoques (que podría ir muchas direcciones):

#include <cassert> 
#include <stdio.h> 
#include <new> 
#include <cstdlib> 

class t_allocator { 
public: 
    t_allocator() { 
    } 

    ~t_allocator() { 
    } 

public: 
    /** tracks an array allocation's data. acts as a scope container for the allocation/types. */ 
    class t_array_record { 
    public: 
    typedef void (*t_destructor)(void* const); 

    template<typename T> 
    t_array_record(T*& outObjects, t_allocator& allocator, const size_t& count) : d_mem(allocator.allocate(sizeof(T), count)), d_destructor(t_allocator::t_array_record::Destruct<T>), d_size(sizeof(T)), d_count(count), d_allocator(allocator) { 
     assert(this->d_mem); 
     /* mind exceptions */ 
     char* const cptr(reinterpret_cast<char*>(this->d_mem)); 

     for (size_t idx(0); idx < this->d_count; ++idx) { 
     /* assignment not required here: */ 
     new (&cptr[this->d_size * idx]) T(); 
     } 

     outObjects = reinterpret_cast<T*>(this->d_mem); 
    } 

    ~t_array_record() { 
     assert(this->d_mem); 
     char* const cptr(reinterpret_cast<char*>(this->d_mem)); 

     for (size_t idx(0); idx < this->d_count; ++idx) { 
     const size_t element(this->d_count - idx - 1U); 
     this->d_destructor(& cptr[this->d_size * element]); 
     } 

     this->d_allocator.free(this->d_mem); 
    } 

    private: 
    template<typename T> 
    static void Destruct(void* const ptr) { 
     T* const obj(reinterpret_cast<T*>(ptr)); 

     obj->~T(); 
    } 

    private: 
    void* const d_mem; 
    t_destructor d_destructor; 
    const size_t d_size; 
    const size_t d_count; 
    t_allocator& d_allocator; 
    public: 
    t_array_record(const t_array_record&); 
    t_array_record& operator=(const t_array_record&); 
    }; 
public: 
    void* allocate(const size_t& size, const size_t& count) { 
    return malloc(size * count); 
    } 

    void free(void* const mem) { 
    ::free(mem); 
    } 
}; 

Demostración:

class SomeClass { 
public: 
    SomeClass() { 
    printf("Constructed\n"); 
    } 

    virtual ~SomeClass() { 
    printf("Destructed\n"); 
    } 

    virtual void greet() { 
    printf("hi: %p\n", this); 
    } 

private: 
    SomeClass(const SomeClass&); 
    SomeClass& operator=(const SomeClass&); 
}; 

class SomeDer : public SomeClass { 
    static int& N() { 
    static int a(0); 

    return ++a; 
    } 

public: 
    SomeDer() : d_number(N()) { 
    printf("Ctor-%i\n", this->d_number); 
    } 

    virtual ~SomeDer() { 
    printf("~Der%i-", this->d_number); 
    } 

    virtual void greet() { 
    printf("Der%i-", this->d_number); 
    SomeClass::greet(); 
    } 

private: 
    const int d_number; /* << so we have different sized types in the example */ 
}; 

template<typename T> 
void TryIt(const size_t& count) { 
    t_allocator allocator; 

    T* things(0); 
    t_allocator::t_array_record record(things, allocator, count); 

    for (size_t idx(0); idx < count; ++idx) { 
    things[idx].greet(); 
    } 
} 

int main() { 
    TryIt<SomeClass>(3); 
    TryIt<SomeDer>(9); 
    return 0; 
} 
+0

¿Cómo se podría hacer su ejemplo sin sobrecargar nuevo [] y eliminar [] para la clase? Esperaba no tener que sobrecargar todas mis clases para nuevo y eliminar, sino simplemente usar un asignador. – mmurphy

+0

@mmurphy si esa es su preferencia, puede definirlos como operadores libres en su lugar. otro enfoque está en el correo ... – justin

+0

@mmurphy agregó una demostración de administración explícita – justin

1

mem[i] tiene tipo Type& mientras new(mem + i) Type(); tiene tipo Type*. Estos son tipos claramente incompatibles y no se pueden asignar. I cree puede eliminar la asignación por completo y se resolverá, aún inicializando la memoria en esa ubicación para usted.

Todavía sería un poco cauteloso con la implementación de sus propios asignadores de matriz (un asignador personalizado para vector sería más obvio, por ejemplo).

+0

Ah sí, 'new (mem + i) Type();' parece ser todo lo que se necesita. Sin embargo, ¿es peligroso utilizar 'placement new' para asignar matrices? Me parece encontrar algunos lugares donde se considera peligroso. – mmurphy

+0

@mmurphy No es peligroso, si sabes lo que estás haciendo. simplemente no es necesario en tu caso. – RedX

+0

@RedX: ¿Cómo es que el uso de la colocación nueva no es necesario en mi caso? – mmurphy

Cuestiones relacionadas