Definición
1. new
new es una palabra clave en C++ cuyo comportamiento siempre es consistente. Primero invoca a operator new para asignar memoria y luego llama al constructor para inicializar esa memoria.
Proceso de ejecución del operador new:
- Llama a operator new para asignar memoria;
- Llama al constructor para crear el objeto de clase en la dirección de memoria devuelta por operator new;
2. operator new
operator new es una función que, al igual que cualquier sobrecarga de operaodr como operator+, se utiliza para asignar memoria (aunque new hace más pasos además de llamarla). Puede ser sobrecargada, a través de su sobrecarga se puede modificar la funcionalidad del operador new. Su función puede compararse con malloc en C. Si una clase no sobrecarga operator new, se llama al ::operator new global para asignar memoria desde el heap.
3. placement new
placement new es una versión estándar y global de operator new en C++. No asigna memoria, simplemente devuelve un puntero a un bloque de memoria ya aisgnado. placement new permite construir un nuevo objeto en una memoria previamente asignada (en stack o heap).
Uso
1. Uso de new
Asignar un bloque de memoria en heap:
struct MiEstructura* ptr0 = new MiEstructura;
struct MiEstructura* ptr1 = new MiEstructura();
Observando el prototipo de new:
void* operator new(std::size_t) _GLIBCXX_THROW (std::bad_alloc)
__attribute__((__externally_visible__));
void* operator new[](std::size_t) _GLIBCXX_THROW (std::bad_alloc)
__attribute__((__externally_visible__));
void operator delete(void*) _GLIBCXX_USE_NOEXCEPT
__attribute__((__externally_visible__));
void operator delete[](void*) _GLIBCXX_USE_NOEXCEPT
__attribute__((__externally_visible__));
void* operator new(std::size_t, const std::nothrow_t&) _GLIBCXX_USE_NOEXCEPT
__attribute__((__externally_visible__));
void* operator new[](std::size_t, const std::nothrow_t&) _GLIBCXX_USE_NOEXCEPT
__attribute__((__externally_visible__));
void operator delete(void*, const std::nothrow_t&) _GLIBCXX_USE_NOEXCEPT
__attribute__((__externally_visible__));
void operator delete[](void*, const std::nothrow_t&) _GLIBCXX_USE_NOEXCEPT
__attribute__((__externally_visible__));
Se observa que tiene un parámetro size_t, que indica el tamaño de memoria asignada por placement new. Luego new llama al constructor en ese bloque de memoria, lo cual también está garantizado por C++.
2. Uso de operator new
Asignar 8 bytes de memoria. Dado que no está sobrecargado, aquí se llama al operator new global, asignando 8 bytes desde heap:
void* ptr = operator new (8);
Al sobrecargar operator new, se deben considerar los siguientes puntos:
- Al sobrecargar, el tipo de retorno debe declararse como void*
- Al sobrecargar, el primer parámetro debe indicar el tamaño del espacio solicitado (en bytes), tipo size_t
- Al sobrecargar, puede incluir otros parámetros
- La función de asignación debe ser miembro de clase o función global; si se declara la función de asignación en un namespace fuera del alcance global, o como estática en el alcance global, el programa es defectuoso
Por ejemplo, en el siguiente código, A sobrecarga operator new para imprimir una etiqueta y devuelve el operador new global, luego en la función principal se llama a la versión sobrecargada de A:
struct MiClase{
int valor;
char caracter;
void* operator new(size_t tamano, int etiqueta) throw(){
cout << etiqueta << endl;
return ::operator new(tamano);
}
};
int main()
{
void* ptr = MiClase::operator new (8,1);
cout << ptr << endl;
return 0;
}
El resultado final es que se asigna memoria y se imprime el valor de la etiqueta.
Si sobrecargamos la función global operator new y luego llamamos a new, la operación de new también cambiará, como en el siguiente ejemplo (este ejemplo tiene operator new con un solo parámetro):
struct MiClase{
int valor;
char caracter;
};
void* operator new(size_t numero) throw(){
cout << numero << endl;
return nullptr;
}
int main()
{
MiClase* ptr = new MiClase;
cout << ptr << endl;
return 0;
}
El resultado final muestra que aunque no se llama directamente a operator new, la operación de new ha sido modificada.
También hay que prestar atención a un detalle: los parámetros al llamar operator new difieren de los parámetros de new. Al llamar new, se ignora el primer parámetro size_t, pero cuando se llama directamente a operator new para asignar memoria, este parámetro es necesario.
Por lo tanto, si la definición de operator new en el segundo ejemplo de esta sección tuviera dos parámetros como en el primer ejemplo, la llamada a new debería ser así:
MiClase* ptr = new(1) MiClase ;
3. Uso de placement new
placement new es una versión implementada por C++ de operator new, su implementación es la siguiente:
// Versiones de placement predeterminadas de operator new.
inline void* operator new(std::size_t, void* __p) _GLIBCXX_USE_NOEXCEPT
{ return __p; }
inline void* operator new[](std::size_t, void* __p) _GLIBCXX_USE_NOEXCEPT
{ return __p; }
// Versiones de placement predeterminadas de operator delete.
inline void operator delete (void*, void*) _GLIBCXX_USE_NOEXCEPT { }
inline void operator delete[](void*, void*) _GLIBCXX_USE_NOEXCEPT { }
//@}
Como se puede ver, simplemente devuelve la dirección recibida. Según el segundo ejemplo de operator, después de sobrecargar el operator new global, la operación de la función new cambia. Por lo tanto, al llamar a new, se debe agregar una dirección como parámetro, y la función de placement new es construir en esa dirección.
Los pasos para usar placement new son los siguientes:
- Asignar memoria
char* buffer = new char[ sizeof(MiObjeto) * NUMERO ];
memset( buffer, 0, sizeof(MiObjeto)*NUMERO );
- Construir objetos
MiObjeto* pObj = new (buffer)MiObjeto;
- Usar objetos
pObj->imprimir();
pObj->set_valor(1.0f);
pObj->get_valor();
- Destruir objetos, llamar explícitamente al destructor de la clase.
pObj->~MiObjeto();
- Eliminar memoria
delete [] buffer;
Estos 5 pasos representan el método estándar de uso de placement new.