C++ and derived classes issue
Geert Janssens
geert.gnucash at kobaltwit.be
Sat Feb 13 12:52:41 EST 2016
(I don't know why but my mail got mangled while going through the intertubes.
I'll try to resend. Hopefully this time it comes through ok.)
On Friday 12 February 2016 17:15:41 John Ralls wrote:
> > On Feb 12, 2016, at 1:09 PM, Geert Janssens
> I think that what you really want to do is
>
> template <typename T> class GncTransProperty {
> public:
> GncTransProperty (T x, GncTransPropType p) : value{x}, property{p}
> {}; T get_value() const { return value; }
> GncTransPropType get_property() const { return property; }
>
> private:
> T value;
> GncTransPropType property;
> }
>
> That does everything you want in 10 lines and avoids the evil void*.
> Even better (at least until Transaction is converted to C++) would be
> to expand its property list to include all of the things that the
> importer will set. Then you can do even more, and get rid of the
> second switch statement:
>
> template <typename T> class GncTransProperty {
> public:
> GncTransProperty (T x, const char* p) : value{x}, property{p} {};
> void set_trans_property (Transaction *txn) const {
> GValue val = G_VALUE_INIT;
> g_value_init_from_instance (&val, G_POINTER(value));
> g_object_set_property (G_OBJECT (txn), property, &val);
> }
>
> private:
> T value;
> char *property;
> }
>
> You'll have to specialize the set_txn_property implementation for
> string, time64, and probably gnc_numeric since they're not GObjects.
> The specialization code looks like:
>
> template <> class GncTransProperty <std::string>{
> public:
> GncTransProperty (std::string& x, const char* p) : value{x},
> property{p} {}; void set_trans_property (Transaction *txn) const {
> GValue val = G_VALUE_INIT;
> g_value_set_string (&val, value.c_str());
> g_object_set_property (G_OBJECT (txn), property, &val);
> }
>
> private:
> std::string value;
> char* property;
> }
>
> With that and the changes in Transaction.c, the loop in
> GncTransPropertyList::to_trans() can be reduced to
> std::for_each(this->begin(), this->end(), [](auto
> elem){elem.set_trans_property();});
>
> BTW, except for the lambda and the curly-bracket initializers that's
> all C++98.
>
> Note that replacing get_type and get_value with set_trans_property
> would work with a class hierarchy, too, because it gets rid of the
> return-type problem with get_value. The template version runs
> slightly faster because the functions aren't called by vtable. The
> template version is also more flexible: The base template can handle
> any GObject type without specialization. It should also be possible
> once Transaction is changed to C++ to create a more type safe (never
> mind faster) way to handle properties to get rid of the
> specializations too because we should be able to say
> txn->set(property, value).
>
> Regards,
> John Ralls
Thanks for the detailed explanation John. It took me several readings to grok it.
I certainly like the idea of a set_trans_property to avoid different return types.
I had already attempted to use a template class instead, but ran into several issues (partly due
my long hiatus in c++ coding).
I'm redoing it now based on your suggestions. Current code is here:
https://github.com/gjanssens/gnucash/blob/cpp/src/import-export/csv-imp/gnc-trans-props.cpp[1]
and
https://github.com/gjanssens/gnucash/blob/cpp/src/import-export/csv-imp/gnc-trans-props.hpp[2]
I'm still having a couple of issues:
1. The first is mainly lack of knowledge. I note you write a constructor like this:
GncTransProperty (T x, const char* p) : value{x}, property{p} {};
I'm not familiar with the ": value{x}, property{p}" notation. Is that a specific C++ notation to
initialize private members ? Any caveats with it ? (I didn't come across an explanation in
Meyers book so far, so I assume it's standard C++). Does it have a name I can google ?
2. I also note you are using "T x" in the constructor, suggesting the GncTransProperty is
constructed from the properly typed value. However the importer so far only has a bunch of
strings to process. The idea was exactly to let the GncTransproperty instances handle type
conversion from the generic string to the proper internal type for later use. In this way that
knowledge is encapsulated in the object that needs to know. If I forgo this, the calling code has
to do the conversion, breaking the encapsulation and in fact removing much of the motivation
to create the GncTransProperty class in the first place. So I have for now kept the more generic
constructor
GncTransProperty (std::string val, GncTransPropType p)
This required specializations for each subtype of GncTransProperty we care about.
3. Just so you can follow I'll also note that I can't do this:
template <typename T> class GncTransProperty
...
std::vector<GncTransProperty> vctp;
Based on the following StackOverflow topic,
http://stackoverflow.com/questions/16527673/c-one-stdvector-containing-template-class-of-multiple-types
I have hence chosen to write a pure virtual interface class GncTransProperty which is the base
class of the GncTransPropImpl template class.
With this I can define
std::vector<std::unique_ptr<GncTransProperty>> vctp
which is the base class of GncTransPropertyList
and get on with my life.
4. Back to the real issues: if I would use g_object_set to set the date_posted for a transaction it
will call different functionality inside the Transaction code than the importer is doing currently.
g_object_set will eventually call xaccTransSetDatePostedTS where the importer has been calling
xaccTransSetDatePostedSecsNormalized. The latter also sets a KVP (with the gdate only) where
the former doesn't. I'm not sure if we really need that additional KVP, but don't like to pull too
many strings at once either.
More information about the gnucash-devel
mailing list