T O P I C R E V I E W |
Mordachai |
Posted - Apr 13 2010 : 3:39:47 PM I have a common situation where VA (10.6.1819.0 - 10/04/06) can't parse it quite right:
template <class X>
class IX : public X
{
... stuff...
};
struct Gump
{
int i;
double d;
}
void main()
{
IX<Gump> gumpy;
gumpy.i = 1; // <- VA flags member "i" as bad, and can't figure out where it comes from...
} The above is of course very rough, but its a common programming idiom to use public or protected inheritance in templates to wrapper a class and give it some additional behaviors in my code... |
11 L A T E S T R E P L I E S (Newest First) |
Mordachai |
Posted - Jul 16 2013 : 09:42:28 AM Thanks for the explanation! Sorry to be very, very late to respond. :(
I used to do stuff like that all of the time in Objective-C, Smalltalk and Java. However, in C++ I tend to avoid such and try to do things using templates and find ways that don't involve macros. However, the approach is a good one, IMHO. It's just the C++ paradigm of avoiding macros to get the job done that makes it a hard sell, I expect. :) |
accord |
Posted - Jan 18 2011 : 01:17:33 AM Glad we agree
quote: Originally posted by Mordachai
Anyway, philosophy aside, I'd be curious to see what you mean by "no macro or template magic"? What exactly was it you did?
I've written a program that runs in the pre-build event. It parses the modified header files in the specified directories, recognizes classes, methods and variables in them and generates a separate file to generates functions for automatic initialization, save-load or populating property window for a class, automatically setting class owner, etc. I include the generated file into the cpp. The function declarations are generated by the macro which I use to flag the class for parsing.
I control the work of my parser through keywords that are essentially empty macros, so I can skip initialization or can use other value than zero, etc, etc.
class cTest
{
REFLECTION; // this class will be parsed
int Apple; // this will be initialized to zero
int Banana; INIT(5); // this will be initialized to 5
};
But as you can see it requires additional work on the parser side, but I like this approach because: - you need to write the variable once to declare, init and serialize it - it is refactoring friendly since you are still using native C++ so VA refactorings will work on them and you need to move only 1 line if you decide to move a variable or function to an other class - you don't need to code a line to save complex class hierarchies (!) It saves time and avoid human errors. - I can generate type-dependent code
Not everyone like the idea because it is being very unusual for them, but I like using it
If I need to place a button on the property window, I just write "BUTTTON;" after the function name, and magic happens :) I don't need to write a line to register call-backs, to place buttons on the GUI, etc. This approach is ideal for problems like research where you want to try a lot of ideas without additional GUI, serialization, etc. coding, or if you want reliable serialization of the whole program, like in game development. The C++ class itself is the GUI definition and the parser does the rest.
OK, that was *long* |
Mordachai |
Posted - Jan 10 2011 : 12:09:35 PM @Accord - thanks for taking the time to dig into this! I figured it was a small possibility at best!
I'm also of the "initialization benefits outweigh any performance issues 99.9999% of time" camp.
All too often I've had to debug somebody's work because it works in debug mode flawlessly, but has intermittent problems in release builds! Ugh!
The .000001 percent of the time that the performance is really critical here... well, it's probably a figment of one's imagination, but even so - restrict the use of uninitialized data to those .000001 percent of the code, and make damn sure you're doing it right.
---
Anyway, philosophy aside, I'd be curious to see what you mean by "no macro or template magic"? What exactly was it you did? |
accord |
Posted - Jan 07 2011 : 7:36:25 PM Sorry for the late answer, I missed your post.
I was able to reproduce the problem using a simplified example, thank you for posting the example code. I have reactivated the case and updated it with the new information.
Regarding alternatives, the most obvious is to use different classes for different types But it is not really an alternative, but rather a simplification Different classes should work with VA (like Initialized_struct, etc.)
Personally, I had written a header parser for my home project, that generates code to initialize, serialize, etc. the members for me. This way I was able to distinguish different types without any macro or template "magic" But it isn't a practical option for everyone, since it requires more work and requires a different approach to the problem and some (i.e. workmates) may be opposed to such ideas In fact, some are opposed to the sole idea that class members should be initialized (with zeros or given value) automatically. But aside from some very special situations, it doesn't cause slowdown, and such a feature can be omitted where required. Ok, it was long |
Mordachai |
Posted - Dec 20 2010 : 10:45:51 AM I'm using VS2008 (latest patches) and VA 10.6.1837.0 built 2010.11.19
However, I concur, in the simple case I am presenting, VA does get it right (now). And I'm not sure that you guys are going to be willing to fix the full case that I use.
On the offchance that you guys have the time & interest, here's the full blown example that actually fails:
The key file is "Initialize.h":
//////////////////////////////////////////////////////////////
// Raw Memory Initialization Helpers
//
// Provides:
// Zero(x) where x is any type, and is completely overwritten by null bytes (0).
// Initialized<T> x; where T is any legacy type, and it is completely null'd before use.
//
// History:
//
// User UncleBen of stackoverflow.com and I tried to come up with
// an improved, integrated approach to Initialized<>
// http://stackoverflow.com/questions/2238197/how-do-i-specialize-a-templated-class-for-data-type-classification
//
// In the end, there are simply some limitations to using this
// approach, which makes it... limited.
//
// For the time being, I have integrated them as best I can
// However, it is best to simply use this feature
// for legacy structs and not much else.
//
// So I recommend stacked based usage for legacy structs in particular:
// Initialized<BITMAP> bm;
//
// And perhaps some very limited use legacy arrays:
// Initialized<TCHAR[MAX_PATH]> filename;
//
// But I would discourage their use for member variables:
// Initialized<size_t> m_cbLength;
// ...as this can defeat template type deduction for such types
// (its not a size_t, but an Initialized<size_t> - different types!)
//
//////////////////////////////////////////////////////////////
#pragma once
// boost
#include <boost/static_assert.hpp>
#include <boost/type_traits.hpp>
// zero the memory space of a given PODS or native array
template <typename T>
void Zero(T & object, int zero_value = 0)
{
// ensure that we aren't trying to overwrite a non-trivial class
BOOST_STATIC_ASSERT((boost::is_POD<T>::value));
// make zeroing out a raw pointer illegal
BOOST_STATIC_ASSERT(!(boost::is_pointer<T>::value));
::memset(&object, zero_value, sizeof(object));
}
// version for simple arrays
template <typename T, size_t N>
void Zero(T (&object)[N], int zero_value = 0)
{
// ensure that we aren't trying to overwrite a non-trivial class
BOOST_STATIC_ASSERT((boost::is_POD<T>::value));
::memset(&object, zero_value, sizeof(object));
}
// version for dynamically allocated memory
template <typename T>
void Zero(T * object, size_t size, int zero_value = 0)
{
// ensure that we aren't trying to overwrite a non-trivial class
BOOST_STATIC_ASSERT((boost::is_POD<T>::value));
::memset(object, zero_value, size);
}
//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Initialized for non-inheritable types
// usage: Initialized<int> i;
template <typename T, bool SCALAR = boost::is_scalar<T>::value>
struct Initialized
{
// ensure that we aren't trying to overwrite a non-trivial class
BOOST_STATIC_ASSERT((boost::is_scalar<T>::value));
// the data
T m_value;
// default valued construction
Initialized() : m_value() { }
// implicit valued construction (auto-conversion)
template <typename U> Initialized(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; }
// zero method for this type
void _zero() { m_value = T(); }
};
//////////////////////////////////////////////////////////////////////////
// Initialized for inheritable types (e.g. structs)
// usage: Initialized<RECT> r;
template <typename T>
struct Initialized<T, false> : public T
{
// ensure that we aren't trying to overwrite a non-trivial class
BOOST_STATIC_ASSERT((boost::is_POD<T>::value));
// default ctor
Initialized() : T() { }
// auto-conversion ctor
template <typename OtherType> Initialized(const OtherType & value) : T(value) { }
// auto-conversion assignment
template <typename OtherType> Initialized & operator = (const OtherType & value) { static_cast<T&>(*this) = value; return *this; }
// zero method for this type
void _zero() { Zero((T&)(*this)); }
};
//////////////////////////////////////////////////////////////////////////
// Initialized arrays of simple types
// usage: Initialized<char, MAXFILENAME> szFilename;
template <typename T, size_t N>
struct Initialized<T[N],false>
{
// ensure that we aren't trying to overwrite a non-trivial class
BOOST_STATIC_ASSERT((boost::is_POD<T>::value));
// internal data
T m_array[N];
// default ctor
//Initialized() : m_array() { } // Generates a warning about new behavior. Its okay, but might as well not produce a warning.
Initialized() { Zero(m_array); }
// array access
operator T * () { return m_array; }
operator const T * () const { return m_array; }
// NOTE: All of the following techniques leads to ambiguity.
// Sadly, allowing the type to convert to ArrayType&, which IMO should
// make it fully "the same as it was without this wrapper" instead causes
// massive confusion for the compiler (it doesn't understand IA + offset, IA[offset], etc.)
// So in the end, the only thing that truly gives the most bang for the buck is T * conversion.
// This means that we cannot really use this for <char> very well, but that's a fairly small loss
// (there are lots of ways of handling character strings already)
// // automatic conversions
// operator ArrayType& () { return m_array; }
// operator const ArrayType& () const { return m_array; }
//
// T * operator + (long offset) { return m_array + offset; }
// const T * operator + (long offset) const { return m_array + offset; }
//
// T & operator [] (long offset) { return m_array[offset]; }
// const T & operator [] (long offset) const { return m_array[offset]; }
// metadata
size_t GetCapacity() const { return N; }
// zero method for this type
void _zero() { Zero(m_array); }
};
//////////////////////////////////////////////////////////////////////////
// Initialized for pointers to simple types
// usage: Initialized<char*> p;
// Please use a real smart pointer (such as std::auto_ptr or boost::shared_ptr)
// instead of this template whenever possible. This is really a stop-gap for legacy
// code, not a comprehensive solution.
template <typename T>
struct Initialized<T*, true>
{
// the pointer
T * m_pointer;
// default valued construction
Initialized() : m_pointer(NULL) { }
// valued construction (auto-conversion)
template <typename U> Initialized(const U * rhs) : m_pointer(rhs) { }
// assignment
template <typename U> T * & operator = (U * rhs) { if (m_pointer != rhs) m_pointer = rhs; return *this; }
template <typename U> T * & operator = (const U * rhs) { if (m_pointer != rhs) m_pointer = rhs; return *this; }
// implicit conversion to underlying type
operator T * & () { return m_pointer; }
operator const T * & () const { return m_pointer; }
// pointer semantics
const T * operator -> () const { return m_pointer; }
T * operator -> () { return m_pointer; }
const T & operator * () const { return *m_pointer; }
T & operator * () { return *m_pointer; }
// allow null assignment
private:
class Dummy {};
public:
// amazingly, this appears to work. The compiler finds that Initialized<T*> p = NULL to match the following definition
T * & operator = (Dummy * value) { m_pointer = NULL; ASSERT(value == NULL); return *this; }
// zero method for this type
void _zero() { m_pointer = NULL; }
};
//////////////////////////////////////////////////////////////////////////
// Uninitialized<T> requires that you explicitly initialize it when you delcare it (or in the owner object's ctor)
// it has no default ctor - so you *must* supply an initial value.
template <typename T>
struct Uninitialized
{
// valued initialization
Uninitialized(T initial_value) : m_value(initial_value) { }
// valued initialization from convertible types
template <typename U> Uninitialized(const U & initial_value) : m_value(initial_value) { }
// assignment
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; }
// the data
T m_value;
};
//////////////////////////////////////////////////////////////////////////
// Zero() overload for Initialized<>
//////////////////////////////////////////////////////////////////////////
// version for Initialized<T>
template <typename T, bool B>
void Zero(Initialized<T,B> & object)
{
object._zero();
}
As the documentation for Initialize.h indicates, you can use it for scalars, PODs, and built-in arrays:
Initialized<double> d; Initialized<BITMAPT> bm; typedef unsigned char eight_bytes[ 8 ]; Initialized<eight_bytes> e;
All of these compile, and the resulting variables are constructed with zero'd memory. But trying to get VA to decode the dereferences of an Initialized<STRUCT> fails - it underlines any such uses as errors, doesn't make appropriate suggestions, etc.
I expect that this is because VA can't actually deduce which specialization of Initialized<> is being invoked, and only one of the three is a subclass of T.
I hope that this clarifies the issue. It does for me. If you have any questions or suggestions for a better approach, I'm all ears.
One obvious change I could make would be to use different template names instead of partial specialization. However, I worked hard to make it work with partial specialization: that was a goal of mine. But maybe I'll let that go at some point... |
accord |
Posted - Dec 17 2010 : 7:00:37 PM Mordachai,
When I have put in the bug report, I was able to reproduce this problem. Now I tried again, but wasn't able to. I may do something differently. Can you please try creating a new win32 project and paste your example code into a cpp to see if you can reproduce the problem there? What IDE and operation system are you using? |
Mordachai |
Posted - Dec 16 2010 : 2:02:20 PM Thanks |
feline |
Posted - Dec 16 2010 : 1:02:00 PM Unfortunately no progress yet. Templates are sometimes difficult for our parser to correctly handle.
I have just increased the priority on this bug for you, hopefully this will help a bit. |
Mordachai |
Posted - Dec 14 2010 : 09:58:39 AM Has there been any movement on this issue? I've been through several updates, renewed licenses, etc., and this still seems to be a bug. |
Mordachai |
Posted - Apr 13 2010 : 4:49:36 PM Thanks for the quick reply. I'll keep subscribed for an update. ;) |
accord |
Posted - Apr 13 2010 : 4:20:05 PM I am seeing the same effect here. Thank you for the clear description.
case=42317 |
|
|