Re: Using this in initialization list??
On May 6, 1:07 pm, Neelesh <neelesh.bo...@gmail.com> wrote:
On May 6, 1:51 pm, James Kanze <james.ka...@gmail.com> wrote:
On May 6, 6:22 am, Neelesh <neelesh.bo...@gmail.com> wrote:
On May 6, 5:36 am, huil...@gmail.com wrote:
Is the following code valid?
template < typename F >
struct base1
{
base1(const F& f):f(f){}
const F& f;
};
template < typename E1, typename E2 >
struct base2
{
base2(const E1& e1, const E2& e2):e1(e1),e2(e2){}
const E1& e1;
const E2& e2;
};
template < typename F1, typename F2 >
struct derived : base1<F1>
,base2<base1<F1>,F2>
{
derived(const F1& f1, const F2& f2)
:base1<F1>(f1)
,base2<base1<F1>,F2>(static_cast<base1<F1> const&>(=
*this), f2) // is
this okay?
{}
};
Yes, the code is valid and the static_cast is not needed.
12.6.2p7
...because the meminitializer are evaluated in the scope
of the constructor, the this pointer can be used in the
expressionlist of a meminitializer to refer to the object
being initialized.
No, it's not valid. The expression *this is an lvalue
expression with type derived. Type derived has a
non-trivial constructor. According to =A73.8:
[...] The lifetime of an object of type T begins when:
-- storage with the proper alignment and size for type
T is obtained, and
-- if T is a class type with a non-trivial constructor
(12.1), the constructor call has completed.
Furthermore:
Before the lifetime of an object has started but after
the storage which the object will occupy has been
allocated or, after the lifetime of an object has ended
and before the storage which the object occupied is
reused or released, any pointer that refers to the
storage location where the object will be or was located
may be used but only in limited ways. Such a pointer
refers to allocated storage (3.7.3.2), and using the
pointer as if the pointer were of type void*, is
well-defined. Such a pointer may be dereferenced but the
resulting lvalue may only be used in limited ways, as
described below. If the object will be or was of a class
type with a non-trivial destructor, and the pointer is
used as the operand of a delete-expression, the program
has undefined behavior. If the object will be or was of
a non-POD class type, the program has undefined behavior
if:
[...]
-- the pointer is used to access a non-static data
member or call a non-static member function of the
object, or
-- the pointer is implicitly converted (4.10) to a
pointer to a base class type, or
-- the pointer is used as the operand of a static_cast
(5.2.9) (except when the conversion is to void*, or
to void* and subsequently to char*, or unsigned
char*).
There's no exception that I can see for the this pointer.
hmm...yes..agreed. In fact, in the current case, 3.8p6 applies
more appropriately:
if the original object will be or was of a nonPOD class type, the
program has undefined behavior if:
[...]
=97 the lvalue is implicitly converted (4.10) to a reference to a base
class type, or
[...]
The lvalue (*this) has been converted to reference to base
class type (base<F1>&) in the constructor of derived class,
which would result in undefined behavior.
However, I believe that this is somewhat strange and I donot
see any obvious reason of not allowing this.
I can't either. I think the case the standard is trying to
address is that of some external pointer. Something along the
lines of:
Derived* p = static_cast< Derived* >(
::operator new( sizeof Derived ) ) ;
new ( p ) Derived ;
and then the base class constructor calls a function which tries
to convert p to a Base*. There are very good reasons why this
should be undefined behavior. And those reasons really apply to
just about any pointer except this; the compiler can only do the
conversion correctly if it knows (more or less) how much has or
has not been constructed (at least if the base is virtual). In
the case of this, however, the compiler knows exactly what the
situation is, what parts have been constructed, and what parts
haven't. And the compiler has to be able to do the conversion
in order to call the various constructors of the base classes
anyway, or to call member functions in already constructed base
classes (which is allowed).
Practically, I'm not sure what this means. You definitely
can call non-static member functions of already constructed
sub-objects,
calling virtual functions of already constructed sub-objects
could be a problem in a sense that it might behave differently
for *this and for any other object of the same type.
Agreed. I'm not sure what calling a virtual function in such
cases would mean. I.e. given:
Derived::Derived() : Base1(), Base2( virtualFuncInBase1() ) {}
When "virtualFuncInBase1" is called, what is the dynamic type of
the object? It can't yet be Derived, since we've yet to enter
the body of Derived, and in the constructor of Base2, it will be
Base2. And if it is Base2, there's likely no virtualFuncInBase1
in Base2, so the code shouldn't even compile. Maybe such calls
are illegal. (My Unix machine is in the process of being
upgraded, and all my copies of the standard are on it, so I
can't verify anything for the moment.) What I can say is that
I've actually had a case a bit like the above, where Base1 was
virtual, and Base2 took a pointer to a base class of Base1. If I
passed this, the generated code core dumped; if I called a
member function of Base1 which returned this, the code worked.
But of course, the behavior of one compiler doesn't prove
anything (especially as other compilers got it right when I
simply passed this).
I'll look into it further once I get access to the standard
again. The case does occur in one very common idiom: when
creating an istream or ostream to use a custom streambuf. The
classic idiom is:
class myistream : private mystreambuf, public std::istream
{
public:
myistream() : mystreambuf(), std::istream( this ) {}
} ;
, with variations on whether you explicitly cast the this or
not, whether the inhertance of mystreambuf is virtual or not,
etc. In the end, what I currently have is a wrapper class
(templated) which contains the mystreambuf, and has a function
which returns a pointer to it. The initializer expression for
std::istream calls this function.
In the current case, if we use (*this) to initialize a
reference e1 in constructor of base2, and call a virtual
member function of e1 from constructor of class base2, I would
not be surprized if the call is resolved by the base1 version,
since there is no derived object constructed yet.
That would be correct (supposing that Base2 takes a reference to
a Base1). I don't see any problem there, provided the correct
pointer (to the Base1 subobject) is passed to the constructor of
Base2. The problem is getting the correct pointer. Consider:
Derived* current ;
Derived* factory()
{
current = static_cast< Derived* >(
::operator new( sizeof( Derived ) ) ;
new ( current ) Derived ;
return current ;
}
Derived::Derived()
: Base1()
, Base2( *current ) // expects a Base1&
{
}
If Base1 is virtual, do you really expect a compiler to be able
to generate the correct code for the call to Base2 (supposing
that it doesn't know analyse enought to know that current is
always equal to this at this point---and I doubt many compilers
are capable of that analysis). It can only suppose that current
points to a fully constructed object, with a vptr which
corresponds to that of the full object. Which isn't necessarily
the case.
The difference when you pass the this pointer, of course, is
that the compiler does know the exact state of the pointed to
object. And it does know how to find the address of any given
base class. (It has to, since it has to be able to set up the
this pointer when calling member functions of Base1 from the
body of the constructor.)
template < typename F > struct base1
Just a nit, but none of this has anything to do with templates.
Using templates in the examples just adds extra noise for the
reader.
{
base1(const F& f): f(f) { }
const F& f;
virtual void fun() const { cout << "base fun" << endl; }
};
template < typename E1, typename E2 > struct base2
{
base2(const E1& e1, const E2& e2):
e1(e1),
e2(e2)
{
e1.fun(); //calling member functions of member objects is fine in
general, but creates problems when e1 is a reference to the derived
object being constructed
If E1 is truely the derived type, the code has undefined
behavior. If E1 is an already constructed base class of the
derived type, the dynamic type (to which the virtual function
resolves) of e1 should be that of the already constructed base
class. Undefined behavior if the function is pure virtual in
this class; well defined behavior (but not necessarily what is
wanted) otherwise.
I don't think that this is really any different than calling any
other function which calls a virtual function in the already
constructed base. The problem in question is getting the
correct address for e1.
}
const E1& e1;
const E2& e2;
};
template < typename F1, typename F2 > struct derived : base1<F1>,
base2<base1<F1>,F2>
{
derived(const F1& f1, const F2& f2):
base1<F1>(f1),
base2<base1<F1>,F2>(*this, f2) //Assume this was legal somehow
{}
virtual void fun() const { cout << "derived fun" << endl; }
};
The call e1.fun(); would typically get resolved dynamically.
However, in the current case, since we are passing a
reference to an not-yet- completely constructed-object, the
call would get resolved by calling the base1::fun() instead of
derived::fun(). (Of course provided that passing *this in the
constructor of derived class was somehow legal.)
Yes. This is well known behavior; it crops up all the time. It
surprises newcomers, but after a bit of analysis, it is clear
that all of the alternative solutions are worse.
Another example to illustrate this point:
class X
{
public:
X() { }
X(const X& x1, const X& x2)
{
x1.f(); x2.f(); //check if call to f() is resolved dynamically or
not
}
virtual void f() const { std::cout << "base" << std::endl; }
};
class Z :public X
{
public:
Z() { }
Z(int m, const X& x) : X(x, *this) //pass two objects of same
type, one completely constructed and one partially constructed. Assume
that passing *this was valid somehow.
{ }
virtual void f() const { std::cout << "derived" << std::endl; }
};
int main()
{
Z z;
Z z2(1, z); //Dummy parameter 1 added, otherwise it will call the
CC.
}
Prints "base" and the "derived", indicating that x.f() is
resolved dynamically but not x2.f().
Both are resolved dynamically. The difference is that when
executing X::X(), the dynamic type of the object is X.
However, I doubt if this is "strong" enough reason to disallow
passing (*this) to base class constructor.
I don't think that this has anything to do with it. The same
"problem" occurs no matter how you call f() in X::X(). The
problem is that in some cases, the compiler needs
meta-information (from the vtbl or elsewhere in the constructed
class) is order to do the derived to base conversion. This
information isn't necessarily valid until you've actually
entered the body of the constructor of the most derived class.
The case of this is different, because 1) the compiler knows
that the class is in the process of being constructed, and is
not in its final state, and 2) the compiler must be able to make
the necessary conversion anyway.
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34