C++模板类:如何避免在运行时给定模板参数时重复编写大型switch语句?

3

我目前正在开发一款图像处理应用程序,主要基于C++和ITK。

1. 现状

我有一些节点类(例如FlipFilter),它们都从同一个基类派生而来。每个节点会得到一个包含image_ptr和所有元信息(图像维度、PixelType(例如RGB、RGBA、标量)和ComponentType(例如Float、int))的结构体。节点必须根据这些Meta-Informations实例化一个ITK-Filter,这些信息可以在每个输入图像上更改。

2. 问题

这些ITK-Filters需要以模板参数的形式传递ImageType(例如带有ComponentType UCHAR的RGB)。模板类必须在编译时实例化。我的节点在运行时获取图像及其类型。因此,我需要为每个节点创建所有过滤器的所有排列,然后使用适当的实例化。

3. 我目前的解决方案

这个结构体包含了所有的元信息和指向实际图像的智能指针。我使用了图像的基指针,因为图像本身也是一个模板(稍后我会进行向下转换)。

struct ImageData
{
    short NumberOfDimensions;
    itk::ImageIOBase::IOComponentType ComponentType;
    itk::ImageIOBase::IOPixelType PixelType;
    itk::DataObject::Pointer Image;
    ImageData() {}
    ~ImageData() {}
};

这是我的Node更新功能。它应该创建筛选器并在图像上执行它。
void LitkFlipImageFilter::update()
{
    if (Input1 == nullptr)
        throw(std::runtime_error("Input1 not set"));


    Input1->update();

    ImageData Input1Data = Input1->getOutput();

    switch (Input1Data.PixelType)
    {
        default:
        {
            throw std::runtime_error("Type not Supported");
            break;
        }
        case itk::ImageIOBase::RGB:
        {
            switch (Input1Data.ComponentType)
            {
                default:
                {
                    throw std::runtime_error("Type not Supported");
                    break;
                }
                case itk::ImageIOBase::IOComponentType::UCHAR:
                {
                    using PixelType = itk::RGBPixel< unsigned char >;
                    using ImageType = itk::Image < PixelType, 2 >;
                    itk::FlipImageFilter<ImageType>::Pointer filter = itk::FlipImageFilter<ImageType>::New();
                    //do stuff

                    break;
                }
            break;
            }
        }
    }
}

4. 我的解决方案存在问题

虽然我的解决方案可以工作,但它会产生大量重复的代码和嵌套很深的switch语句。您是否知道更加优雅的解决方案呢?

谢谢!


你的解决方案没有问题。实现它一次,然后继续。 - Ben
ITK的设计理念是在编译时就知道图像类型。如果不知道,考虑使用其他库。SimpleITK为您实现了所有这些情况,让您拥有更简单的接口来执行相同的例程。您还可以考虑使用DIPlib,它支持运行时类型和维度选择(我是作者)。 - Cris Luengo
@CrisLuengo 我会看一下 DIPlib ;) - mdin
1个回答

2
你想要的高级处理是:
template <typename PixelType>
void do_stuff()
{
    using ImageType = Image < PixelType, 2 >;
    ...do stuff...
}

您可以创建一个冗长但可重用的(通过变化“Fn”代码来调度)版本的切换代码:
template <typename Fn>
void dispatch(PixelType pt, ComponentTypeId ct, Fn fn) {
    switch (pt)
    {
      case RGB:
        switch (ct) {
          case Uint8_t: fn(RGBPixel<uint8_t>{}); return;
          case Float:   fn(RGBPixel<float>{}); return;
        };
      case RGBA:
        switch (ct) {
          case Uint8_t: fn(RGBAPixel<uint8_t>{}); return;
          case Float:   fn(RGBAPixel<float>{}); return;
        };
      case Scalar:
        switch (ct) {
          case Uint8_t: fn(ScalarPixel<uint8_t>{}); return;
          case Float:   fn(ScalarPixel<float>{}); return;
        };
    }
}

然后,像这样调用它:
dispatch(runtime_pixel_type, runtime_component_type, 
         [](auto pt) { do_stuff<decltype(pt)>(); });

注:

  • 在lambda中使用默认构造的“XXXPixel”参数很丑陋-C++2a应该引入适当的模板化lambda,可能(?!)可以清理这个问题。

  • 您可以链接几个“dispatch”函数,每个函数都基于一个运行时变量进行调度,以避免开关情况的乘法爆炸;这样更具可扩展性,但在这里是过度设计,并且您必须解决PixelType是一个模板的问题。

  • 您可以将default: throw添加回去-不需要在它们之后break(或return),因为它们永远不会返回到下一行代码


首先,感谢您的建议!在您的lambda函数中,您已经启动了do_stuff模板。然后调度程序获取该函数并再次给出类型,但我想做的是在通用函数(例如您的调度程序)中实例化do_stuff以用于不同的节点类型(例如FlipNode,FlopNode,XYZNode),这样我就不必为每个节点类型编写一个巨大的开关案例。我尝试修改您的解决方案,但到目前为止我还没有成功。 :( - mdin
@mdin:“在您的lambda函数中,您已经启动了do_stuff模板。然后调度程序获取该函数并为其提供类型(再次)”- lambda为像素类型指定了“auto”,因此可以通过调用调度程序代码为switch中处理的每种类型实例化它。所以,不确定您为什么说“再次”。无论如何,您确实需要某种从运行时值到实例化的映射;如果switch太冗长,请考虑这些技术。 - Tony Delroy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接