traducir significa qué palabras orden inglés ingles español escribe cómo como buscar alfabetico c++ embedded dynamic-memory-allocation pimpl-idiom

c++ - significa - traducir al español



Idioma Pimpl sin utilizar asignación de memoria dinámica (6)

Consulte The Fast Pimpl Idiom y The Joy of Pimpls sobre el uso de un asignador fijo junto con el lenguaje pimpl.

queremos usar el lenguaje pimpl para ciertas partes de nuestro proyecto. Estas partes del proyecto también son partes donde la asignación de memoria dinámica está prohibida y esta decisión no está bajo nuestro control.

Entonces, lo que estoy preguntando es: ¿existe una forma limpia y agradable de implementar el lenguaje pimpl sin asignación de memoria dinámica?

Editar
Aquí hay algunas otras limitaciones: Plataforma integrada, Estándar C ++ 98, sin bibliotecas externas, sin plantillas.


El punto de usar pimpl es ocultar la implementación de su objeto. Esto incluye el tamaño del verdadero objeto de implementación. Sin embargo, esto también hace que sea incómodo evitar la asignación dinámica: para reservar suficiente espacio de pila para el objeto, necesita saber qué tan grande es el objeto.

La solución típica es, de hecho, utilizar la asignación dinámica y pasar la responsabilidad de asignar espacio suficiente a la implementación (oculta). Sin embargo, esto no es posible en su caso, por lo que necesitaremos otra opción.

Una de estas opciones es usar alloca() . Esta función poco conocida asigna memoria en la pila; la memoria se liberará automáticamente cuando la función salga de su alcance. Esto no es C ++ portátil , sin embargo, muchas implementaciones de C ++ lo admiten (o una variación de esta idea).

Tenga en cuenta que debe asignar sus objetos pimpl''d utilizando una macro; alloca() debe invocarse para obtener la memoria necesaria directamente de la función propietaria. Ejemplo:

// Foo.h class Foo { void *pImpl; public: void bar(); static const size_t implsz_; Foo(void *); ~Foo(); }; #define DECLARE_FOO(name) / Foo name(alloca(Foo::implsz_)); // Foo.cpp class FooImpl { void bar() { std::cout << "Bar!/n"; } }; Foo::Foo(void *pImpl) { this->pImpl = pImpl; new(this->pImpl) FooImpl; } Foo::~Foo() { ((FooImpl*)pImpl)->~FooImpl(); } void Foo::Bar() { ((FooImpl*)pImpl)->Bar(); } // Baz.cpp void callFoo() { DECLARE_FOO(x); x.bar(); }

Esto, como puede ver, hace que la sintaxis sea algo incómoda, pero logra un análogo de pimpl.

Si puede codificar el tamaño del objeto en el encabezado, también existe la opción de usar una matriz de caracteres:

class Foo { private: enum { IMPL_SIZE = 123; }; union { char implbuf[IMPL_SIZE]; double aligndummy; // make this the type with strictest alignment on your platform } impl; // ... }

Esto es menos puro que el enfoque anterior, ya que debe cambiar los encabezados siempre que cambie el tamaño de la implementación. Sin embargo, le permite utilizar la sintaxis normal para la inicialización.

También puede implementar una pila sombra, es decir, una pila secundaria separada de la pila normal de C ++, específicamente para contener objetos con pImpl. Esto requiere una administración muy cuidadosa, pero, correctamente envuelto, debería funcionar. Este tipo de está en la zona gris entre la asignación dinámica y estática.

// One instance per thread; TLS is left as an exercise for the reader class ShadowStack { char stack[4096]; ssize_t ptr; public: ShadowStack() { ptr = sizeof(stack); } ~ShadowStack() { assert(ptr == sizeof(stack)); } void *alloc(size_t sz) { if (sz % 8) // replace 8 with max alignment for your platform sz += 8 - (sz % 8); if (ptr < sz) return NULL; ptr -= sz; return &stack[ptr]; } void free(void *p, size_t sz) { assert(p == stack[ptr]); ptr += sz; assert(ptr < sizeof(stack)); } }; ShadowStack theStack; Foo::Foo(ShadowStack *ss = NULL) { this->ss = ss; if (ss) pImpl = ss->alloc(sizeof(FooImpl)); else pImpl = new FooImpl(); } Foo::~Foo() { if (ss) ss->free(pImpl, sizeof(FooImpl)); else delete ss; } void callFoo() { Foo x(&theStack); x.Foo(); }

Con este enfoque, es fundamental asegurarse de que NO use la pila de sombra para los objetos en los que el objeto envoltorio está en el montón; esto violaría la suposición de que los objetos siempre se destruyen en orden inverso a la creación.


Si puede usar boost, considere boost::optional<> . Esto evita el costo de la asignación dinámica, pero al mismo tiempo, su objeto no se construirá hasta que lo considere necesario.


Una forma sería tener una matriz char [] en su clase. Hágalo lo suficientemente grande para que su Impl se ajuste, y en su constructor, cree una instancia de su Impl en su lugar, con una nueva ubicación: new (&array[0]) Impl(...) .

También debe asegurarse de no tener ningún problema de alineación, probablemente al hacer que su char [] sea miembro de una unión. Esta:

union { char array[xxx]; int i; double d; char *p; };

por ejemplo, se asegurará de que la alineación de la array[0] sea ​​adecuada para un int, doble o un puntero.


pimpl se basa en los punteros y puede establecerlos en cualquier lugar donde se asignen sus objetos. Esta también puede ser una tabla estática de objetos declarados en el archivo cpp. El punto principal de pimpl es mantener las interfaces estables y ocultar la implementación (y sus tipos utilizados).


Advertencia: el código aquí solo muestra el aspecto de almacenamiento, es un esqueleto, no se ha tenido en cuenta ningún aspecto dinámico (construcción, copia, movimiento, destrucción).

Yo sugeriría un enfoque utilizando la nueva clase c ++ 0x aligned_storage , que está precisamente diseñada para tener almacenamiento en bruto.

// header class Foo { public: private: struct Impl; Impl& impl() { return reinterpret_cast<Impl&>(_storage); } Impl const& impl() const { return reinterpret_cast<Impl const&>(_storage); } static const size_t StorageSize = XXX; static const size_t StorageAlign = YYY; std::aligned_storage<StorageSize, StorageAlign>::type _storage; };

En la fuente, entonces implementas un cheque:

struct Foo::Impl { ... }; Foo::Foo() { // 10% tolerance margin static_assert(sizeof(Impl) <= StorageSize && StorageSize <= sizeof(Impl) * 1.1, "Foo::StorageSize need be changed"); static_assert(StorageAlign == alignof(Impl), "Foo::StorageAlign need be changed"); /// anything }

De esta manera, aunque tendrá que cambiar la alineación de inmediato (si es necesario), el tamaño solo cambiará si el objeto cambia demasiado.

Y, obviamente, ya que el cheque está en el momento de la compilación, simplemente no puedes perderlo :)

Si no tiene acceso a las funciones de C ++ 0x, hay equivalentes en el espacio de nombres TR1 para aligned_storage y alignof y hay implementaciones de macros de static_assert .