Pregunta

Estoy tratando de crear un tipo inmutable (clase) en C ++,

Lo hice para que todos los métodos "aka funciones miembro" no modifique el objeto y devuelva una nueva instancia en su lugar.

Me encuentro con un montón de problemas, pero todos giran en torno a los tipos de referencia en C ++.

Un ejemplo es cuando se pasan parámetros del mismo tipo de clase por referencia:

Imm Imm::someOp( const Imm& p_im ) const
{
   ...
   p_im = p_im.someOtherOp(); //error, p_im is const, can't modify it!
   ...
}

El error se produce al pasar el valor por referencia. Si, en cambio, estaba pasando la referencia por valor, ¡entonces la línea de error anterior no sería un error!

Considere un ejemplo de Java / C #

class Imm
{
    ...
    Imm someOp( Imm p_im )
    {
        ....
        p_im = p_im.someOtherOp(); //ok, you're not modifying the 
                 //original object, just changing the local reference
        ....  
    }
    ....
}

¿Cómo puedo hacer algo así en C ++? Sé que puedo usar punteros pero luego me encuentro con todo el desorden de administración de memoria. No quiero preocuparme de quién posee referencias a objetos.

Idealmente, me gustaría diseñar la clase para que sea como cadenas inmutables en python; puede usarlos sin darse cuenta o incluso sin saber que son inmutables, y simplemente se comportan como espera; simplemente funcionan.

EDIT

Por supuesto, puedo evitarlo pasando por valor o usando una variable temporal (que es lo que estoy haciendo actualmente). Lo que estoy preguntando es "cómo pasar referencias por valor en C ++"

Espero que la respuesta gire en torno a algo en el STL, actualmente estoy buscando en la familia de plantillas smart_ptr.

UPDATE

Gracias por las respuestas, me doy cuenta de que no hay escapatoria de punteros. (vea mi otra pregunta , que es realmente un seguimiento de esta)

¿Fue útil?

Solución

En Java y C #, en realidad no se trata de una referencia, son más como identificadores o punteros. Una referencia en C ++ es realmente otro nombre para el objeto original, no un puntero al mismo (aunque puede implementarse con un puntero). Cuando asigna un valor a una referencia, está asignando al objeto mismo. Hay confusión en que para inicializar una referencia puede usar el carácter = , pero es una inicialización, no una asignación.

 Imm im, im2, im3; 
 Imm &imr = im;  // initialize a reference to im
 imr = im2; // assign im2 to imr (changes the value of im as well)
 Imm *imp = &im; // initialize a pointer to the address of im
 imp = &im3; // assign the address of im3 to imp (im is unnaffected);
 (*imp) = im2; // assign im2 to imp (modifies im3 as well).

Si específicamente desea pasar " referencias por valor " entonces esencialmente estás pidiendo una contradición en términos. Las referencias, por definición, se pasan por referencia. Como se señaló en otra parte, puede pasar un puntero por valor, o bien un valor directo. Si realmente quiere, puede mantener una referencia en una clase y pasarla por valor:

 struct ImmRef
 {
     Imm &Ref;
     ImmRef(Imm &ref) : Ref(ref) {}
 };

Tenga en cuenta también que una constante aplicada a una referencia está haciendo constante el objeto al que se hace referencia, no la referencia. Las referencias son siempre constantes.

Otros consejos

¿La asignación, por definición, no es una operación constante?

Parece que está tratando de asignar algo a una referencia constante, lo que anula totalmente la idea de una referencia constante.

Creo que puede estar buscando un puntero en lugar de una referencia.

No funciona así en C ++.

Cuando pasa una referencia a un objeto, en realidad está pasando la dirección en la memoria del objeto. Las referencias tampoco se pueden recolocar a otros objetos, de ahí que el adagio C ++ "la referencia ES el objeto". Tiene que hacer una copia para modificarlo. Java lo hará detrás de escena por usted. C ++, solo tienes que copiarlo.

¿No olvidó establecer el método que llama como constante?

EDITAR: entonces, con el const arreglado.

Quizás deberías hacer algo como

Imm & tmp = p_im.someOtherOp();

Luego realice más operaciones en la variable tmp.

Si establece una variable o un parámetro como const & amp; simplemente no puede asignarlo.

Marque esto para saber sobre la vida útil temporal http://herbsutter.wordpress.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/

Imm Imm::someOp( const Imm& p_im ) const
{
   ...
   //Imm& im = p_im.someOtherOp();       // will *not* work
   const Imm& im = p_im.someOtherOp();   // will work, but you get a const reference
   ...
}

Pero puedes usar boost :: shared_ptr

shared_ptr<Imm> Imm::someOtherOp() const
{
  shared_ptr<Imm> ret = new Imm;
  ...
  return ret;
}

shared_ptr<Imm> Imm::someOp(const share_ptr<Imm>& p_im) const
{
  shared_ptr<Imm> im = p_im->someOtherOp();
}

Necesita hacer una nueva copia de su argumento entrante. Puede hacer lo que quiera de varias maneras equivalentes: 1) puede pasar por valor:

Imm Imm::someOp( Imm im ) const {
   im = im.someOtherOp();      // local im is a copy, original im not modified
   return im;                  // return by value (another copy)
}

o 2) puede pasar por referencia y hacer una copia explícitamente:

Imm Imm::someOp( const Imm & im ) const {
   Imm tmp = im.someOtherOp(); // local tmp is a copy
   return tmp;                 // return by value (another copy)
}

ambas formas son equivalentes.

C ++ tiene algo mejor que los tipos inmutables & # 8212; const . Un solo tipo puede ser mutable o no dependiendo de sus necesidades. Dicho esto, hay patrones útiles para lidiar con copias de corta duración (casi):

void f(const X &x) {
  // Trivial case: unconditional copy
  X x2=transform(x);
  // Less trivial: conditional copy
  std::optional<X> maybe;
  const X &use=need_copy ? maybe.emplace(transform(x)) : x;
  use.go();  // whichever, x or *maybe
}  // *maybe destroyed iff created, then x2 destroyed

std :: unique_ptr puede usarse de manera similar antes de C ++ 17, aunque la función puede, por supuesto, arrojar std :: bad_alloc .

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