c++ - una - ¿Por qué el operador=trabaja en estructuras sin haber sido definido?
operador-> c++ (7)
Veamos un ejemplo simple:
struct some_struct {
std::string str;
int a, b, c;
}
some_struct abc, abc_copy;
abc.str = "some text";
abc.a = 1;
abc.b = 2;
abc.c = 3;
abc_copy = abc;
Entonces abc_copy es una copia exacta de abc
... ¿cómo es posible sin definir el operador = ?
(Esto me tomó por sorpresa cuando trabajaba en algún código ...)
El operador de asignación ( operator=
) es una de las funciones generadas implícitamente para una estructura o clase en C ++.
Aquí hay una referencia que describe los 4 miembros generados implícitamente:
http://www.cs.ucf.edu/~leavens/larchc++manual/lcpp_136.html
En resumen, el miembro generado implícitamente realiza una copia poco profunda para miembros . Aquí está la versión larga de la página vinculada:
La especificación del operador de asignación generado implícitamente, cuando sea necesario, es la siguiente. La especificación dice que el resultado es el objeto que se le asigna (
self
) y que el valor del valor abstracto deself
en elself
post-estado "es el mismo que el valor del valor abstracto del argumentofrom
.
// @(#)$Id: default_assignment_op.lh,v 1.3 1998/08/27 22:42:13 leavens Exp $
#include "default_interfaces.lh"
T& T::operator = (const T& from) throw();
//@ behavior {
//@ requires assigned(from, any) // assigned(from/any, any);
//@ modifies self;
//@ ensures result = self // self" = from/any/any;
//@ ensures redundantly assigned(self, post) // assigned(self'', post);
// thus
//@ ensures redundantly assigned(result, post) // assigned(result'', post);
//@ }
El compilador sintetizará algunos miembros por ti si no los defines explícitamente. El operador de asignación es uno de ellos. Un constructor de copia es otro, y usted también obtiene un destructor. También obtendrá un constructor predeterminado si no proporciona ningún constructor propio. Más allá de eso, no estoy seguro de qué más, pero creo que puede haber otros (el enlace en la respuesta dada por 280Z28 sugiere lo contrario y no recuerdo dónde lo leí ahora, así que tal vez sean solo cuatro).
En C ++, las estructuras son equivalentes a las clases donde los miembros tienen acceso público de forma predeterminada.
Los compiladores de C ++ también generarán los siguientes miembros especiales de una clase automáticamente si no se proporcionan:
- Constructor por defecto : sin argumentos, por defecto se inicia todo.
- Copiar constructor - es decir, un método con el mismo nombre que la clase, que toma una referencia a otro objeto de la misma clase. Copia todos los valores.
- Destructor : se invoca cuando se destruye el objeto. Por defecto no hace nada.
- Operador de asignación : se llama cuando una estructura / clase se asigna a otra. Este es el método generado automáticamente que se llama en el caso anterior.
Ese comportamiento es necesario para mantener la compatibilidad de la fuente con C.
C no le da la capacidad de definir / anular operadores, por lo que las estructuras normalmente se copian con el operador =.
Pero está definido. En el estándar. Si no proporciona operador =, se le proporciona uno. Y el operador predeterminado simplemente copia cada una de las variables miembro. ¿Y cómo sabe qué forma de copiar a cada miembro? llama a su operador = (que, si no está definido, se suministra por defecto ...).
Si no define estos cuatro métodos (seis en C ++ 11), el compilador los generará por usted:
- Constructor predeterminado
- Copiar Constructor
- Operador de Asignación
- Incinerador de basuras
- Move Constructor (C ++ 11)
- Asignación de movimiento (C ++ 11)
Si quieres saber por qué?
Es para mantener la compatibilidad con C (porque las estructuras C se pueden copiar utilizando = y en la declaración). Pero también hace que escribir clases simples sea más fácil. Algunos argumentarían que agrega problemas debido al "problema de copia superficial". Mi argumento en contra de eso es que no deberías tener una clase con punteros RAW en ella. Al usar los indicadores inteligentes apropiados, ese problema desaparece.
Constructor predeterminado (si no se definen otros constructores)
El constructor predeterminado generado por el compilador llamará al constructor predeterminado de las clases base y luego al constructor predeterminado de cada miembro (en el orden en que se declaran)
Destructor (si no se definió un destructor)
Llama al destructor de cada miembro en el orden inverso de la declaración. Luego llama al destructor de la clase base.
Copy Constructor (si no se define un constructor de copia)
Llama al constructor de copia de la clase base que pasa el objeto src. Luego llama al constructor de copia de cada miembro que usa los miembros de src objects como el valor que se va a copiar.
Operador de Asignación
Llama al operador de asignación de clase base que pasa el objeto src. Luego llama al operador de asignación en cada miembro que usa el objeto src como el valor que se va a copiar.
Move Constructor (si no se define ningún constructor de movimiento)
Llama al constructor de movimientos de la clase base que pasa el objeto src. Luego llama al constructor de movimientos de cada miembro que usa los miembros de los objetos src como el valor que se moverá.
Mover operador de asignación
Llama al operador de asignación de movimiento de la clase base que pasa el objeto src. Luego llama al operador de asignación de movimiento en cada miembro utilizando el objeto src como el valor que se va a copiar.
Si defines una clase como esta:
struct some_struct: public some_base
{
std::string str1;
int a;
float b;
char* c;
std::string str2;
};
Lo que el compilador construirá es:
struct some_struct: public some_base
{
std::string str1;
int a;
float b;
char* c;
std::string str2;
// Conceptually two different versions of the default constructor are built
// One is for value-initialization the other for zero-initialization
// The one used depends on how the object is declared.
// some_struct* a = new some_struct; // value-initialized
// some_struct* b = new some_struct(); // zero-initialized
// some_struct c; // value-initialized
// some_struct d = some_struct(); // zero-initialized
// Note: Just because there are conceptually two constructors does not mean
// there are actually two built.
// value-initialize version
some_struct()
: some_base() // value-initialize base (if compiler generated)
, str1() // has a normal constructor so just call it
// PODS not initialized
, str2()
{}
// zero-initialize version
some_struct()
: some_base() // zero-initialize base (if compiler generated)
, str1() // has a normal constructor so just call it.
, a(0)
, b(0)
, c(0) // 0 is NULL
, str2()
// Initialize all padding to zero
{}
some_struct(some_struct const& copy)
: some_base(copy)
, str1(copy.str1)
, a(copy.a)
, b(copy.b)
, c(copy.c)
, str2(copy.str2)
{}
some_struct& operator=(some_struct const& copy)
{
some_base::operator=(copy);
str1 = copy.str1;
a = copy.a;
b = copy.b;
c = copy.c;
str2 = copy.str2;
return *this;
}
~some_struct()
{}
// Note the below is pseudo code
// Also note member destruction happens after user code.
// In the compiler generated version the user code is empty
: ~str2()
// PODs don''t have destructor
, ~str1()
, ~some_base();
// End of destructor here.
// In C++11 we also have Move constructor and move assignment.
some_struct(some_struct&& copy)
// ^^^^ Notice the double &&
: some_base(std::move(copy))
, str1(std::move(copy.str1))
, a(std::move(copy.a))
, b(std::move(copy.b))
, c(std::move(copy.c))
, str2(std::move(copy.str2))
{}
some_struct& operator=(some_struct&& copy)
// ^^^^ Notice the double &&
{
some_base::operator=(std::move(copy));
str1 = std::move(copy.str1);
a = std::move(copy.a);
b = std::move(copy.b);
c = std::move(copy.c);
str2 = std::move(copy.str2);
return *this;
}
};
las estructuras son básicamente una concatenación de sus componentes en la memoria (con algún posible relleno incorporado para la alineación). Cuando asigna una estructura al valor de otra, los valores simplemente se sobreponen.