由于模板参数,如何避免 C++ 中的大型 switch 语句
How to avoid large switch statements in C++ due to template parameters
场景:
我想读取一个文件,其中包含在其 header 中定义的数据类型的值,将其内容复制到临时图像,修改临时图像的内容并再次保存。
问题是数据类型的大小需要不同的方式 accessing/modifying 内容并导致由于不同的数据类型导致大量的switch语句(这里的列表被缩短)。
此外,该类型仅在运行时已知,而不是在 compile-time.
#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);
}
是否可以重写图像 class/struct 以使用模板 class 并在 read_image()
中设置数据类型?因此减少了 switch 语句的数量。
我的局限性是我不能使用 C++ 标准库功能,而且我仅限于 C++03。
我找到了一个解决方案with function pointers,但这个解决方案似乎并不比那些大的 switch 语句短。
你的问题是"data being used to find code"中的经典问题。除了为每个有效路径(即每个有效数据类型加上一个错误路径)提供代码之外,别无他法。
进一步的模板魔术最终不会减少击键次数。宏可能。
基于类型的重载函数可能会有所帮助。
此外,数据类型名称(或 ID)和函数指针的映射也有帮助。这适用于 C 语言。
由于您的枚举是连续的,因此您不需要花哨的映射并且可以使用老式数组(如果您可以在没有错误检查的情况下生活)。
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);
}
从您的代码片段中不清楚处理不同数据类型(与 sizeof 相关的除外)的实际差异是什么。
处理 "typed" 对象族的 C++ 方法之一是定义 traits 并结合通用对象模板(应该适用于 C++03 ),然后使用给定的 "trait" 在模板特化中实现特定于类型的处理。本质上,特征负责"enum"。这确实可以大大压缩编写的代码,尤其是当处理不同数据类型非常相似时。
但是,在您的情况下,关于数据类型的决定似乎与外部数据(文件)有关。您的数据类型粒度非常详细(即您定义了简短的无符号类型),因此简单的 strtol/strtod()
不会在这里直接生成模板化对象。
因此,您可能希望在外部数据中包含数据类型 ID,并使用它来创建特征对象,而该对象又会为您生成一个实际使用数据的模板化数据对象。否则,在解析外部数据以将外部(基于字符串)类型映射到内部(数据类型-ID)类型后,您至少需要一个 swtich 语句。
我发现 Alexandrescu 对 traits 做了很好的介绍:http://erdani.com/publications/traits.html。
如果不进一步了解 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;
}
通过将图像 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);
}
场景: 我想读取一个文件,其中包含在其 header 中定义的数据类型的值,将其内容复制到临时图像,修改临时图像的内容并再次保存。
问题是数据类型的大小需要不同的方式 accessing/modifying 内容并导致由于不同的数据类型导致大量的switch语句(这里的列表被缩短)。 此外,该类型仅在运行时已知,而不是在 compile-time.
#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);
}
是否可以重写图像 class/struct 以使用模板 class 并在 read_image()
中设置数据类型?因此减少了 switch 语句的数量。
我的局限性是我不能使用 C++ 标准库功能,而且我仅限于 C++03。
我找到了一个解决方案with function pointers,但这个解决方案似乎并不比那些大的 switch 语句短。
你的问题是"data being used to find code"中的经典问题。除了为每个有效路径(即每个有效数据类型加上一个错误路径)提供代码之外,别无他法。
进一步的模板魔术最终不会减少击键次数。宏可能。
基于类型的重载函数可能会有所帮助。
此外,数据类型名称(或 ID)和函数指针的映射也有帮助。这适用于 C 语言。
由于您的枚举是连续的,因此您不需要花哨的映射并且可以使用老式数组(如果您可以在没有错误检查的情况下生活)。
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);
}
从您的代码片段中不清楚处理不同数据类型(与 sizeof 相关的除外)的实际差异是什么。
处理 "typed" 对象族的 C++ 方法之一是定义 traits 并结合通用对象模板(应该适用于 C++03 ),然后使用给定的 "trait" 在模板特化中实现特定于类型的处理。本质上,特征负责"enum"。这确实可以大大压缩编写的代码,尤其是当处理不同数据类型非常相似时。
但是,在您的情况下,关于数据类型的决定似乎与外部数据(文件)有关。您的数据类型粒度非常详细(即您定义了简短的无符号类型),因此简单的 strtol/strtod()
不会在这里直接生成模板化对象。
因此,您可能希望在外部数据中包含数据类型 ID,并使用它来创建特征对象,而该对象又会为您生成一个实际使用数据的模板化数据对象。否则,在解析外部数据以将外部(基于字符串)类型映射到内部(数据类型-ID)类型后,您至少需要一个 swtich 语句。
我发现 Alexandrescu 对 traits 做了很好的介绍:http://erdani.com/publications/traits.html。
如果不进一步了解 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;
}
通过将图像 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);
}