RAII for value objects

B

Buster

Markus said:
I try to express the RAII design pattern/idiom in C++ language terms.

I think that the virtual methods are needed so that they can be
overloaded.

"Overridden". You're right, I think, but a real example would help to
convince me. I can't think of a situation where you know at compile time
that there needs to be some RAII done, but you don't know exactly what
the resource is going to be until run time. Enlighten me?
Do you know a wording which would achieve a similar effect with code
generation by the templates?

Sure. Check out the standard C++ library algorithms. Here's a simple
example. Notice there's no code at all corresponding to the Concept.

// Concept: Transmogrifiable.
// If x is an object of a type T which models
// the Transmogrifiable concept, then the
// following expression is valid:
//
// x.transmogrify ();
// Postcondition: x is transmogrified.

// Algorithm: transmogrify_n
// If x is an object of a type T which models
// the Transmogrifiable concept, and n is an integer,
// then the following expression is valid:
//
// transmogrify_n (x, n);
// Postcondition: x is transmogrified n times.

template <typename T, typename Integer>
void transmogrify_n (T & x, Integer n)
{
for (Integer i (0); i < n; ++ i)
{
x.transmogrify ();
}
}
The difference between RAII2 and RAII3 is this:
- RAII2 uses delegation over the variable "_m_p". A method pair can be
selected at run time.
- RAII3 uses private inheritance to specify construction and
destruction as an implementation detail that should not be visible to
the clients of the class. The selction is done at compile time.



The adapter or wrapper is needed if your class X offers two related
methods (a specific pair) to which calls should be encapsulated by the
RAII technique.

Sure. But why not use the constructor and destructor?
The methods "begin" and "end" should be kept private as far as
possible because they play their role as a virtual constructor and
destructor.

Ah-hah. I suppose so.
Your "X_MP_adapter" sketch looks like an approach that is covered by
the class "std::auto_ptr" already.
I am interested in the RAII handling of data structures that are not
concerned with a managed pointer.



Would you like to look at an other specific implementation?
- http://zthread.sourceforge.net/html/classZThread_1_1Guard.html
- http://zthread.sourceforge.net/html/classZThread_1_1LockedScope.html

I see also use cases besides synchronization in the document "Memory
model for multithreaded C++".
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1680.pdf

I would like to use the class "std::atomic_int" with my suggestion.
Such an integer value will be reset (e.g. 0 as default) in the
destructor.
I guess that the class "std::msync" will become another interesting
candidate for some applications.

If it works, go for it. I think you're wasting your time trying to solve
a very general problem, when the solution is easy (write a class with a
constructor and destructor) for any particular instance of the problem.
 
M

Markus Elfring

If it works, go for it. I think you're wasting your time trying to solve
a very general problem, when the solution is easy (write a class with a
constructor and destructor) for any particular instance of the problem.

How do you think about it if a template would generate this class for
you?
I try to solve the "particular" or "general" use case to rename two
methods to "constructor and destructor".

The template instantiation can be performed at the place where the
normal attributes and variables are initialized at the beginning of a
scope and can make the use of RAII objects easy.
I guess that a lot of developers can benefit from a centralized
implementation for this aspect. Would you like to avoid any
repetitions to write your own "guards", "state savers" or "resetters"?

How many library designers provide such objects or interfaces to deal
with function and methods pairs for their users as a service?
There are some libraries that offer this kind of encapsulation while
others need still a couple of work and development to reach this
level. Which methods were not envisioned to be used like a
constructor/destructor combination?

Regards,
Markus
 
P

Philipp Bachmann

How do you think about this design sketch?
template<typename X, const initial, const final>
class RAII
{
public:
RAII() : _value(initial) {}
RAII(const X& x) : _value(x) {}
~RAII () { _value = final; }

X& operator=(const X& x) { if (this != &x) { _value = x._value }
return *this; }
X& operator*() const { return _value; }
X* operator->() const throw() { return &_value; }
// Do you need more methods?

private:
X _value;
}

I won't let "RAII<>" operate on a copy of "x". Your design makes the
destructor almost useless, because nobody cares about the change of
"RAII<>::_value" just before it's being destructed.

So let me propose the following changes:

template<typename X, const initial, const final> class RAII
{
public:
RAII(X& x) : _value(x) { _value=initial; }
[...]
private:
X &_value;
RAII(const RAII &);
RAII &operator=(const RAII &);
};

And don't forget to forbid copy and assignment as indicated above or
to implement the move semantics instead as "std::auto_ptr<>" does.

My motivation to experiment with similar, general templates to facilitate implementation
of RAII guards was to reduce the work to always reimplement the move stuff
mentioned the paragraph before - this is a lot of work (I exactly followed the
"std::auto_ptr<>" approach with its embedded helper class).

But I'm not sure any more whether the use of such a very general template is really
simpler than to implement RAII every time you need it, because of the following reasons:

1. the template has to be instantiated with functors / function pointers which carry the
information what to do on construction and what on destruction of the guard, and these
functors / functions have to be written for each type you want to build a RAII guard for.

2. The constructors of your template guard can't match all constructors your users
want to have for their special RAII instantiations. E.g. for locks the above suffices, but
with a guard which temporarily alters "iostate" on a stream you certainly want an additional
argument for the "iostate" the stream is set to during the lifetime of the guard - you have
to put such arguments into the constructor of your functor then, that is executed on construction
of the RAII template, which complicated usage i.m.o.

3. As Pavel already pointed out, strong exception safety of such a template can be a
problem - either your template doesn't solve it, or additional overhead is introduced,
basically due to the fact that if you supply the function to be called on destruction by
means of a functor (which in general is not guaranteed to be "throw()" on assignment), then you
have to go for a PIMPL implementation of your guard template to make assignment
strongly exception safe.

Cheers,
Philipp.
 
M

Markus Elfring

Is there some specific problem you're trying to solve? If I've missed
your point, it might help me to understand better if I saw some real
code.

The section "3.5 RAII Types" and the chapter "6 Scoping Classes" of the book
"Imperfect C++ - Practical Solutions for Real-Life Programming" (ISBN
0-321-22877-4) by Matthew Wilson contain more
variations on the discussed topic.

Regards,
Markus
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,767
Messages
2,569,572
Members
45,046
Latest member
Gavizuho

Latest Threads

Top