Le 04/07/2011 21:35, Alf P. Steinbach /Usenet a ?crit :
* Anonymous, on 04.07.2011 20:41:
class VirtualBase {
protected:
class Initializer;
friend class Initializer;
class Initializer {
public:
Initializer() {
std::cout << "Initializer" << std::endl;
p = 0;
}
~Initializer() {
std::cout << "~Initializer" << std::endl;
if (p)
p->Init(); // call the virtual function
}
// might be private if VirtualBase is declared as friend...
void Register(VirtualBase* b) const {
p = b;
}
private:
mutable VirtualBase* p;
// private and not implemented
Initializer(const Initializer&);
void operator =(const Initializer&);
};
public:
VirtualBase(const Initializer& i) {
std::cout << "VirtualBase" << std::endl;
i.Register(this);
}
private:
virtual void Init() = 0;
// will be called immediately after the constructor
// of the most derived class
};
// This is the actual hierarchy
class Base : public virtual VirtualBase {
public:
Base(const VirtualBase::Initializer& i = Initializer()) :
VirtualBase(i) {
std::cout << "Base" << std::endl;
}
~Base() {
std::cout << "~Base" << std::endl;
}
void Init() {
std::cout << "Base::Init()" << std::endl;
}
};
class Derived : public Base {
public:
// Derived() : Base() {} // compile-time error as wanted
Derived(const VirtualBase::Initializer& i = Initializer()) :
Base(i), /* Base(i) is optional...*/
VirtualBase(i)
{
std::cout << "Derived" << std::endl;
}
~Derived() {
std::cout << "~Derived" << std::endl;
}
void Init() {
std::cout << "Derived::Init()" << std::endl;
}
};
int main() {
Base x; // calls Base::Init
Derived y ; // calls Derived::Init
return( 0 ) ;
}
I reworked your example to show off the memory leak problem that I
mentioned.
In addition this led me to realize one problem that I didn't think of
earlier, namely that in the event of a failing constructor, the
Initializer destructor will be calling a virtual method on a
non-existent object, invoking UB.
Here's the reworked code, just play with the macro symbols NO_INIT_CALL,
FAIL_CONSTRUCTOR and FAIL_INIT:
<code>
#include <iostream>
#include <stdexcept>
#include <stdlib.h>
using namespace std;
namespace g {
int nObjectsAllocated = 0;
} // namespace g
class VirtualBase {
protected:
class Initializer;
friend class Initializer;
class Initializer {
public:
Initializer() {
cout << "Initializer" << endl;
p = 0;
}
~Initializer() {
cout << "~Initializer" << endl;
#ifndef NO_INIT_CALL
if (p)
p->Init(); // call the virtual function
#endif
}
// might be private if VirtualBase is declared as friend...
void Register(VirtualBase* b) const {
p = b;
}
private:
mutable VirtualBase* p;
// private and not implemented
Initializer(const Initializer&);
void operator =(const Initializer&);
};
public:
VirtualBase(const Initializer& i) {
cout << "VirtualBase" << endl;
i.Register(this);
}
private:
virtual void Init() = 0;
// will be called immediately after the constructor
// of the most derived class
};
// This is the actual hierarchy
class Base : public virtual VirtualBase {
public:
static void* operator new( size_t size )
{
cout << "Allocation" << endl;
++g::nObjectsAllocated;
return ::operator new( size );
}
static void operator delete( void* p )
{
cout << "Deallocation" << endl;
--g::nObjectsAllocated;
return ::operator delete( p );
}
Base(const VirtualBase::Initializer& i = Initializer()) : VirtualBase(i) {
cout << "Base" << endl;
#ifdef FAIL_CONSTRUCTOR
throw runtime_error( "Oops, `Base::Base` failed." );
#endif
}
~Base() {
cout << "~Base" << endl;
}
void Init() {
cout << "Base::Init()" << endl;
#ifdef FAIL_INIT
throw runtime_error( "Oops, `Init` failed." );
#endif
}
};
int main()
{
int result = EXIT_FAILURE;
try
{
Base* p = new Base; // calls Base::Init
cout << endl;
delete p;
result = EXIT_SUCCESS;
}
catch( exception const& x )
{
cerr << "!" << x.what() << endl;
}
if( g::nObjectsAllocated > 0 )
{
cerr << "!Memory leak." << endl;
}
cout << "Finished." << endl;
return result;
}
</code>
As you can see the scheme, at least in its current incarnation, is not
very exception-friendly: invoking UB, and leaking memory.
Can it be made exception safe?
Cheers & hth.,
- Alf
So the code should have no more memory leaks.