Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Auto destruct

0 views
Skip to first unread message

JKop

unread,
Aug 5, 2004, 9:49:32 AM8/5/04
to

Using MSWindows as an example:

On MSWindows, there's a thing called the System Registry, which is a really
big database that holds all the settings of the OS. There's API's for
working with the registry. The two I will be concerned with are:

RegOpenKeyEx
RegCloseKey

Now, here's some code:

int main()
{
HKEY hkey;

RegOpenKeyEx( &hkey );

... //some stuff

RegCloseKey( hkey );
}


The only problem here is that if an exception is thrown during "some stuff",
then the key never gets closed. Here was my first solution:

class HKEYe
{
public:

HKEY hkey;
bool to_be_closed;

HKEYe() : to_be_closed(false) {}

~HKEYe()
{
if (to_be_closed)
{
RegCloseKey(hkey);
}
}

operator HKEY&() { return hkey; }
operator const HKEY&() const { return hkey; }


HKEY* operator&() { return &hkey; }
const HKEY* operator&() const { return &hkey; }
};

int main()
{
HKEYe hkey;

RegOpenKeyEx( &hkey );

hkey.to_be_closed = true;

//... some stuff

RegCloseKey(hkey);
hkey.to_be_closed = false;

}


Now if an exception is thrown, then the key does get closed.

Anyway, my next thought was to make a template out of this. Has this been
done before? Anyway, here's my first attempt:


template<class T>
class AutoDestructive
{
public:

T t;
bool to_be_auto_destructed;

AutoDestructive() : t(), to_be_auto_destructed(false) {}
template<class A> AutoDestructive(A a) : t(a), to_be_auto_destructed
(false) {}
template<class A> AutoDestructive(const A a) : t(a),
to_be_auto_destructed(false) {}
template<class A, class B> AutoDestructive(A a, B b) : t(a,b),
to_be_auto_destructed(false) {}
template<class A, class B, class C> AutoDestructive(A a, B b, C c) : t
(a,b,c), to_be_auto_destructed(false) {}

virtual ~AutoDestructive() = 0;

operator T&() { return t; }
operator const T&() const { return t; }


T* operator&() { return &t; }
const T* operator&() const { return &t; }
};

class AutoDestructive_HKEY : public AutoDestructive<HKEY>
{
public:

~AutoDestructive_HKEY()
{
if (to_be_auto_destructed) RegCloseKey(t);
}
};

int main()
{
AutoDestructive_HKEY hkey;

LONG error_code = RegOpenKeyEx(HKEY_CLASSES_ROOT,
"*",
0,
KEY_QUERY_VALUE|KEY_SET_VALUE,
&hkey);

if (error_code == ERROR_SUCCESS)
{
hkey.to_be_auto_destructed = true;
}
}


Comments, questions, suggestions welcomed.

-JKop

Alf P. Steinbach

unread,
Aug 5, 2004, 9:56:31 AM8/5/04
to
* JKop:

>
> Now if an exception is thrown, then the key does get closed.
>
> Anyway, my next thought was to make a template out of this. Has this been
> done before?

Petri Marginean's ScopeGuard-class.

See the article about ScopeGuard (Andrei Alexandrescu & Petri Marginean) at
<url: http://www.cuj.com/documents/s=8000/cujcexp1812alexandr/>, source code
at <url: ftp://ftp.cuj.com/pub/2000/cujdec2000.zip>.

