如何避免由于模板参数导致的C ++中的大型switch语句

时间:2015-01-13 16:55:02

标签: c++ templates

场景: 我想读取一个文件,其中包含在其标题中定义的数据类型的值,将其内容复制到临时图像,修改临时图像的内容并再次保存。

问题在于数据类型的大小需要不同的访问/修改内容的方式,并且由于不同的数据类型而导致大的switch语句(这里的列表被缩短)。 此类型仅在运行时已知,而不是在编译时。

#include <stdint.h>
#include <stdlib.h>

typedef enum {
    DT_UCHAR,                   /** @brief Datatype 'unsigned char'     */
    DT_CHAR,                    /** @brief Datatype 'signed char'       */
    DT_USHORT,                  /** @brief Datatype 'unsigned short'    */
    DT_SHORT,                   /** @brief Datatype 'signed short'      */
    DT_UINT,                    /** @brief Datatype 'unsigned int'      */
    DT_INT,                     /** @brief Datatype 'signed int'        */
    DT_FLOAT,                   /** @brief Datatype 'float'             */
    DT_DOUBLE,                  /** @brief Datatype 'double'            */
} e_datatype;

struct image {
    e_datatype      type;
    size_t          size;
    void*           data;
};

image image1;
image image2;
image image3;

template <typename T>
void create_mask(void *const dst, const unsigned i_dst, const void *const src, const unsigned i_src, const void* const threshold) {
    if (static_cast<const T*>(src) < static_cast<const T*>(threshold))
        *(static_cast<T*>(dst)+i_dst) = 1;
}


void create_mask(image *const out, const unsigned out_i, const image *const in, const unsigned in_i, const image* const threshold) {

    if (in->type != threshold->type || out->type != DT_UCHAR)
        return;

    switch (out->type) {
    case DT_UCHAR:
        create_mask<unsigned char>(out->data, out_i, in->data, in_i, threshold->data);
        break;
    case DT_CHAR:
        create_mask<signed char>(out->data, out_i, in->data, in_i, threshold->data);
        break;
    case DT_USHORT:
        create_mask<unsigned short>(out->data, out_i, in->data, in_i, threshold->data);
        break;
    case DT_SHORT:
        create_mask<signed short>(out->data, out_i, in->data, in_i, threshold->data);
        break;
    case DT_UINT:
        create_mask<unsigned int>(out->data, out_i, in->data, in_i, threshold->data);
        break;
    case DT_INT:
        create_mask<signed int>(out->data, out_i, in->data, in_i, threshold->data);
        break;
    case DT_FLOAT:
        create_mask<float>(out->data, out_i, in->data, in_i, threshold->data);
        break;
    case DT_DOUBLE:
        create_mask<double>(out->data, out_i, in->data, in_i, threshold->data);
        break;
    default:
        //printf("ERROR. Unknown type.\n");
        break;
    }
}

size_t sizeof_image(e_datatype type) { return 1 /* another switch with the size of each datatype */;  }

void read_image() {

    image *my_image1 = &image1;
    image *my_image2 = &image2;
    image *threshold = &image3;

    // read header and save it in my_image and then
    // read data and copy it to the data field of my_image
    read_image_header("mydata_uint.dat", my_image1);
    my_image1->data = calloc(my_image1->size, sizeof_image(my_image1->type));
    read_image_data("mydata_uint.dat", my_image1);

    // create output mask
    my_image2->size = my_image1->size;
    my_image2->type = DT_UCHAR;
    my_image2->data = calloc(my_image2->size, sizeof_image(DT_UCHAR));

    // read threshold value from another image
    read_image_header("mydata_thresh.dat", threshold);
    threshold->data = calloc(threshold->size, sizeof_image(threshold->type));
    read_image_data("mydata_thresh.dat", threshold);

    for (unsigned i = 0; i < my_image1->size; i++)
        create_mask(my_image1, i, my_image2, i, threshold);
}

是否可以重写图像类/结构,以便它使用模板类,其数据类型设置在read_image()内?因此减少了switch语句的数量。

我的限制是我无法使用C ++标准库功能,而且仅限于C ++ 03。

我找到了一个解决方案with function pointers,但这个解决方案似乎并不比那些大型的switch语句短。

6 个答案:

答案 0 :(得分:2)

如果不了解copy1D()及其他功能的使用案例,则很难提供完整的解决方案。在任何情况下,您的解决方案的可能替代方案是使用动态多态。例如:

使用您需要的所有方法创建一个界面

// Interface
struct Image {
    virtual Image* copy() = 0;
    virtual void manipulate() = 0;
    virtual void writeToFile() = 0;
    virtual ~Image() { }
};

现在让模板处理你的界面的实现

// Templated implementation
template <typename T>
struct ImageImpl : Image {

    size_t size;
    T* data;

    ImageImpl( const ImageImpl<T>& other ) {
        size = other.size;
        data = new T[size];
        memcpy( data, other.data, size * sizeof(T) );
    }

    ~ImageImpl() {
        delete[] data;
    }

    virtual Image* copy() {
        return new ImageImpl( *this );
    }

    virtual void manipulate() {
        for ( int i = 0; i < size; i++ ) {
            data[i] = data[i] + 1; // Do something with the data
        }
    }

