The Design of Software (CLOSED)

A public forum for discussing the design of software, from the user interface to the code architecture. Now closed.

The "Design of Software" discussion group has been merged with the main Joel on Software discussion group.

The archives will remain online indefinitely.

organising conversions

My data comes in a variety of different endianess and packing and alignment and such formats and needs to be output in the same variety of different formats.

I was wondering how best to organise these functions.

At the moment, I have a destination-format class for each type.  They all inherit from a common basetype, that has virtual methods to set it from the variety of formats.  So it looks a bit like this:

class Convertor {
  public:
    virtual void setFromFormatA() = 0;
    virtual void setFromFormatB() = 0;
    ...
};

class ConvertToFormatD: public Convertor { ...

I have some more code elsewhere that dispatches to the correct convertor, and it is something like:

switch(destinationFormat) {
  case FormatA: convertor = convertToFormatA; break;
  ...

switch(sourceFormat) {
  case FormatA: convertor.setFromFormatA(sourceValue); break;
  ...

I illustrate how horrid the whole thing is.  My biggest problem is maintaining and navigating this big web of largely similiar code.  With 24 formats, I have 24 destination classes each with 24 setters etc.

I was wondering if there was some better way to arrange this code?
nick nick, new rep
Saturday, September 10, 2005
 
 
Where are you getting the data from?  A byte array or an object with a type?

If it's a byte array, you could have one constructor that passes the parsing off to one of 24 functions depending on the input format. 

If it's an object with a type, you could make 24 constructors that take each type and put them into the Convertor format.

I'd do it in one class myself, I don't think inheritance will buy  you anything special here.  Unless you have a big group of pointers and you don't know which format you want out of them...maybe...

You could have the FormatA, FormatB, etc. all take a Convertor as a constructor parameter, that way you can construct them from the Convertor directly.  Then you wouldn't *need* the Convertor to make new objects, they know how to make themselves.

class Convertor
{
  Convertor(const char * const sv)
  {
    switch(sv[0])
    {
      case A:
        this->readA(sv);
        break;
      //etc...
    }
  }

  Convertor(const FormatA &fa)
  {
    this->value = fa.value;
  }

  FormatA toFormatA()
  {
    return FormatA(this);
  }

  //etc...
}

class FormatA
{
  FormatA(const Convertor &c)
  {
    this->value = c.value;
  }
}
Ryan Phelps Send private email
Saturday, September 10, 2005
 
 
The data probably doesn't know its external representation(s), so maybe you want to have 24 load functions, and 24 save functions, and a class for objects that hold the data -- all independent.

By keeping the load and save as functions, you eliminate the boilerplate, and reduce the overhead of the infrastructure. Then you can have a nice table of function pointers, and your switch-and-case statements vanish into nothing too.
Tom_
Saturday, September 10, 2005
 
 
Oops. I only spot my typos when they're in Garamond -- I mean, the data probably doesn't *need* to know its external representation(s). Hopefully my post makes a bit more sense then.
Tom_
Saturday, September 10, 2005
 
 
I have a successful intermediary format, and that is working.

The problem is performance.  The majority of conversions are not particularly common, and the cost of generic, shared code (import from and export to a standard intermediary format) is not important.  But some key conversions are expensive because they are called so often.

I want a way to elegantly shortcut those conversions that have been identified by profiling as necessary.

I am curious about the 'array of conversion functions' idea.  How do I make a cheaper switch-statement, and how do I keep the number of functions to something small and manageable while having a good fast codepath for those performance-critical parts?
new nick, new rep
Monday, September 12, 2005
 
 
mmm, interesting problem. The array of functions is a good idea, but you don't want to do this for every permutation if my maths is right there are 552 functions....with 24 input and 24 output formats (24*(24-1)=552!).

So you would probably need a two step approach, the first step is to see if a 'fast' custom method exists for the conversion if not then revert back to the common format.

The way that this is done will depend on the format you require after the conversion, a class, byte stream or struct?

So one solution is to create a ConverterFactory class that sets up the standard conversion and dedicated conversion methods. This class will contain an internal array of function pointers to conversion methods.

// I haven't tested this class so don't expect it to work straight up. Additional info on function pointers can be found at: http://www.newty.de/fpt/fpt.html

byte** FormatA2B(byte** input, int fromFormat, int toFormat)
{
  // do stuff;
}

byte** FormatB2A(byte** input, int fromFormat, int toFormat)
{
  // do stuff;
}

class FormatFactory
{
  //
  // These might be better placed as #defs or in a separate
  // class/file so that recurive includes is avoided....
  //
  const int FORMAT_A = 1;
  const int FORMAT_B = 2;
  //...
  const int FORMAT_COUNT = 24;

  public static Format* Create(byte** input, int formatType)
  {
    switch( formatType )
    {
      case FORMAT_A: return create FormatA(input);
      case FORMAT_B: return create FormatB(input);
    }
  }
}

#include "FormatFactory.h"

class ConverterFactory
{

  typedef byte**(*pt2ConvertFn)(byte**, int, int);

  converters pt2ConvertFn[FORMAT_COUNT][FORMAT_COUNT];

  ConverterFactory()
  {
    // init array converters ...
    //  The init may init so that all items point to the GenericConvert method...(?)
    // This could be moved outside this class...
    Add(&FormatA2B,FomratFactory::FORMAT_A, FomratFactory::FORMAT_B);
    Add(&FormatB2A,FomratFactory::FORMAT_B, FomratFactory::FORMAT_A);
    // ...others...
  }
 
  public void Add(pt2ConvertFn convertFn, int fromFormat, int toFormat)
  {
    converters[fromFormat][toFormat] = convertFn;
  }

  public byte** Convert(byte** input, int fromFormat, int toFormat)
  {
    //
    // This statement could be avoided by changing the init of the converters array
    // to (by default) point to the standard conversion function...therefore this
    // method is simplified as a single function call...
    //
    if( NULL != converters[fromFormat][toFormat] )
    {
      return (converters[fromFormat][toFormat])(input, fromFormat, toFormat);
    }
    else
    {
      return GenericConvert(input,fromFormat,toFormat);
    }
  }

  public byte** GenericConvert(byte** input, int fromFormat, int toFormat)
  {
      // do the standard conversion thing...you may wish to init the converters matrix
      // with a standard converter that is a separate function that creates the
      // format classes...I would store the creation of these classes in a separate
      // 'factory' something like (?) This class will contain a switch statement...

      Formatter inputFormat = FormatFactory.Create(fromFormat);
      FOrmatter outputFormat = FormatFactory.Create(toFormat);

      inputFormat.read(input);
      outformat.convet(inputFormat.getStandardFormat());
      return outFormat.write()
  }

}

If you need the format class to be returned then you could use a FormatFactory that creates the classes for both the input and output (as in the GenericConvert method), then check to see if there is a custom method for the conversion, instead of passing the byte** pass the two format classes (you won't require the type numbers).

The custom 'fast' conversion can then use the methods on the classes to set the internal data accordingly. If there is no 'fast' method then a standard one will be used that takes the two classes and uses the standard intermediate format.

The downside is that if a new format type has been added this class will have to change to support it. But something somewhere will need to change anyway, unless you dynamically load .dlls which means the entire configuration could be setup (and changed) at runtime....

Hope this makes sense...any comments?
Dave Barker Send private email
Monday, September 12, 2005
 
 
Templates seem promising:

#include <iostream>
using namespace std;

enum FormatType {
    FormatA,
    FormatB,
    FormatC,
    FormatLast
};

typedef unsigned char Byte;

template<FormatType mov> inline void Move(const Byte* in,Byte* out,unsigned count) {
    cout << "moving type " << mov
        << " for " << count << " whatevers" << endl;
}

template<FormatType src,FormatType dest> inline void Convert(const Byte* in,Byte* out,unsigned count) {
    cout << "generic conversion of type " << src
        << " to " << dest
        << " for " << count << " whatevers" << endl;
}

template<> void Convert<FormatC,FormatB>(const Byte* in,Byte* out,unsigned count) {
    cout << "specialized conversion of type " << FormatC
        << " to " << FormatB
        << " for " << count << " whatevers" << endl;
}

typedef void (*ConvertorFunc)(const Byte*,Byte*,unsigned);

static const ConvertorFunc Convertor[FormatLast][FormatLast] = {
    { Move<FormatA>, Convert<FormatA,FormatB>, Convert<FormatA,FormatC> },
    { Convert<FormatB,FormatA>, Move<FormatB>, Convert<FormatB,FormatC> },
    { Convert<FormatC,FormatA>, Convert<FormatC,FormatB>, Move<FormatC> }
};

int main() {
    for(unsigned src=FormatA; src<FormatLast; src++) {
        for(unsigned dest=FormatA; dest<FormatLast; dest++) {
            Convertor[src][dest](0,0,0);
        }
    }
    return 0;
}


We'll see what the forum software makes of that code snippet..

I got quick and helpful assistance on #c++ at irc.freenode.net

The output is:

moving type 0 for 0 whatevers
generic conversion of type 0 to 1 for 0 whatevers
generic conversion of type 0 to 2 for 0 whatevers
generic conversion of type 1 to 0 for 0 whatevers
moving type 1 for 0 whatevers
generic conversion of type 1 to 2 for 0 whatevers
generic conversion of type 2 to 0 for 0 whatevers
specialized conversion of type 2 to 1 for 0 whatevers
moving type 2 for 0 whatevers
will varfar
Wednesday, September 14, 2005
 
 

This topic is archived. No further replies will be accepted.

Other recent topics Other recent topics
 
Powered by FogBugz