2

I am in a situation where I have to produce C++ code that is going to be used from the JNI. I have defined along with the java peers the right structure to pass data. However there is a legacy, hacky and super ugly structure that I was asked to use instead of the clean structure designed. So I have something like:

struct NiceStructure{
    int a;
    int b;
    bool c;
};


struct LegacyStructure{
    string a;
    double b;
    bool c;
    int old_dont_use;
    string type;
    int I_dont_remember_what_this_is_but_dont_touch_it;
    bool clip;
};

The NiceStructure is well suited for the operations I want to perform and the LegacyStructure can be hacked (like it has traditionally been) to do what I want to do.

The question is: Can I program a type agnostic function that can deal with both structures?

An example function would be:

vector<NiceStructure> do_something(vector<NiceStructure> array_of_values){      
    vector<NiceStructure> res;
    for (int i=0; i< array_of_values.size(); i++){
        if (array_of_values[i].a + array_of_values[i].b > 10 && array_of_values[i].c)
            res.push_back(array_of_values[i])
    }
    return res;
}

I should be able to change the type NiceStructure by LegacyStructure and work with either of them somehow.

Santi Peñate-Vera
  • 1,053
  • 4
  • 33
  • 68
  • 1
    Or build a conversion constructor for NiceStructure out of LegacyStructure or conversion operator in LegacyStructure for NiceStructure and make your methods accept NiceStructure. See https://stackoverflow.com/questions/1384007/conversion-constructor-vs-conversion-operator-precedence – Gerriet May 23 '17 at 06:23
  • I agree with the conversion. Actually it was my first option. However since the amount of structures is in the hundreds of thousands, I might not want to "waste" time in conversions. At least I was told so... – Santi Peñate-Vera May 23 '17 at 06:36
  • Would it be an option to make the legacy structures a private aggregate member of the new ones? – Aconcagua May 23 '17 at 06:39
  • Could you please post an example of what you mean? – Santi Peñate-Vera May 23 '17 at 06:43
  • @SantiPeñate-Vera `struct NiceStructure{private: LegacyStructure data;};` and your NiceStructure can operate directly on the data of the legacy structure... – Aconcagua May 23 '17 at 06:45
  • hummm, don't really like it but it could be a way – Santi Peñate-Vera May 23 '17 at 06:55
  • 1
    or make a member function that outputs a LegacyStructure from NiceStructure: LegacyStructure NiceStructure::GetLegacyStructure(). Then you only make one when needed. – Jason Lang May 23 '17 at 06:57

1 Answers1

5

Firstly, I would try to avoid writing code that works with both data types. Instead write converters to and from the Legacy format and quarantine LegacyStructure type as early as possible.

for example:

// Updates the LegacyStructure in place.
// This is our quarantine point.
void updateLegacy(LegacyStructure & s) {
  NiceStructure n = fromLegacy(s);
  updateNice(n);
  syncLegacy(s, n);
}

// The brains is here, and its free from Legacy cruft
void updateNice(NiceStructure &n) { ... }

// We need a way to get back all the changed fields from
// a NiceStructure back into a LegacyStrcuture
void syncLegacy(LegacyStructure &s, const NiceStructure & n);

// And a way to get our initial NiceStructure
NiceStructure fromLegacy(const LegacyStructure & s);

If end up needing to avoid copies of the data (For example you're working on a vector<LegacyStructure>), then there are a few options. I think the neatest of these is to actually use templates, but to do it through a traits type approach, to access each field as a common type.

// Base trait type - 
// Trying to use this on any type we haven't specialised on 
// will cause an error
template<typename T>
struct StructureTraits {};

// Specialisation of traits type for LegacyStructure
template<>
struct StructureTraits<LegacyStructure> {
  static int getA(const LegacyStructure &s) { return int_from_string(s.a); }
  static void setA(LegacyStructrue &s, int a) { s.a = string_from_int(s); }
  ...
};

// Specialisation of traits type for NiceStructure
template<>
struct StructureTraits<NiceStructure> {
   static int getA(const NiceStructure &s) { return s.a; }
   static void setA(LegacyStructure &s, int a) { s.a. = a; }
};

//TODO: Need some template hackery to provide const versions etc.

// This version will work for both types. 
template<typename T>
void updateStructureList( std::vector<T> & structures ) { 
  for( auto & x : structures ) {
     StructureTraits<T>::setA(x, StructureTraits<T>::getA(x) + 1 );
  }
}

Alternative approaches include, adding a common base type to both classes or creating an interface with simple wrappers for both classes.

Michael Anderson
  • 70,661
  • 7
  • 134
  • 187
  • That's going to be quite a lot of copying if you have a vector of `LegacyStructure`. – Martin Bonner supports Monica May 23 '17 at 16:50
  • @MartinBonner agreed. I'd still go with doing the copy until profiling shows that it is a bottleneck. But I've added an alternative traits based approach for when you really need to avoid those copies. – Michael Anderson May 24 '17 at 01:18
  • "Write clean code first and then optimize IF the profiler says you have a problem" - Hey! That's *my* line. (Lots of copying is an "obvious" bottleneck. So far I have twice managed to guess where the performance problem is; but I've only been doing this 35 years. I did once reject a very simple architecture because it would be too slow. We eventually switched to it because the other approach was too complex; it turned out not to be too slow.) – Martin Bonner supports Monica May 24 '17 at 05:50