c++ - español - Usando nuevo(esto) para reutilizar constructores
qgis español (7)
A medida que se escribe ese código exacto, debería funcionar, aunque no puedo imaginar exactamente por qué escribiría un código así. En particular, depende del hecho de que todo el puntero solo se use para referirse a un solo int. Siendo ese el caso, ¿por qué no solo pusieron un int en el objeto, en lugar de usar un puntero y asignar el int dinámicamente? En resumen, lo que tienen es largo e ineficiente, pero no significativamente diferente de:
class MyClass {
int v;
public:
MyClass(int init) : v(init) {}
int value() { return v; }
};
Desafortunadamente, en el momento en que intenta obtener un uso real del puntero (por ejemplo, la asignación de diferentes cantidades de memoria en diferentes objetos), el "truco" que utilizan al colocar nuevas paradas funciona, depende completamente del hecho de que cada objeto esté asignando exactamente la misma cantidad de memoria. Ya que está limitado a exactamente la misma asignación en cada una, ¿por qué poner esa asignación en el montón en lugar de hacerlo parte del objeto en sí?
A decir verdad, hay circunstancias en las que tendría sentido. Sin embargo, el único en el que puedo pensar es que la asignación es grande y se está ejecutando en un entorno en el que hay mucho más espacio de almacenamiento dinámico que de pila.
El código funciona, pero solo es útil en circunstancias bastante limitadas y específicas. No me parece algo que recomiendo como ejemplo de cómo hacer las cosas.
Esto surgió recientemente en una clase para la que soy asistente de enseñanza. Enseñamos a los alumnos cómo hacer copias de constructores en c ++, y los alumnos a quienes se les enseñó java originalmente preguntaron si puede llamar a un constructor de otro. Sé que la respuesta a esta pregunta es no, ya que están utilizando la marca pedante para su código en clase, y los estándares anteriores no tienen soporte para esto. Encontré en Stackoverflow y en otros sitios una sugerencia para falsificar esto usando un new (this)
como el siguiente
class MyClass
{
private:
int * storedValue;
public:
MyClass(int initialValue = 0)
{
storedValue = new int(initialValue);
}
~ MyClass()
{
delete storedValue;
}
MyClass(const MyClass &b)
{
new (this) MyClass(*(b.storedValue));
}
int value() {
return *storedValue;
}
};
Este es un código realmente simple, y obviamente no guarda ningún código reutilizando el constructor, pero es solo por ejemplo.
Mi pregunta es si esto cumple con el estándar, y si hay algún caso límite que se deba considerar que impida que esto sea un código de sonido.
Edit : Debo tener en cuenta que esto me parece muy peligroso, pero eso es más bien desde el punto de vista de que realmente no lo entiendo más que saber cómo puede ir mal. Solo quería asegurarme de que, si los alumnos me preguntan, puedo dirigirlos hacia por qué pueden o no deben hacerlo. Ya les sugerí, para todos los propósitos prácticos, utilizar un método de inicialización compartido. Esto es más una cuestión de enseñanza que para un proyecto práctico.
A menos que esté intentando llamar al constructor de un padre, sugeriría hacer un método de inicialización privado. No hay razón para que no puedas llamar a un inicializador compartido entre tus constructores.
Aquí está lo que C ++ FAQ tiene que decir al respecto, en la question , "¿Puede un constructor de una clase llamar a otro constructor de la misma clase para inicializar este objeto?":
Por cierto, no intente lograr esto a través de una nueva colocación. Algunas personas piensan que pueden decir
new(this) Foo(x, int(x)+7)
dentro del cuerpo deFoo::Foo(char)
. Sin embargo eso es malo, malo, malo. Por favor, no me escriba y dígame que parece funcionar en su versión particular de su compilador particular; es malo. Los constructores hacen un montón de pequeñas cosas mágicas detrás de las escenas, pero esa mala técnica pisa esos bits parcialmente construidos. Solo di no.
C ++ 0x introducirá la sintaxis para permitir que los constructores llamen a otros constructores.
Hasta entonces, el new(this)
funciona en algunos casos, pero no en todos. En particular, una vez en el constructor, sus clases base ya están completamente construidas. La reconstrucción a través de new(this)
vuelve a llamar a los constructores de base sin llamar a los destructores de base, así que espere problemas si las clases base no esperaban este tipo de piratería, y probablemente no lo fueron.
Un ejemplo de claridad:
class Base
{
public:
char *ptr;
MyFile file;
std::vector vect;
Base()
{
ptr = new char[1000];
file.open("some_file");
}
~Base()
{
delete [] ptr;
file.close();
}
};
class Derived : Base
{
Derived(Foo foo)
{
}
Derived(Bar bar)
{
printf(ptr...); // ptr in base is already valid
new (this) Derived(bar.foo); // ptr re-allocated, original not deleted
//Base.file opened twice, not closed
// vect is who-knows-what
// etc
}
}
o como dicen "la hilaridad se produce"
Esto no funciona si tienes un constructor como ese:
class MyClass {
public:
MyClass( const std::string & PathToFile )
: m_File( PathToFile.c_str( ) )
{
}
private:
std::ifstream m_File;
}
El argumento original no se puede recuperar, por lo que no puede llamar a ese constructor desde un constructor de copia.
Los miembros y las clases base se inicializarán antes de ingresar al cuerpo del constructor, y luego se inicializarán nuevamente cuando llame al segundo constructor. En general, esto conducirá a fugas de memoria y posiblemente un comportamiento indefinido.
Entonces la respuesta es "no, esto no es un código de sonido".
Me parece que es posible usar nuevo (esto) de manera segura incluso en el constructor de una clase derivada, si sabes lo que estás haciendo. Solo debes asegurarte de que tu clase base tenga un constructor ficticio (y lo mismo para su clase base, hasta el final de la cadena). Por ejemplo:
#include <stdio.h>
#include <new>
struct Dummy {};
struct print
{
print(const char *message) { fputs(message, stdout); }
print(const char *format, int arg1) { printf(format, arg1); }
print(const char *format, int arg1, int arg2) { printf(format, arg1, arg2); }
};
struct print2 : public print
{
print2(const char *message) : print(message) {}
print2(const char *format, int arg1) : print(format, arg1) {}
print2(const char *format, int arg1, int arg2) : print(format, arg1, arg2) {}
};
class foo : public print
{
int *n;
public:
foo(Dummy) : print("foo::foo(Dummy) {}/n") {}
foo() : print("foo::foo() : n(new int) {}/n"), n(new int) {}
foo(int n) : print("foo::foo(int n=%d) : n(new int(n)) {}/n", n), n(new int(n)) {}
int Get() const { return *n; }
~foo()
{
printf("foo::~foo() { delete n; }/n");
delete n;
}
};
class bar : public print2, public foo
{
public:
bar(int x, int y) : print2("bar::bar(int x=%d, int y=%d) : foo(x*y) {}/n", x, y), foo(x*y) {}
bar(int n) : print2("bar::bar(int n=%d) : foo(Dummy()) { new(this) bar(n, n); }/n", n), foo(Dummy())
{
__assume(this); // without this, MSVC++ compiles two extra instructions checking if this==NULL and skipping the constructor call if it does
new(this) bar(n, n);
}
~bar()
{
printf("bar::~bar() {}/n");
}
};
void main()
{
printf("bar z(4);/n");
bar z(4);
printf("z.Get() == %d/n", z.Get());
}
Salida:
bar z(4);
bar::bar(int n=4) : foo(Dummy()) { new(this) bar(n, n); }
foo::foo(Dummy) {}
bar::bar(int x=4, int y=4) : foo(x*y) {}
foo::foo(int n=16) : n(new int(n)) {}
z.Get() == 16
bar::~bar() {}
foo::~foo() { delete n; }
Por supuesto, está fuera de suerte si la clase base tiene miembros constantes * o de referencia (o si no puede editar el archivo que contiene la declaración de la clase base). Eso haría imposible escribir un constructor ficticio en él, sin mencionar que con "nuevo (esto)", ¡estarías inicializando estos miembros "constantes" dos veces! Ahí es donde realmente podrían ser útiles los constructores delegados C ++ 0x.
Por favor, dígame si hay algo más acerca de esta técnica que aún pueda ser insegura o no portátil.
(Edición: También me di cuenta de que tal vez en una clase virtual, la tabla de funciones virtuales podría inicializarse dos veces. Eso sería inofensivo, pero ineficiente. Necesito intentarlo y ver cómo se ve el código compilado).
* Si simplemente tienes miembros constantes (y no hay referencias) en la clase base, no estás completamente sin suerte. Puede asegurarse de que todas las clases de todos los miembros constantes tengan sus propios constructores ficticios a los que el constructor ficticio de la clase base puede llamar por turno. Sin embargo, no tendrá suerte si algunas de las constantes tienen tipos incorporados como int , que se iniciarán inevitablemente (por ejemplo, una constante se inicializará a cero).
Edición: Este es un ejemplo de encadenamiento de constructores ficticios, que se rompería si el valor int se convirtiera en un valor constante dentro de la clase FooBar:
#include <stdio.h>
#include <new>
struct Dummy {};
struct print
{
print(const char *message) { fputs(message, stdout); }
print(const char *format, int arg1) { printf(format, arg1); }
print(const char *format, int arg1, int arg2) { printf(format, arg1, arg2); }
};
struct print2 : public print
{
print2(const char *message) : print(message) {}
print2(const char *format, int arg1) : print(format, arg1) {}
print2(const char *format, int arg1, int arg2) : print(format, arg1, arg2) {}
};
class FooBar : public print
{
int value;
public:
FooBar() : print("FooBar::FooBar() : value(0x12345678) {}/n"), value(0x12345678) {}
FooBar(Dummy) : print("FooBar::FooBar(Dummy) {}/n") {}
int Get() const { return value; }
};
class foo : public print
{
const FooBar j;
int *n;
public:
foo(Dummy) : print("foo::foo(Dummy) : j(Dummy) {}/n"), j(Dummy()) {}
foo() : print("foo::foo() : n(new int), j() {}/n"), n(new int), j() {}
foo(int n) : print("foo::foo(int n=%d) : n(new int(n)), j() {}/n", n), n(new int(n)), j() {}
int Get() const { return *n; }
int GetJ() const { return j.Get(); }
~foo()
{
printf("foo::~foo() { delete n; }/n");
delete n;
}
};
class bar : public print2, public foo
{
public:
bar(int x, int y) : print2("bar::bar(int x=%d, int y=%d) : foo(x*y) {}/n", x, y), foo(x*y) {}
bar(int n) : print2("bar::bar(int n=%d) : foo(Dummy()) { new(this) bar(n, n); }/n", n), foo(Dummy())
{
printf("GetJ() == 0x%X/n", GetJ());
__assume(this); // without this, MSVC++ compiles two extra instructions checking if this==NULL and skipping the constructor call if it does
new(this) bar(n, n);
}
~bar()
{
printf("bar::~bar() {}/n");
}
};
void main()
{
printf("bar z(4);/n");
bar z(4);
printf("z.Get() == %d/n", z.Get());
printf("z.GetJ() == 0x%X/n", z.GetJ());
}
Salida:
bar z(4);
bar::bar(int n=4) : foo(Dummy()) { new(this) bar(n, n); }
foo::foo(Dummy) : j(Dummy) {}
FooBar::FooBar(Dummy) {}
GetJ() == 0xCCCCCCCC
bar::bar(int x=4, int y=4) : foo(x*y) {}
foo::foo(int n=16) : n(new int(n)), j() {}
FooBar::FooBar() : value(0x12345678) {}
z.Get() == 16
z.GetJ() == 0x12345678
bar::~bar() {}
foo::~foo() { delete n; }
(El 0xCCCCCCCC es con lo que se inicializa la memoria no inicializada en la compilación de depuración).