Guarantee initialized legacy variables
Desire:
To ensure that all legacy variables, whether local, global, or struct
and class members, be initialized to zero / null / false if not
explicitly initialized.
Quick Background:
I am in charge of a good deal of C code written in the 80s which is of
the style int x; ....many lines later... x = 1; ... many more lines
later... if (x < 3) ... and so on.
Motivation:
When maintanance programmers update the legacy C function that has the
declaration of x (or any variable) at the top of the function, then
its initialization is a complex bit of code that may or may not
initialize x properly for any given code-path, to a final chunk of
code which actually depends on the value of said variable to decide
what to do, it becomes increasingly likely that the maintanance
programmer will change the code in such a way that x is no longer
necessarily initialized for one or more code-paths through the
function, leading to random (or worse) undefined behaviour.
Approach:
I have a template: Initialised<T>, and a set of partial
specializations of Initialised<T>.
T must be a PODT, or a sturct of PODTs, or array of PODTs. If T were
not a plain old data type, then it would have its own ctor, and
wouldn't need a templated wrapper to ensure its proper initialization.
An Initialised<T> should be a drop-in replacement for a given type T.
All code which refers to iT (iT is an instance of type T) should not
need to change whatsoever when I change iT to iIT (iIT is an instance
of an Initialised<T>).
Problems:
P(1): I have a possible solution, but would like community feedback on
a technique for allowing an Initialised<T*> to be assigned NULL (I
certainly wish to allow this, its just a matter of what technique is
best).
P(2): I am unable to discern how to partially specialize
Initialised<T> for T being an array of PODTs.
P(3): I am unable to discern how to partially specialize
Initialised<T> for T being a struct of PODTs.
Lets start with the basic definition of Initialised<T>:
template <typename T>
struct Initialised
{
// default valued construction
Initialised() : m_value(0) { }
// implicit valued construction (auto-conversion)
template <typename U> Initialised(const U & rhs) : m_value(rhs) { }
// assignment
template <typename U> T & operator = (const U & rhs) { if
((void*)&m_value != (void*)&rhs) m_value = rhs; return *this; }
// implicit conversion to the underlying type
operator T & () { return m_value; }
operator const T & () const { return m_value; }
// the data
T m_value;
};
So, as you can see - very simple. For any T that can be initialized
to literal 0, this works. It provides automatic conversion from and
to its underlying type, T, and hence is a T for most intents and
purposes.
Here is the partial specialization for raw pointers:
template <typename T>
struct Initialised<T*>
{
// default valued construction
Initialised() : m_value(NULL) { }
// valued construction (auto-conversion)
template <typename U> Initialised(const U * rhs) : m_value(rhs) { }
// assignment
template <typename U> T* & operator = (U * rhs) { if (m_value != rhs)
m_value = rhs; return *this; }
template <typename U> T* & operator = (const U * rhs) { if (m_value !
= rhs) m_value = rhs; return *this; }
// implicit conversion to underlying type
operator T * & () { return m_value; }
operator const T * & () const { return m_value; }
// pointer semantics
const T * operator -> () const { return m_value; }
T * operator -> () { return m_value; }
const T & operator * () const { return *m_value; }
T & operator * () { return *m_value; }
// the pointer
T * m_value;
// allow null assignment
private:
class Dummy {};
public:
// amazingly, this appears to work - at least under VS2005.
// the compiler determins that Initialized<T*> p = NULL matches the
following definition
T * & operator = (Dummy * value) { m_value = NULL; ASSERT(value ==
NULL); return *this; }
};
Of my original questions / problems, this specialization contains P(1)
- a technique for allowing an iIT* to be assigned NULL. I have tested
this under VS2005, and it compiles and generates the right code. For
any iIT* = compatible pointer, it selects one of the more appropriate
operator=() members, and for = NULL it selects the operator =
(DUMMY*). The ASSERT(value == NULL) seems to be unnecessary, as the
argument deduction never resolves to operator = (DUMMY*) for non-null
argument. In fact, this seems to achieve my exact wishes for an
Initialized<T*> - to allow it to be used as if it were the underlying
T* without modification to the user-code.
Which brings me to question / problem P(2) - is there a syntax that
will allow me to do a partial specialization for an array of T?
I have tried:
template <typename T, size_t count>
struct Initialised<T(&)[count]>
{
Initialised() { Zero(m_value); }
T m_value[count];
};
which itself doesn't error - but any use of Initialised<???> fails to
ever generate a instance of the above specialization.
For example:
struct S
{
Initialised<int[50]> m_ints1; // ERROR: cannot specify explicit
initializer for arrays
// the compiler tries to instanciate the most generic
version of Initialised<>
// and fails to instanciate the partial
specialization for arrays!
Initialised<int,50> m_ints2; // ERROR: too many template arguments
};
S neither of the above quite give me what I want. I can certainly
declare:
struct S
{
Initialised<int> m_ints3[50]; // success
};
But it would be more efficient if I could use a partial
specialization, as that would allow me to use a much faster memory
overwrite, instead of calling the default ctor on each and every int
in m_ints3.
Finally, I wonder if there is a way to do a partial specialization for
a struct of PODTs? Take the following class as a basic example of
what I'm trying for - again, something that is a drop-in replacement
for its underlying type:
template <typename T>
struct Initialized : public T
{
// ensure that we aren't trying to overwrite a non-trivial class
BOOST_STATIC_ASSERT((boost::is_POD<T>::value));
// publish our underlying data type
typedef T DataType;
// default (initialized) ctor
Initialized() { Reset(); }
// reset
void Reset() { Zero((T&)(*this)); }
// auto-conversion ctor
template <typename OtherType> Initialized(const OtherType & t) : T(t)
{ }
// auto-conversion assignment
template <typename OtherType> Initialized<DataType> & operator =
(const OtherType & t) { *this = t; }
};
Although I haven't included the definitions of Zero(), you can assume
it does the obvious thing (memset).
This last template is clearly not a specialization of Initialised<>,
but hopefully this gets the idea across of what I'm going towards - a
struct which can be a drop-in replacement for legacy code that
declares various sturcts - guaranteeing that the variables will always
be initialized before use.
Thanks for any feedback you may have to offer.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]