Using new (this) to reuse constructors
This came up recently in a class for which I am a teaching assistant. We were teaching the students how to do copy constructors in c++, and the students who were originally taught java asked if you can call one constructor from another. I know the answer to this is no, as they are using the pedantic flag for their code in class, and the old standards do not have support for this. I found on Stackoverflow and other sites a suggestion to fake this using new (this)
such as follows
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;
}
};
This is really simple code, and obviously does not save any code by reusing the constructor, but it is just for example.
My question is if this is even standard compliant, and if there are any edge cases that should be considered that would prevent this from being sound code?
Edit: I should note that this seems very dangerous to me, but that is more from a view point of I don't really understand开发者_如何学Python it more than knowing how it can go bad. I just wanted to make sure that if asked about it by students that I can direct them towards why the can or shouldn't do it. I already suggested to them for all practical purposes to use a shared initialization method. This is more of a teaching question than for a practical project.
C++0x will introduce syntax to allow constructors to call other constructors.
Until then, new(this)
works in some cases, but not all. In particular, once in the constructor, your base class(es) are already fully constructed. Reconstructing via new(this)
re-calls the base constructors without calling the base destructors, so expect problems if the base classes weren't expecting this kind of hackery - and they probably weren't.
An example for clarity:
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
}
}
or as they say 'hilarity ensues'
The members and base classes will be initialised before entering the constructor body, then initialised again when you call the second constructor. In general this will lead to memory leaks and possibly undefined behaviour.
So the answer is "no, this is not sound code".
Here is what C++ FAQ has to say about it, in the question, “Can one constructor of a class call another constructor of the same class to initialize the this object?”:
BTW do NOT try to achieve this via placement new. Some people think they can say
new(this) Foo(x, int(x)+7)
within the body ofFoo::Foo(char)
. However that is bad, bad, bad. Please don't write me and tell me that it seems to work on your particular version of your particular compiler; it's bad. Constructors do a bunch of little magical things behind the scenes, but that bad technique steps on those partially constructed bits. Just say no.
Unless you're trying to call a parent's constructor, I'd suggest making a private initialization method. There's no reason you couldn't call a shared initializer across your constructors.
This does not work if you have a constructor like that:
class MyClass {
public:
MyClass( const std::string & PathToFile )
: m_File( PathToFile.c_str( ) )
{
}
private:
std::ifstream m_File;
}
The original argument cannot be recovered, so you cannot call that constructor from a copy-constructor.
As that exact code is written, it should work -- though I can't imagine exactly why you'd write code like that. In particular, it depends on the fact that all the pointer is only ever used to refer to a single int. That being the case, why didn't they just put an int in the object, instead of using a pointer and allocating the int dynamically? In short, what they have is long and inefficient, but not significantly different from:
class MyClass {
int v;
public:
MyClass(int init) : v(init) {}
int value() { return v; }
};
Unfortunately, the minute you try to get some real use from the pointer (e.g., allocating different amounts of memory in different objects) the "trick" they using with placement new quits working -- it depends completely on the fact that every object is allocating exactly the same amount of memory. Since you're limited to exactly the same allocation in each, why put that allocation in the heap instead of making it part of the object itself?
Truthfully, there are circumstances when it would make sense. The only one I can think of right off, however, is that the allocation is large and you're running in an environment where there's a lot more heap space than stack space.
The code works, but it's only useful under fairly narrow, specific circumstances. It doesn't strike me as something I'd recommend as an example of how to do things.
It seems to me that it's possible to use new(this) safely even in the constructor of a derived class, if you know what you're doing. You just have to make sure that your base class has a dummy constructor (and the same for its base class, all the way down the chain). For example:
#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());
}
Output:
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; }
Of course you're out of luck if the base class has constant* or reference members (or if you can't edit the file containing the base class's declaration). That would make it impossible to write a dummy constructor in it — not to mention that with "new(this)", you'd then be initializing these "constant" members twice! That's where the real thing, C++0x delegating constructors, could really come in handy.
Please tell me if there's anything else about this technique that can still be unsafe or non-portable.
(Edit: I've also realized that maybe in a virtual class, the virtual function table might be initialized twice. That would be harmless, but inefficient. I need to try that and see what the compiled code looks like.)
*If you merely have constant members (and no references) in the base class, you're not completely out of luck. You can just make sure all the classes of all the constant members have their own dummy constructors that the base class's dummy constructor can call in turn. You're out of luck if some of the constants have built-in types like int, though — those will be initialized unavoidably (e.g., a const int will be initialized to zero).
Edit: Here's an example of chaining dummy constructors, that would be broken if int value became const int value inside class 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());
}
Output:
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; }
(The 0xCCCCCCCC is what uninitialized memory is initalized with in the Debug build.)
精彩评论