All non-static member functions have a hidden parameter, a pointer to an instance of the class, named this
; this parameter is silently inserted at the beginning of the parameter list, and handled entirely by the compiler. When a member of the class is accessed inside a member function, it is silently accessed through this
; this allows the compiler to use a single non-static member function for all instances, and allows a member function to call other member functions polymorphically.
struct ThisPointer {
int i;
ThisPointer(int ii);
virtual void func();
int get_i() const;
void set_i(int ii);
};
ThisPointer::ThisPointer(int ii) : i(ii) {}
// Compiler rewrites as:
ThisPointer::ThisPointer(int ii) : this->i(ii) {}
// Constructor is responsible for turning allocated memory into 'this'.
// As the constructor is responsible for creating the object, 'this' will not be "fully"
// valid until the instance is fully constructed.
/* virtual */ void ThisPointer::func() {
if (some_external_condition) {
set_i(182);
} else {
i = 218;
}
}
// Compiler rewrites as:
/* virtual */ void ThisPointer::func(ThisPointer* this) {
if (some_external_condition) {
this->set_i(182);
} else {
this->i = 218;
}
}
int ThisPointer::get_i() const { return i; }
// Compiler rewrites as:
int ThisPointer::get_i(const ThisPointer* this) { return this->i; }
void ThisPointer::set_i(int ii) { i = ii; }
// Compiler rewrites as:
void ThisPointer::set_i(ThisPointer* this, int ii) { this->i = ii; }
In a constructor, this
can safely be used to (implicitly or explicitly) access any field that has already been initialised, or any field in a parent class; conversely, (implicitly or explicitly) accessing any fields that haven't yet been initialised, or any fields in a derived class, is unsafe (due to the derived class not yet being constructed, and thus its fields neither being initialised nor existing). It is also unsafe to call virtual member functions through this
in the constructor, as any derived class functions will not be considered (due to the derived class not yet being constructed, and thus its constructor not yet updating the vtable).
Also note that while in a constructor, the type of the object is the type which that constructor constructs. This holds true even if the object is declared as a derived type. For example, in the below example, ctd_good
and ctd_bad
are type CtorThisBase
inside CtorThisBase()
, and type CtorThis
inside CtorThis()
, even though their canonical type is CtorThisDerived
. As the more-derived classes are constructed around the base class, the instance gradually goes through the class hierarchy until it is a fully-constructed instance of its intended type.
class CtorThisBase {
short s;
public:
CtorThisBase() : s(516) {}
};
class CtorThis : public CtorThisBase {
int i, j, k;
public:
// Good constructor.
CtorThis() : i(s + 42), j(this->i), k(j) {}
// Bad constructor.
CtorThis(int ii) : i(ii), j(this->k), k(b ? 51 : -51) {
virt_func();
}
virtual void virt_func() { i += 2; }
};
class CtorThisDerived : public CtorThis {
bool b;
public:
CtorThisDerived() : b(true) {}
CtorThisDerived(int ii) : CtorThis(ii), b(false) {}
void virt_func() override { k += (2 * i); }
};
// ...
CtorThisDerived ctd_good;
CtorThisDerived ctd_bad(3);
With these classes and member functions:
ctd_good
:
CtorThisBase
is fully constructed by the time the CtorThis
constructor is entered. Therefore, s
is in a valid state while initialising i
, and can thus be accessed.i
is initialised before j(this->i)
is reached. Therefore, i
is in a valid state while initialising j
, and can thus be accessed.j
is initialised before k(j)
is reached. Therefore, j
is in a valid state while initialising k
, and can thus be accessed.ctd_bad
:
k
is initialised after j(this->k)
is reached. Therefore, k
is in an invalid state while initialising j
, and accessing it causes undefined behaviour.CtorThisDerived
is not constructed until after CtorThis
is constructed. Therefore, b
is in an invalid state while initialising k
, and accessing it causes undefined behaviour.ctd_bad
is still a CtorThis
until it leaves CtorThis()
, and will not be updated to use CtorThisDerived
's vtable until CtorThisDerived()
. Therefore, virt_func()
will call CtorThis::virt_func()
, regardless of whether it is intended to call that or CtorThisDerived::virt_func()
.