Note that for MSVC you'll have to replace use of standard __LINE__ with
Microsoft __COUNTER__ to make it work in general, at least if you're using the
"edit & continue" option of that compiler (it's a bit broken).

--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?

Niels Dybdahl

unread,
Aug 5, 2004, 10:12:40 AM8/5/04
to
> ...
> Comments, questions, suggestions welcomed.

I prefer writing such a class like this:

class CRegKey {
public:
HKEY hkey;
CRegKey() {
if (RegOpenKeyEx( &hkey ))
throw...
}
~CRegKey() {
RegCloseKey( hkey );
}
};

int main()
{
{
CRegKey regKey;
...
}
};

The code for the class is so small that it is not worth making a template
for it. An invariant for the class is that the handle is open, so you do not
have to test if it is open, you do not have to remember to open it and you
do not have to remember to close it. As long as it is in scope it is open.

Niels Dybdahl


Peter van Merkerk

unread,
Aug 5, 2004, 10:19:42 AM8/5/04
to
JKop wrote:

Congratulations, you just "invented" the RAII idiom. The std::auto_ptr
class in standard library is example of the RAII idiom. This idiom is
described in the book "The C++ Programming Language" from Bjarne
Stroustrup. Using the destructor to do proper clean up is a well known
C++ way to write exception safe code.

This idiom can be used for many more cases than just releasing memory or
handles. For example in a GUI application you could write a class that
in the constructor changes the cursor shape to an hourglass and in the
destructor restores the cursor to its previous shape.

void HeavyProcessing()
{
WaitCursor wc;

// Do some stuff here....
// Exceptions or returns anywhere in the
// function will restore the cursor shape.
}

--
Peter van Merkerk
peter.van.merkerk(at)dse.nl

Thore Karlsen

unread,
Aug 5, 2004, 10:26:30 AM8/5/04
to
On Thu, 05 Aug 2004 13:49:32 GMT, JKop <NU...@NULL.NULL> wrote:

[...]

>Now if an exception is thrown, then the key does get closed.
>
>Anyway, my next thought was to make a template out of this. Has this been
>done before?

Yes. For registry keys, you have CRegKey in ATL 7.x. A more generic
solution can be found in the ScopeGuard class, as mentioned by Alf. You
can also take a look at the SmartAny library in the PowerTools package
at:

http://www.gotdotnet.com/team/cplusplus/

--
Be seeing you.

JKop

unread,
Aug 5, 2004, 10:48:46 AM8/5/04
to
Alf P. Steinbach posted:

> * JKop:
>>
>> Now if an exception is thrown, then the key does get
closed.
>>
>> Anyway, my next thought was to make a template out of
this. Has this
>> been done before?
>
> Petri Marginean's ScopeGuard-class.
>
> See the article about ScopeGuard (Andrei Alexandrescu &
Petri
> Marginean) at
><url: http://www.cuj.com/documents/s=
8000/cujcexp1812alexandr/>, source
>code
> at <url: ftp://ftp.cuj.com/pub/2000/cujdec2000.zip>.
>
> Note that for MSVC you'll have to replace use of
standard __LINE__ with
> Microsoft __COUNTER__ to make it work in general, at
least if you're
> using the "edit & continue" option of that compiler
(it's a bit
> broken).
>

Seems to me that the only problem is that you can't
specify arguments to the function. I suppose an inline
function with global references/pointers would do the job,
but how and ever.


I might keep going with my template and see how it goes.

First thing though, is there any way for a derived class
to inherit the constructors of its base? eg.

class Base
{
public:

Base() {}
Base(int) {}
Base(double&) {}
};

class Derived : public Base
{

};

int main()
{
double k;

Derived monkey(k);
}


I know that you can do the following:

class Derived : public Base
{
public:

Derived() : Base() {}
Derived(int) : Base(int) {}
Derived(double&) : Base(double&) {}
};

But this will be very tedious if there's dozens of
contructors in the base. For example, for my template
class, the base will have loads of constructors so that
you can actually use the constructor of the type you're
"protecting".


-JKop

JKop

unread,
Aug 5, 2004, 11:17:26 AM8/5/04
to
g++ cnt.cpp -ansi -pedantic -o c.exe

C:\WINDOWS\TEMP/ccgrbaaa.o(.text$_ZN15AutoDestructiveIiED2Ev+0x16):cnt.cpp:
undefined reference to `AutoDestructive<int>::CleanUp()'


Can some-one else please try compile it and see what happens.

Anyone know what's wrong with the code?


Here's the code:


typedef int HKEY;
const int ERROR_SUCCESS(5);

void RegCloseKey(HKEY hkey) {}
int RegOpenKeyEx(HKEY *hkey) { return ERROR_SUCCESS; }

template<class T>
class AutoDestructive
{
public:

T t;
bool to_be_auto_destructed;

AutoDestructive() : t(), to_be_auto_destructed(false) {}

virtual void CleanUp() = 0;

~AutoDestructive()
{
this->CleanUp();
}

operator T&() { return t; }
operator const T&() const { return t; }


T* operator&() { return &t; }
const T* operator&() const { return &t; }
};


class AutoDestructive_HKEY : public AutoDestructive<HKEY>
{
public:

virtual void CleanUp()
{
if (to_be_auto_destructed)
{
RegCloseKey(t);
to_be_auto_destructed = false;
}
}
};


int main()
{
AutoDestructive_HKEY hkey;

int error_code = RegOpenKeyEx(&hkey);

if (error_code == ERROR_SUCCESS)
{
hkey.to_be_auto_destructed = true;
}
}


-JKop

Ioannis Vranos

unread,
Aug 5, 2004, 11:25:53 AM8/5/04
to

System specific stuff but a generic issue. :-) Check "Resource
Acquisition is Initialisation" technique (RAII in short) even in my
messages of the past. :-)


Regards,

Ioannis Vranos

http://www23.brinkster.com/noicys

Alf P. Steinbach

unread,
Aug 5, 2004, 11:44:11 AM8/5/04
to
* JKop:

>
> Seems to me that the only problem is that you can't
> specify arguments to the function.

You can.

JKop

unread,
Aug 5, 2004, 12:16:54 PM8/5/04
to
> ~AutoDestructive()
> {
> this->CleanUp();
> }

The probem is with the above.

The following doesn't compile either:

class Base
{
public:

virtual void Monkey() = 0;

~Base()
{
this->Monkey();
}
};

class Derived : public Base
{
public:

virtual void Monkey()
{

}
};

int main()
{
Derived checker;
}


WHY?


-JKop

Julián Albo

unread,
Aug 5, 2004, 12:38:59 PM8/5/04
to
JKop wrote:

>> ~AutoDestructive()
>> {
>> this->CleanUp();
>> }
>
> The probem is with the above.

You can't safely call virtual functions inside contructors and destructors,
because the state of the object is not completely defined.

--
Salu2

Victor Bazarov

unread,
Aug 5, 2004, 12:48:17 PM8/5/04
to

Why do you say that? What is exactly not completely defined in the
object's state? The base classes and members have been initialised
when the body of the c-tor is entered, and are still around when
the body of the d-tor is executing. What _else_ is there?

Virtual functions are not linked dynamically from c-tors and d-tors
and instead are statically linked, but that, again, should not be
a problem: the 'CleanUp' member of 'AutoDestructive' should only
require base classes and members for its functionality, and not any
of potential derived classes, no?

So, what's the problem with safety?

V

tom_usenet

unread,
Aug 5, 2004, 12:56:49 PM8/5/04
to
On Thu, 05 Aug 2004 15:17:26 GMT, JKop <NU...@NULL.NULL> wrote:

>g++ cnt.cpp -ansi -pedantic -o c.exe
>
>C:\WINDOWS\TEMP/ccgrbaaa.o(.text$_ZN15AutoDestructiveIiED2Ev+0x16):cnt.cpp:
>undefined reference to `AutoDestructive<int>::CleanUp()'
>
>
>Can some-one else please try compile it and see what happens.
>
>Anyone know what's wrong with the code?
>
>
>Here's the code:
>
>
>typedef int HKEY;
>const int ERROR_SUCCESS(5);
>
>void RegCloseKey(HKEY hkey) {}
>int RegOpenKeyEx(HKEY *hkey) { return ERROR_SUCCESS; }
>
>template<class T>
>class AutoDestructive
>{
>public:
>
> T t;
> bool to_be_auto_destructed;
>
> AutoDestructive() : t(), to_be_auto_destructed(false) {}
>
> virtual void CleanUp() = 0;
>
> ~AutoDestructive()
> {
> this->CleanUp();

There's your problem - calling a virtual function from a destructor
doesn't do what you think it does. Calling a pure virtual function
from a destructor causes undefined behaviour (and fortunately VC seems
to indicate this with a linker error, which is as nice as undefined
behaviour gets).

Basically, destruction goes for typical implementations:

derived class destructor called
derived members destructed in reverse order of declaration
*vtable pointer updated to point to base class vtable*
base class destructor called
base class members destructed in reverse order of declaration

The emphasised line is what causes the problem for you - calling a
virtual function from a constructor or destructor calls the function
in the base object currently being constructed or destructed, not the
one in the most derived class.

Tom

Julián Albo

unread,
Aug 5, 2004, 1:26:14 PM8/5/04
to
Victor Bazarov wrote:

> Julián Albo wrote:
>> JKop wrote:
>>
>>
>>>> ~AutoDestructive()
>>>> {
>>>> this->CleanUp();
>>>> }
>>>
>>>The probem is with the above.
>>
>>
>> You can't safely call virtual functions inside contructors and
>> destructors, because the state of the object is not completely defined.
>
> Why do you say that? What is exactly not completely defined in the
> object's state? The base classes and members have been initialised
> when the body of the c-tor is entered, and are still around when
> the body of the d-tor is executing. What _else_ is there?

The subobject of the base class, yes. The complete object, not.

> Virtual functions are not linked dynamically from c-tors and d-tors
> and instead are statically linked, but that, again, should not be
> a problem: the 'CleanUp' member of 'AutoDestructive' should only
> require base classes and members for its functionality, and not any
> of potential derived classes, no?

The CleanUp member of AutoDestructive is pure virtual. You can't call it
without a complete object of a derived class,

--
Salu2

Victor Bazarov

unread,
Aug 5, 2004, 1:37:47 PM8/5/04
to
Julián Albo wrote:
> Victor Bazarov wrote:
>
>
>>Julián Albo wrote:
>>
>>>JKop wrote:
>>>
>>>
>>>
>>>>> ~AutoDestructive()
>>>>> {
>>>>> this->CleanUp();
>>>>> }
>>>>
>>>>The probem is with the above.
>>>
>>>
>>>You can't safely call virtual functions inside contructors and
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

>>>destructors, because the state of the object is not completely defined.

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Do you see the word "pure" there?

>>
>>Why do you say that? What is exactly not completely defined in the
>>object's state? The base classes and members have been initialised
>>when the body of the c-tor is entered, and are still around when
>>the body of the d-tor is executing. What _else_ is there?
>
>
> The subobject of the base class, yes. The complete object, not.

Really? What is "the complete object"? Why is it "not"?

#include <iostream>

class SomeClass : public SomeBase, SomeBase2 {
int somedata;
public:
virtual void foo() {
std::cout << somedata;
}
~SomeClass() {
// what's not defined here?
// why is it unsafe to call 'foo'?
}
};

Please elaborate on "complete object" using the example above.

>
>
>>Virtual functions are not linked dynamically from c-tors and d-tors
>>and instead are statically linked, but that, again, should not be
>>a problem: the 'CleanUp' member of 'AutoDestructive' should only
>>require base classes and members for its functionality, and not any
>>of potential derived classes, no?
>
>
> The CleanUp member of AutoDestructive is pure virtual. You can't call it
> without a complete object of a derived class,

Yes, but calling a pure virtual function that causes undefined behaviour.
Not "unsafe", just "undefined". Besides, in your posting there was just
a statement that a virtual function cannot be "safely" called.

Care to post a correction to it?

Victor

JKop

unread,
Aug 5, 2004, 2:07:34 PM8/5/04
to

>> ~AutoDestructive()
>> {
>> this->CleanUp();
>
> There's your problem - calling a virtual function from a destructor
> doesn't do what you think it does. Calling a pure virtual function
> from a destructor causes undefined behaviour (and fortunately VC seems
> to indicate this with a linker error, which is as nice as undefined
> behaviour gets).
>
> Basically, destruction goes for typical implementations:
>
> derived class destructor called
> derived members destructed in reverse order of declaration
> *vtable pointer updated to point to base class vtable*
> base class destructor called
> base class members destructed in reverse order of declaration
>
> The emphasised line is what causes the problem for you - calling a
> virtual function from a constructor or destructor calls the function
> in the base object currently being constructed or destructed, not the
> one in the most derived class.
>
> Tom


Okay, is there anything wrong with the following though? It compiles for me:


class Base
{
public:

virtual void Monkey() = 0;

void HelperMonkey()
{
this->Monkey();
}

~Base()
{
HelperMonkey();
}
};

class Derived : public Base
{
public:

virtual void Monkey()
{

}
};

int main()
{
Derived checker;
}


-JKop

Julián Albo

unread,
Aug 5, 2004, 2:14:30 PM8/5/04
to
Victor Bazarov wrote:

>>>>
>>>>You can't safely call virtual functions inside contructors and
> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>
>>>>destructors, because the state of the object is not completely defined.
> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>
> Do you see the word "pure" there?

I was answering the OP question in his context, not writing a book. Thanks
for yor review, anyway.

--
Salu2

JKop

unread,
Aug 5, 2004, 2:16:55 PM8/5/04
to

It doesn't work. I ran the program and the Win OS printed:

pure virtual method called

This application has requested the Runtime to terminate it
in an unusual way.
Please contact the application's support team for more
information.


A nice bit of help there!


I'm off to redesign it.


-JKop

JKop

unread,
Aug 5, 2004, 6:23:38 PM8/5/04
to
I'm very nearly finished my template. It's "const" compatible, if you know
what I mean, eg. you can define a both a non-const object and a const object
with it and they'll behave as expected. Here's how you use it: First you
make a function like so

void CleanUp_HKEY(HKEY& hkey, bool& to_be_closed)
{
if (to_be_closed)
{
RegCloseKey(hkey);
to_be_closed = false;
}
}

And you define your object as so:

AutoDestructive<HKEY,CleanUp_HKEY> my_key;

To gain access to the actual HKEY object, you use function notation, eg. to
pass it to a function that takes a HKEY:

SomeFunct(my_key());

Or even just setting it yourself:

my_key() = HKEY();


It's not quite finished yet, I don't know if I've to make it volatile
compatible. Also, I need to deal with the constructors of the type I'm
protecting. Anyway, here it is:


//auto_destruct.hpp : assume that there's inclusion guards

template<class T, void (&CleanUp)(T&,bool&)>
class AutoDestructive
{
private:

AutoDestructive(const AutoDestructive& original) :
t(original.t),
t_ref(t),
to_be_auto_destructed(false) { }
AutoDestructive& operator=(const AutoDestructive&);
AutoDestructive* operator&();
const AutoDestructive* operator&() const;

private:

mutable T t;

public:

T& t_ref;

mutable bool to_be_auto_destructed;

AutoDestructive() : t(), t_ref(t), to_be_auto_destructed(false) {}

~AutoDestructive() { CleanUp(t,to_be_auto_destructed); }

T& operator()() { return t_ref; }
const T& operator()() const { return t_ref; }
};


And here's a sample usage:

// blah.cpp

#include <auto_destruct.hpp>

void CleanUp_HKEY(HKEY& hkey, bool& to_be_closed)
{
if (to_be_closed)
{
RegCloseKey(hkey);

to_be_closed = false;
}
}

void CleanUp_HDLL(HDLL& hdll, bool& to_be_freed)
{
if (to_be_freed)
{
FreeLibrary(hdll);
to_be_freed = false;
}
}

typedef AutoDestructive<HKEY,CleanUp_HKEY> AutoDestructive_HKEY;

typedef AutoDestructive<HDLL,CleanUp_HDLL> AutoDestructive_HDLL;

int main()
{
AutoDestructive_HKEY hkey;

LONG error_code = RegOpenKeyEx(HKEY_CLASSES_ROOT,
"*",
0,
KEY_QUERY_VALUE|KEY_SET_VALUE,

&hkey());

if (error_code == ERROR_SUCCESS)
{
hkey.to_be_auto_destructed = true;
}

//Some suff, exception might get thrown here

RegCloseKey(hkey());

hkey.to_be_auto_destructed = false;


AutoDestructive_HDLL hdll;

hdll() = LoadLibrary("Monkey");
if(hdll) hdll.to_be_auto_destructed = true;

//some stuff, exception may be throw

FreeLibrary(hdll());
hdll.to_be_auto_destructed = false;
}


-JKop

JKop

unread,
Aug 5, 2004, 6:29:10 PM8/5/04
to
Here it is again, without the typos. This one compiles:

#include <iostream>
#include <windows.h>

template<class T, void (&CleanUp)(T&,bool&)>
class AutoDestructive
{
private:

AutoDestructive(const AutoDestructive&);


AutoDestructive& operator=(const AutoDestructive&);
AutoDestructive* operator&();
const AutoDestructive* operator&() const;

private:

mutable T t;

public:

T& t_ref;

mutable bool to_be_auto_destructed;

AutoDestructive() : t(), t_ref(t),
to_be_auto_destructed(false) {}
~AutoDestructive() { CleanUp(t,to_be_auto_destructed);
}

T& operator()() { return t_ref; }
const T& operator()() const { return t_ref; }
};

void CleanUp_HKEY(HKEY& hkey, bool& to_be_closed)


{
if (to_be_closed)
{
RegCloseKey(hkey);

to_be_closed = false;
}
}

void CleanUp_HDLL(HMODULE& hdll, bool& to_be_freed)


{
if (to_be_freed)
{
FreeLibrary(hdll);
to_be_freed = false;
}
}

typedef AutoDestructive<HKEY,CleanUp_HKEY>
AutoDestructive_HKEY;

typedef AutoDestructive<HMODULE,CleanUp_HDLL>
AutoDestructive_HMODULE;

int main()
{
AutoDestructive_HKEY hkey;

LONG error_code = RegOpenKeyEx(HKEY_CLASSES_ROOT,
"*",
0,
KEY_QUERY_VALUE|KEY_SET_VALUE,
&hkey());

if (error_code == ERROR_SUCCESS)
{
hkey.to_be_auto_destructed = true;
}

//Some suff, exception might get thrown here

RegCloseKey(hkey());

hkey.to_be_auto_destructed = false;


AutoDestructive_HMODULE hdll;

hdll() = LoadLibrary("Monkey");

if(hdll()) hdll.to_be_auto_destructed = true;

JKop

unread,
Aug 5, 2004, 6:44:08 PM8/5/04
to

The latest (sorry about the non-standard header):


#include <iostream>
#include <windows.h>

template<class T, void (&CleanUp)(T&,bool&)>
class AutoDestructive
{
private:

AutoDestructive(const AutoDestructive&);
AutoDestructive& operator=(const AutoDestructive&);
AutoDestructive* operator&();
const AutoDestructive* operator&() const;

private:

mutable T t;

public:

T& t_ref;

mutable bool to_be_auto_destructed;

AutoDestructive() : t(), t_ref(t), to_be_auto_destructed(false) {}

~AutoDestructive() { ManualCleanUp(); }

T& operator()() { return t_ref; }
const T& operator()() const { return t_ref; }

void ManualCleanUp()
{
CleanUp(t,to_be_auto_destructed);
}
};

void CleanUp_HKEY(HKEY& hkey, bool& to_be_closed)
{
if (to_be_closed)
{
RegCloseKey(hkey);

to_be_closed = false;
}
}

void CleanUp_HDLL(HMODULE& hdll, bool& to_be_freed)
{
if (to_be_freed)
{
FreeLibrary(hdll);

to_be_freed = false;
}
}

typedef AutoDestructive<HKEY,CleanUp_HKEY> AutoDestructive_HKEY;

typedef AutoDestructive<HMODULE,CleanUp_HDLL> AutoDestructive_HMODULE;


int main()
{
AutoDestructive_HKEY hkey;

LONG error_code = RegOpenKeyEx(HKEY_CLASSES_ROOT,
"*",
0,
KEY_QUERY_VALUE|KEY_SET_VALUE,
&hkey());

if (error_code == ERROR_SUCCESS)
{
hkey.to_be_auto_destructed = true;
}

//Some suff, exception might get thrown here

hkey.ManualCleanUp();

AutoDestructive_HMODULE hdll;

hdll() = LoadLibrary("Monkey");
if(hdll()) hdll.to_be_auto_destructed = true;

//some stuff, exception may be throw

hdll.ManualCleanUp();
}


Next step is contructors...

-JKop

Alf P. Steinbach

unread,
Aug 5, 2004, 6:47:57 PM8/5/04
to
* JKop:

>
> Here it is again, without the typos. This one compiles:
>
> #include <iostream>
> #include <windows.h>

Although platform-specific: for C++ you need to #define STRICT and
NOMINMAX before including <windows.h>.

Above gives you declarations for C, not for C++ (much of it works
but some of it doesn't).



> template<class T, void (&CleanUp)(T&,bool&)>

You might want to take a look at boost::shared_ptr as well as
ScopeGuard.

More concrete: passing a bool "should something be done or not"
argument to the CleanUp function is not a good idea.

Instead the class should make that decision and call the CleanUp
function or not.


> class AutoDestructive
> {
> private:
>
> AutoDestructive(const AutoDestructive&);
> AutoDestructive& operator=(const AutoDestructive&);

OK.

> AutoDestructive* operator&();
> const AutoDestructive* operator&() const;

AFAICS these are unnecessary.


> private:
>
> mutable T t;

Guideline: use 'mutable' only (and if at all) for data members
that can be changed without changing externally observable state.

>
> public:
>
> T& t_ref;
>
> mutable bool to_be_auto_destructed;

See previous comment.

Here it's more acute: don't use 'public' data members for non-trivial
class, and absolutely not 'mutable' -- when you declare an object
const you don't do that because you're OK with that object changing.


> AutoDestructive() : t(), t_ref(t),
> to_be_auto_destructed(false) {}

Constructor should initialize data members to useful values,
not a default null-state.


> ~AutoDestructive() { CleanUp(t,to_be_auto_destructed);
> }
>
> T& operator()() { return t_ref; }
> const T& operator()() const { return t_ref; }
> };

Here's an idea that has worked for me many times:

Try _first_ to encapsulate the kind of object or handle in question
(here: registry handle) in a class that provides a complete operation
set -- no "native" stuff in the external interface.

Only then see if you can lift out critical parts as e.g. a reusable
AutoDestructive class.

Andre Kostur

unread,
Aug 5, 2004, 6:41:16 PM8/5/04
to
JKop <NU...@NULL.NULL> wrote in news:WwyQc.24473$Z14....@news.indigo.ie:

> Here it is again, without the typos. This one compiles:
>

[snip code example]

Yuck. Too much manual intervention. The class should be able to manage
whether it should "destroy" the object or not, not the user (OK, not
directly). Model it after std::auto_ptr. Or better yet, get a hold of the
Aug 2001 issue of the C/C++ User's Journal and read the "Generalizing the
Concepts Behind auto_ptr" article by Cristian Vlasceanu.

Wagner Bruna

unread,
Aug 5, 2004, 8:03:05 PM8/5/04
to
Hi,

JKop <NU...@NULL.NULL> wrote in message news:<rQuQc.24448$Z14....@news.indigo.ie>...


> It doesn't work. I ran the program and the Win OS printed:
>
> pure virtual method called
>

> (...)

There is a section on the FAQ about calling virtual functions from
constructors of the base class (same problem with destructors):

http://www.parashift.com/c++-faq-lite/

Hope that helps,
Wagner

David Rubin

unread,
Aug 7, 2004, 12:15:42 AM8/7/04
to
JKop <NU...@NULL.NULL> wrote in message news:<YKyQc.24474$Z14....@news.indigo.ie>...

> The latest (sorry about the non-standard header):
>
>
> #include <iostream>
> #include <windows.h>
>
> template<class T, void (&CleanUp)(T&,bool&)>
^^^^^^^^^^^^^^^^^^^^^^^^^

If you make this a (regular?) parameter (e.g., to the contructor)
rather than a template parameter, you will only instantiate the
template for every T, not for every (T,CleanUp) pair.

> class AutoDestructive
> {
> private:
>
> AutoDestructive(const AutoDestructive&);
> AutoDestructive& operator=(const AutoDestructive&);
> AutoDestructive* operator&();
> const AutoDestructive* operator&() const;
>
> private:
>
> mutable T t;
>
> public:
>
> T& t_ref;
>
> mutable bool to_be_auto_destructed;
>
> AutoDestructive() : t(), t_ref(t), to_be_auto_destructed(false) {}
>
> ~AutoDestructive() { ManualCleanUp(); }
>
> T& operator()() { return t_ref; }
> const T& operator()() const { return t_ref; }
>
> void ManualCleanUp()
> {
> CleanUp(t,to_be_auto_destructed);
> }
> };

IIRC, someone pointed out in an earlier version of this code that
using 'to_be_auto_destucted' is very unnatural, especially since you
require a free function which takes this parameter. The class should
control whether or not the cleanup occurs. Here is an alternative:

template <typename T>
class my_RefProctor {
// This class provides exception safety for an
// object of type T, held by reference.

public:
typedef void (*Callback)(T&); // cleanup callback

private:
T& d_object; // proctored object (held)
Callback d_callback; // user-specified callback
int d_cleanup; // true if cleanup should be done

private:
// not implemented
my_RefProctor(const my_RefProctor&);
my_RefProctor& operator=(const my_RefProctor&);

public:
my_RefProctor(T& object, Callback callback);
// Create a 'my_RefProctor' object to provide exception
// safety for the specified 'object' using the specified
// 'callback' function. The behavior is undefined unless
// 'object' and 'callback' are valid objects.

~my_RefProctor();
// Perform some cleanup for the proctored object by
// calling the stored callback function if 'd_cleanup'
// is 'true'.

void release();
// Release the maintained object from this proctor; no
// cleanup will be done.
};

> void CleanUp_HKEY(HKEY& hkey, bool& to_be_closed)
> {
> if (to_be_closed)
> {
> RegCloseKey(hkey);
> to_be_closed = false;
> }
> }
>

> typedef AutoDestructive<HKEY,CleanUp_HKEY> AutoDestructive_HKEY;

[snip]


> int main()
> {
> AutoDestructive_HKEY hkey;
>
> LONG error_code = RegOpenKeyEx(HKEY_CLASSES_ROOT,
> "*",
> 0,
> KEY_QUERY_VALUE|KEY_SET_VALUE,
> &hkey());
>
> if (error_code == ERROR_SUCCESS)
> {
> hkey.to_be_auto_destructed = true;
> }
>
> //Some suff, exception might get thrown here
>
> hkey.ManualCleanUp();

[...]
> }

This becomes

int main()
{
HKEY hkey;

LONG rc = RegOpenKeyEx(HKEY_CLASSES_ROOT,
"*", 0, KEY_QUERY_VALUE|KEY_SET_VALUE, hkey);
if (rc != ERROR_SUCCESS) {
// handle error
}

my_RefProctor<HKEY> proctor(hkey, RegCloseKey);

// Some stuff which may throw an exception

proctor.release(); // no exceptions

// further processing

RegCloseKey(hkey);
return 0;
}

This is much more natural as it does no assignments to public members,
does not use or require application-layer wrapper functions, and
conforms to common library usage rather than executing library APIs
through some indirect method.

HTH, /david

JKop

unread,
Aug 7, 2004, 5:34:34 AM8/7/04
to
David Rubin posted:

>> template<class T, void (&CleanUp)(T&,bool&)> ^^^^^^^^^^^^^^^^^^^^^^^^^
>
> If you make this a (regular?) parameter (e.g., to the contructor)
> rather than a template parameter, you will only instantiate the
> template for every T, not for every (T,CleanUp) pair.

I want to be able to do the following:

void CleanUp_HKEY_Normal(T& hkey,bool& to_be_closed) {}

void CleanUp_HKEY_Special(T& hkey,bool& to_be closed) {}

AutoDestructive<HKEY,CleanUp_HKEY_Normal> hkeynormal;
AutoDestructive<HKEY,CleanUp_HKEY_Special> hkeyspecial;


>> void ManualCleanUp()
>> {
>> CleanUp(t,to_be_auto_destructed); } };
>
> IIRC, someone pointed out in an earlier version of this code that
> using 'to_be_auto_destucted' is very unnatural, especially since you
> require a free function which takes this parameter. The class should
> control whether or not the cleanup occurs.


I made it so for added flexibility. Consider that in your CleanUp function,
even if the key isn't to be closed, you may want to close the entire
registry itself.


-JKop

0 new messages