    virtual void writeToFile( const char* filename ) {
        for ( int i = 0; i < size; i++ ) {
            write( data[i] );
        }
    }
};

使用示例:

Image* newFromFile( const char* filename )
{
    Image* i = NULL;

    if ( isIntFile( filename ) ) {
        i = new ImageImpl<int>( ... );
    }
    else if ( isFloatFile( filename ) ) {
        i = new ImageImpl<float>( ... );
    }
    ...

    return i;
}

int main()
{
    Image* i = newFromFile( "example.img" );

    Image* iCopy = i->copy();
    iCopy->manipulate();
    iCopy->writeToFile( "new.img" );

    delete iCopy;
    delete i;
}

答案 1 :(得分:0)

您的问题是“用于查找代码的数据”中的经典问题。除了为每个有效路径提供代码(即每个有效数据类型加上一个错误路径)之外,没有别的办法。

进一步的模板魔术最终不会减少击键次数。宏可能。

答案 2 :(得分:0)

基于类型的重载函数可能会有所帮助。

此外,数据类型名称(或ID)和函数指针的映射也可以提供帮助。这适用于C语言。

答案 3 :(得分:0)

由于你的枚举是连续的,你不需要一个花哨的地图,并且可以使用一个老式的数组(如果你可以在没有错误检查的情况下生活)。

typedef void (*copyFunction)(void *const, const unsigned, const void *const, const unsigned);

void copy1D(image *const out, const unsigned out_i, const image *const in, const unsigned in_i)
{
    static const copyFunction functions[] =
    {
       copyValue<unsigned char>,
       copyValue<signed char>,
       copyValue<unsigned short>,
       copyValue<signed short>,
       copyValue<unsigned int>,
       copyValue<signed int>,
       copyValue<float>,
       copyValue<double>
    };
    functions[out->type](out->data, out_i, in->data, in_i);
}

您可以使用宏来概括:

#define MAKE_TABLE(function)\
   {\
       function<unsigned char>,\
       function<signed char>,\
       function<unsigned short>,\
       function<signed short>,\
       function<unsigned int>,\
       function<signed int>,\
       function<float>,\
       function<double>\
   }

typedef void (*copyFunction)(void *const, const unsigned, const void *const, const unsigned);

void copy1D(image *const out, const unsigned out_i, const image *const in, const unsigned in_i) 
{
   static const copyFunction functions[] = MAKE_TABLE(copyValue);
   functions[out->type](out->data, out_i, in->data, in_i);
}

或者,您可以将函数指针作为image的成员(这就像手动管理的虚函数)

typedef void (*copyFunction)(void *const, const unsigned, const void *const, const unsigned);

struct image {
   e_datatype      type;
   size_t          size;
   void*           data;
   copyFunction    copy;
};

void read_image() 
{
    image *my_image = 0;
    // Read image...
    // ...

    // Set up the "virtual function"
    static const copyFunction functions[] = MAKE_TABLE(copyValue);
    my_image->copy = functions[my_image->type];
}

void copy1D(image *const out, const unsigned out_i, const image *const in, const unsigned in_i) 
{
   out->copy(out->data, out_i, in->data, in_i);
}

答案 4 :(得分:0)

从您的代码片段中不清楚处理不同数据类型(与sizeof相关的除外)之间的实际差异是什么。

处理一系列“类型”对象的C ++方法之一是将 traits 与公共对象模板一起定义(应该可以使用C ++ 03),然后实现类型 - 具有给定“特征”的模板特化中的特定处理。从本质上讲,特质照顾“枚举”。这可能确实压缩了编写的代码,特别是在处理不同的数据类型时非常相似。

但是,在您的情况下,关于数据类型的决定似乎与外部数据(文件)有关。您的数据类型粒度非常详细(即您定义了短的无符号类型),因此简单的strtol/strtod()不会在此处切割以直接生成模板化对象。

因此,您可能希望在外部数据中包含数据类型ID,并使用它来创建特征对象,从而产生实际消耗数据的模板化数据对象。否则,在解析外部数据以将外部(基于字符串的)类型映射到内部(数据类型ID)类型之后,您需要至少有一个swtich语句。

我发现特征的一个很好的介绍是由Alexandrescu撰写的:http://erdani.com/publications/traits.html

答案 5 :(得分:0)

通过将图像IO与图像本身分离,您已经进入了正确的方向。为了能够在编写器中使用图像类型,您有多个选项。我的方法是:

#include <cstdint>
#include <vector>

using namespace std;

template <typename T> class Image
{
public:
    typedef T Pixel_t;
    vector<Pixel_t> data; // or any suitable data structure

// all other stuff that belongs to the Image Class
};

template <class T> ImageWriter
{
public:
    typedef T Image_t;

    void writeImage(const Image_t& image)
    {
        write(file, &image[0], image.size()*sizeof(Image_t::Pixel_t));
    }
// other stuff that belongs to the ImageWriter
private:
    void write(File& file, void* data, size_t size);
};

然后您可以在您的应用程序中使用它

typedef Image<uint32_t> img_t;

void myStuff()
{
    img_t img;
    ImageWriter<img_t::Pixel_t> writer;

    // ...
    writer.writeImage(img);
}