Polimorfismo sin nuevo

Polimorfismo sin nuevo

Una forma sencilla de evitar la asignación dinámica es usar asignación estática , que es lo más opuesto posible a la asignación dinámica. Sin embargo, debe hacerse con cuidado, porque incluso con un programa sin subprocesos, uno puede entrar inadvertidamente en una situación en la que dos o más partes del código piensen que son "dueños" de algún objeto asignado estáticamente. Peor aún, tales esencialmente variables globales (incluso cuando se disfrazan de singletons, o en el código a continuación como estáticas locales) sirven esencialmente como centros centrales para la comunicación de espagueti , donde la información que induce al caos se propaga libremente entre lugares que nunca podrías imaginar, totalmente fuera de tu control.

Entonces, el esquema de asignación estática tiene algunos inconvenientes... :-)

Pero empecemos por ahí:

// Using static allocation.

#include <iostream>
using namespace std;

struct A { virtual void g() = 0; };

struct B : A { virtual void g() override { wcout << "A\n"; } };
struct C : A { virtual void g() override { wcout << "B\n"; } };

A& f( bool const x )
{
    static B    theB;
    static C    theC;

    if( x ) { theB = B(); return theB; } else { theC = C(); return theC; }
}

bool get_boolean() { return false; }

int main()
{
    bool const b = get_boolean();
    A& x = f( b ); 
    x.g();
}

Para evitar el inconveniente de propiedad errónea del esquema de asignación estática, puede proporcionar el almacenamiento en la pila mediante la asignación automática de C++. (La asignación automática de C++ es una pila por definición, un esquema de asignación LIFO). Pero esto significa pasar el almacenamiento a la función. La función puede devolver una referencia al objeto relevante:

// Using automatic storage (the stack)

#include <iostream>
using namespace std;

struct A { virtual void g() = 0; };

struct B : A { virtual void g() override { wcout << "A\n"; } };
struct C : A { virtual void g() override { wcout << "B\n"; } };

A& f( bool const x, B& b, C& c )
{
    if( x ) { b = B(); return b; } else { c = C(); return c; }
}

bool get_boolean() { return false; }

int main()
{
    bool const b = get_boolean();
    B   objBStorage;
    C   objCStorage;
    A&  x   = f( b, objBStorage, objCStorage ); 
    x.g();
}

Pero incluso cuando elegimos ignorar problemas como la construcción con efectos secundarios, y así sucesivamente, es decir, cuando asumimos alegremente que las clases B y C están diseñados para funcionar bien con dicho esquema, el anterior desperdicios de almacenamiento . Si B y C las instancias son grandes, por lo tanto, se puede considerar el uso de las funciones de C++ para construir objetos en almacenamiento preexistente, lo que se conoce como ubicación nueva . Debido a problemas de alineación de la memoria, es un poco difícil hacerlo correctamente en C++ 03, pero C++ 11 ofrece un mejor soporte, de la siguiente manera:

#include <iostream>
#include <memory>           // unique_ptr
#include <new>              // new
#include <type_traits>      // aligned_storage
using namespace std;

typedef unsigned char Byte;

struct A { virtual void g() = 0; };

struct B : A { virtual void g() override { wcout << "A\n"; } };
struct C : A { virtual void g() override { wcout << "B\n"; } };

A* f( bool const x, void* storage )
{
    return (x? static_cast<A*>( ::new( storage ) B() ) : ::new( storage ) C());
}

bool get_boolean() { return false; }

void destroyA( A* p ) { p->~A(); }

int main()
{
    enum{ enoughBytes = 
        (sizeof( B ) > sizeof( C ))? sizeof( B ) : sizeof( C ) };
    typedef aligned_storage< enoughBytes >::type StorageForBOrC;

    bool const b = get_boolean();
    StorageForBOrC storage;
    A* const pX = f( b, &storage );
    unique_ptr<A, void(*)(A*)> const cleanup( pX, destroyA );
    pX->g();
}

Ahora, ¿cuál de los anteriores elegiría?

¿Elegiría la asignación estática severamente restringida pero simple e instantánea, o elegiría la asignación automática que desperdicia memoria, o quizás... la construcción de objetos in situ optimizada pero algo compleja?

La respuesta es, ¡no elegiría ninguno de ellos!

En lugar de centrarme en la microeficiencia, me centraría en la claridad y corrección , y por lo tanto simplemente recibir el impacto en el rendimiento de una asignación dinámica. Para mayor corrección, usaría un puntero inteligente para el resultado de la función. Si esto realmente estuviera ralentizando las cosas, tal vez consideraría usar un asignador de objetos pequeños dedicado. .

En conclusión, ¡no te preocupes por las cosas pequeñas! :-)


En función f objetos B() o C() ambos son temporales, por lo que solo puede devolverlos desde f por valor.

Quizás boost::variant es para ti. Entonces ni siquiera necesita tener el método virtual o derivar de una clase base común.