Pregunta

¿Qué es un buen patrón de clase existente / diseño para la construcción de varias etapas / inicialización de un objeto en C ++?

tengo una clase con algunos miembros de datos que deben ser inicializados en diferentes puntos en el flujo del programa, por lo que su inicialización tiene que ser retrasada. Por ejemplo, un argumento puede ser leída desde un archivo y otro de la red.

Actualmente estoy usando impulso :: opcional para la construcción retardado de los miembros de datos, pero me molesta que es opcional semánticamente diferente de retardo-construida.

Lo que necesito recuerda características de impulso :: bind y la aplicación parcial de la función lambda, y el uso de estas bibliotecas probable que pueda diseñar la construcción de varias etapas - pero yo prefiero usar, clases probadas existentes. (O tal vez hay otro modelo de la construcción de varias etapas que no estoy familiarizado).

¿Fue útil?

Solución

La cuestión clave es si debe o no distinguir objetos completamente pobladas de objetos de forma incompleta pobladas en el nivel de tipo . Si decide no hacer una distinción, a continuación, sólo tiene que utilizar boost::optional o similar a la que está haciendo: esto hace que sea fácil de obtener la codificación rápida. Otoh no se puede obtener el compilador para hacer cumplir el requisito de que una función particular requiere un objeto completamente poblada; es necesario realizar la comprobación en tiempo de ejecución de los campos cada vez.

tipos de parámetros de grupo

Si distinguir objetos completamente pobladas de objetos de forma incompleta pobladas en el nivel de tipo, puede cumplir el requisito de que una función puede pasar un objeto completo. Para ello me permito sugerir la creación de un XParams tipo correspondiente para cada tipo X relevante. XParams tiene miembros boost::optional y funciones setter para cada parámetro que se puede ajustar después de la construcción inicial. A continuación, puede forzar X tener sólo un constructor (no copia), que toma un XParams como su único argumento y comprueba que cada parámetro necesario ha sido ajustada en el interior de ese objeto XParams. (No estoy seguro si este patrón tiene un nombre - nadie como a editar esto a nosotros rellene)

Tipos "objeto parcial"

Esto funciona de maravilla si usted realmente no tiene que lo cualquier cosa con el objeto antes de que esté completamente poblada (quizá aparte de cosas triviales como obtener los valores de campo de la espalda). Si usted tiene que tratar a veces una X incompletamente poblada como un X "completa", en su lugar puede hacer que derivan X de un XPartial tipo, que contiene toda la lógica, más protected métodos virtuales para realizar pruebas de condición previa de que la prueba si todos los campos necesarios están poblado. Entonces, si asegura X que sólo alguna vez se puede construir en un estado completamente poblada, se puede anular esos métodos protegidas con cheques triviales que siempre devuelven true:

class XPartial {
    optional<string> name_;

public:
    void setName(string x) { name_.reset(x); }  // Can add getters and/or ctors
    string makeGreeting(string title) {
        if (checkMakeGreeting_()) {             // Is it safe?
            return string("Hello, ") + title + " " + *name_;
        } else {
            throw domain_error("ZOINKS");       // Or similar
        }
    }
    bool isComplete() const { return checkMakeGreeting_(); }  // All tests here

protected:
    virtual bool checkMakeGreeting_() const { return name_; }   // Populated?
};

class X : public XPartial {
    X();     // Forbid default-construction; or, you could supply a "full" ctor

public:
    explicit X(XPartial const& x) : XPartial(x) {  // Avoid implicit conversion
        if (!x.isComplete()) throw domain_error("ZOINKS");
    }

    X& operator=(XPartial const& x) {
        if (!x.isComplete()) throw domain_error("ZOINKS");
        return static_cast<X&>(XPartial::operator=(x));
    }

protected:
    virtual bool checkMakeGreeting_() { return true; }   // No checking needed!
};

A pesar de que pueda parecer la herencia aquí es "al revés", haciendo de esta manera que un medio X con seguridad se puede suministrar en cualquier lugar se le preguntó a un XPartial& para, por lo que este enfoque obedece a la principio de sustitución de liskov . Esto significa que una función se puede utilizar un tipo de parámetro de X& para indicar que necesita un objeto X completa, o XPartial& para indicar que puede manejar objetos parcialmente pobladas -., En cuyo caso se pueden pasar ya sea un objeto XPartial o una X completo

Al principio yo tenía isComplete() como protected, pero encontró esto no funcionó, ya Héctor copia de X y operador de asignación deben llamar a esta función en su argumento XPartial&, y ellos no tienen acceso suficiente. En la reflexión, que tiene más sentido para exponer públicamente esta funcionalidad.

Otros consejos

Debo estar perdiendo algo aquí - que hago este tipo de cosas todo el tiempo. Es muy común tener objetos que son grandes y / o no necesarios a la clase en todas las circunstancias. Por lo tanto crear dinámicamente!

struct Big {
    char a[1000000];
};

class A {
  public: 
    A() : big(0) {}
   ~A() { delete big; }

   void f() {
      makebig();
      big->a[42] = 66;
   }
  private:
    Big * big;
    void makebig() {
      if ( ! big ) {
         big = new Big;
      }
    }
};

