c++ - operador nueva sobrecarga y alineación
operator-overloading new-operator (3)
Esta es una posible solución. Siempre elegirá el operador con la alineación más alta en una jerarquía dada:
#include <exception>
#include <iostream>
#include <cstdlib>
// provides operators for any alignment >= 4 bytes
template<int Alignment>
struct DeAllocator;
template<int Alignment>
struct DeAllocator : virtual DeAllocator<Alignment/2> {
void *operator new(size_t s) throw (std::bad_alloc) {
std::cerr << "alignment: " << Alignment << "/n";
return ::operator new(s);
}
void operator delete(void *p) {
::operator delete(p);
}
};
template<>
struct DeAllocator<2> { };
// ........... Test .............
// different classes needing different alignments
struct Align8 : virtual DeAllocator<8> { };
struct Align16 : Align8, virtual DeAllocator<16> { };
struct DontCare : Align16, virtual DeAllocator<4> { };
int main() {
delete new Align8; // alignment: 8
delete new Align16; // alignment: 16
delete new DontCare; // alignment: 16
}
Se basa en la regla de dominancia: si hay una ambigüedad en la búsqueda, y la ambigüedad es entre los nombres de una clase base derivada y una virtual, en su lugar se toma el nombre de la clase derivada.
DeAllocator<I>
preguntas sobre por qué DeAllocator<I>
hereda DeAllocator<I / 2>
. La respuesta es porque en una jerarquía dada, puede haber diferentes requisitos de alineación impuestos por las clases. Imagine que IBase
no tiene requisitos de alineación, A
tiene un requisito de 8 bytes y B
tiene un requisito de 16 bytes y hereda A
:
class IBAse { };
class A : IBase, Alignment<8> { };
class B : A, Alignment<16> { };
Alignment<16>
y Alignment<8>
exponen a un operator new
. Si ahora dice new B
, el compilador buscará operator new
en B
y encontrará dos funciones:
// op new
Alignment<8> IBase
^ /
/ /
/ /
// op new / /
Alignment<16> A
/ /
/ /
/ /
B
B -> Alignment<16> -> operator new
B -> A -> Alignment<8> -> operator new
Por lo tanto, esto sería ambiguo y no compilaríamos: ninguno de estos oculta al otro. Pero si ahora hereda la Alignment<16>
virtualmente de la Alignment<8>
y hace que A
y B
hereden virtualmente, el operator new
en Alignment<8>
estará oculto:
// op new
Alignment<8> IBase
^ /
/ / /
/ / /
// op new / / /
Alignment<16> A
/ /
/ /
/ /
B
Esta regla de ocultación especial (también llamada regla de dominación ) sin embargo solo funciona si todos los objetos de Alignment<8>
son los mismos. Por lo tanto, siempre heredamos virtualmente: en ese caso, solo existe un objeto Alignment<8>
(o 16, ...) existente en cualquier jerarquía de clases dada.
Estoy sobrecargando el operator new
, pero recientemente he encontrado un problema con la alineación. Básicamente, tengo una clase IBase
que proporciona operator new
y delete
en todas las variantes requeridas. Todas las clases se derivan de IBase
y, por lo tanto, también usan los asignadores personalizados.
El problema al que me enfrento ahora es que tengo un Foo
infantil que debe estar alineado con 16 bytes, mientras que todos los demás están bien cuando están alineados con 8 bytes. Sin embargo, mi asignador de memoria se alinea con los límites de 8 bytes por defecto, por lo que ahora el código en IBase::operator new
devuelve una pieza de memoria inutilizable. ¿Cómo se supone que esto se resuelva correctamente?
Simplemente puedo forzar todas las asignaciones a 16 bytes, lo que funcionará bien hasta que aparezca un tipo alineado de 32 bytes. Averiguar la alineación dentro del operator new
no parece ser trivial (¿puedo hacer una llamada de función virtual allí para obtener la alineación real?) ¿Cuál es la forma recomendada de manejar esto?
Sé que se supone que malloc
devuelve un trozo de memoria que está adecuadamente alineado para todo, desafortunadamente, este "todo" no incluye los tipos SSE y realmente me gustaría hacerlo funcionar sin requerir que el usuario recuerde qué tipo tiene qué alineación.
Usando Visual Studio Express 2010, el ejemplo anterior no parece funcionar con nuevo:
CacheAlign *c = new CacheAlign;
dará __align_of (c) == 4 (se espera que supongo), pero la dirección de un primer miembro de CacheAlign no está alineada como se solicita.
No es una pregunta reciente, pero si entiendo el OP correctamente, él tiene clases que son hijos de una clase para padres que definen el asignador y el desasignador, y todos pueden requerir una alineación específica. ¿Qué hay de malo en tener un nuevo simple que llama a un asignador privado que hace el trabajo real y recibe un argumento de alineación, una versión genérica con alineación predeterminada en la clase padre heredada o sobrecargada con una versión que especifica la alineación correcta?
mixins es el enfoque correcto, sin embargo, la sobrecarga del operador nuevo no lo es. Esto logrará lo que necesita:
__declspec(align(256)) struct cachealign{};
__declspec(align(4096)) struct pagealign{};
struct DefaultAlign{};
struct CacheAlign:private cachealign{};
struct PageAlign: CacheAlign,private pagealign{};
void foo(){
DefaultAlign d;
CacheAlign c;
PageAlign p;
std::cout<<"Alignment of d "<<__alignof(d)<<std::endl;
std::cout<<"Alignment of c "<<__alignof(c)<<std::endl;
std::cout<<"Alignment of p "<<__alignof(p)<<std::endl;
}
Huellas dactilares
Alignment of d 1
Alignment of c 256
Alignment of p 4096
Para gcc, use
struct cachealign{}__attribute__ ((aligned (256)));
Tenga en cuenta que hay una selección automática de la alineación más grande, y esto funciona para los objetos colocados en la pila, los que se renuevan y los miembros de otras clases. Tampoco agrega ningún virtual y asumiendo EBCO, no hay un tamaño extra para la clase (fuera del relleno necesario para la alineación).