No veo la necesidad de más de lujo nada de eso, excepto que makebig () debe ser probablemente const (y tal vez en línea), y el puntero grande, probablemente debería ser mutable. Y, por supuesto, A debe ser capaz de construir grandes, lo que puede significar que en otros casos el almacenamiento en caché parámetros del constructor de la clase contenida. También tendrá que decidir sobre una política de copia / asignación -. Probablemente me había prohibido para este tipo de clase

No tenemos información disponible sobre los patrones para hacer frente a este problema específico. Es una cuestión de diseño complicado, y uno algo único a lenguajes como C ++. Otra cuestión es que la respuesta a esta pregunta está estrechamente ligada a su estilo individual (o corporativa) de codificación.

Yo usaría punteros para estos miembros, y cuando tienen que ser construidos, les asignarán al mismo tiempo. Puede utilizar auto_ptr para estos, y comprobar contra NULL para ver si se inicializan. (Pienso en los punteros son un tipo incorporado "opcional" en C / C ++ / Java, hay otros idiomas, donde NULL no es un puntero válido).

Una cuestión como una cuestión de estilo es que se le puede confiar en su constructores de hacer demasiado trabajo. Cuando estoy OO de codificación, tengo los constructores hacen lo suficiente trabajo para conseguir el objeto en un estado coherente. Por ejemplo, si tengo una clase Image y yo quiero leer desde un archivo, que podía hacer esto:

image = new Image("unicorn.jpeg"); /* I'm not fond of this style */

o, lo que podía hacer esto:

image = new Image(); /* I like this better */
image->read("unicorn.jpeg");

Se puede conseguir difícil razonar acerca de cómo un C ++ funciona el programa si los constructores tienen una gran cantidad de código en ellos, sobre todo si se hace la pregunta: "¿Qué ocurre si falla un constructor?" Este es el principal beneficio del movimiento de código de los constructores.

Me tendría más que decir, pero no sé lo que estás tratando de hacer con la construcción retardada.

Edit: Me acordé de que hay un camino (tanto perversa) para llamar a un constructor en un objeto en cualquier momento arbitrario. He aquí un ejemplo:

class Counter {
public:
    Counter(int &cref) : c(cref) { }
    void incr(int x) { c += x; }
private:
    int &c;
};

void dontTryThisAtHome() {
    int i = 0, j = 0;
    Counter c(i);       // Call constructor first time on c
    c.incr(5);          // now i = 5
    new(&c) Counter(j); // Call the constructor AGAIN on c
    c.incr(3);          // now j = 3
}

Tenga en cuenta que hacer algo tan imprudente como esto podría ganarse el desprecio de sus compañeros programadores, a menos que tenga razones sólidas para el uso de esta técnica. Esto también no retrasa el constructor, simplemente le permite llamar de nuevo más tarde.

El uso de miradas boost.optional como una buena solución para algunos casos de uso. No he jugado mucho con ella, así que no puedo comentar mucho. Una cosa que tener en cuenta cuando se trata de tal funcionalidad es si puedo utilizar constructores sobrecargados por defecto en lugar de copiar y constructores.

Cuando necesito tal funcionalidad que acaba de utilizar un puntero al tipo del campo necesario de esta manera:

public:
  MyClass() : field_(0) { } // constructor, additional initializers and code omitted
  ~MyClass() {
    if (field_)
      delete field_; // free the constructed object only if initialized
  }
  ...
private:
  ...
  field_type* field_;

A continuación, en lugar de utilizar el puntero Me gustaría tener acceso al campo a través del siguiente método:

private:
  ...
  field_type& field() {
    if (!field_)
      field_ = new field_type(...);
    return field_;
  }

He omitido la semántica de acceso const

La forma más sencilla que conozco es similar a la técnica sugerida por Dietrich Epp, excepto que le permite retardar realmente la construcción de un objeto hasta un momento de su elección.

Básicamente:. Reserva el objeto con malloc vez de nuevo (evitando así el constructor), a continuación, llamar al nuevo operador sobrecargado cuando realmente quiere construir el objeto a través de la colocación de nuevo

Ejemplo:

Object *x = (Object *) malloc(sizeof(Object));
//Use the object member items here. Be careful: no constructors have been called!
//This means you can assign values to ints, structs, etc... but nested objects can wreak havoc!

//Now we want to call the constructor of the object
new(x) Object(params);

//However, you must remember to also manually call the destructor!
x.~Object();
free(x);

//Note: if you're the malloc and new calls in your development stack 
//store in the same heap, you can just call delete(x) instead of the 
//destructor followed by free, but the above is the  correct way of 
//doing it

En lo personal, la única vez que he utilizado esta sintaxis fue cuando tuve que usar un factor de imputación basada en C personalizada para objetos C ++. Como sugiere Dietrich, se debe cuestionar si realmente, realmente debe retrasar la llamada al constructor. El Base constructor debe realizar el mínimo para conseguir su objeto en un estado útil, mientras que otros constructores sobrecargados pueden realizar más trabajo, según sea necesario.

No sé si hay un patrón formal para esto. En los lugares en los que he visto, lo llamamos "perezosa", "demanda" o "a la carta".

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top