【问题标题】:How can I transition from a runtime value to a template argument?如何从运行时值转换为模板参数?
【发布时间】:2014-10-16 01:25:37
【问题描述】:

背景:我正在编写一些方法,旨在对图像中的数千个像素调用。图像可以有不同的像素格式(8 位灰度、16 位 RGB、24 位 RGB 等)。对每个像素的像素格式重新检查和分支会效率低下,因此我使用 C++ 模板在编译时为每种支持的像素格式生成整个过程的版本:

// Note: PixelFormat::Enum is an integral type.
template<PixelFormat::Enum PixelFormat>
struct EdgeTracer {

  Point FindInitialEdge(const void *pixels, int stride) {
    /* [...] */
  }

  std::vector<Point> TraceEdge(
    const void *pixels, int stride, const Point &initialEdge
  ) {
    /* [...] */
  }

};

现在我想为这些方法创建包装器作为纯 C 函数(用于将 DLL 导出到 .NET P/Invoke)。目前我正在这样做:

EXPORT Point DLLAPI FindInitialEdge(
  const void *pixels, int stride, PixelFormat::Enum pixelFormat
) {
  switch(pixelFormat) {
    case PixelFormat::Format8bppIndexed: {
      return EdgeTracer<PixelFormat::Format8bppIndexed>::FindInitialEdge(
        pixels, stride
      );
    }
    case PixelFormat::Format16bppRgb565: {
      return EdgeTracer<PixelFormat::Format16bppRgb565>::FindInitialEdge(
        pixels, stride
      );
    }
    case PixelFormat::Format24bppRgb888: {
      return EdgeTracer<PixelFormat::Format24bppRgb888>::FindInitialEdge(
        pixels, stride
      );
    }
    default: {
      // error handling
    }
  }
}

EXPORT PointBuffer *DLLAPI TraceEdge(
  const void *pixels, int stride, const Point *initialEdge,
  PixelFormat::Enum pixelFormat
) {
  // Nearly the same switch statement all over again
}

当我想添加对新像素格式的支持时,必须去触摸许多不相关的功能并不是最佳选择。有没有巧妙的方法来避免多余的switch 语句?

这是我到目前为止所制作的,但我还没有设法制作它的可变版本:

template<typename T>
void invokeTemplated(
  PixelFormat::Enum pixelFormat, void (T::*method)(const void *pixels)
) {
  switch(pixelFormat) {
    case PixelFormat::Format16bppRgb555: {
      T<PixelFormat::Format16bppRgb555> instance;
      ((&instance)->*method)(pixels);
      break;
    }
    // [...]
  }
}

【问题讨论】:

    标签: c++ templates optimization variadic-functions


    【解决方案1】:
    1. 编写一个抽象接口来镜像您的公共接口,但只是内部接口:

      class Interface {
      public:
        virtual ~Interface() {}
        virtual Point FindInitialEdge(const void *pixels, int stride) = 0;
        // other pure virtual methods here
      };
      
    2. 创建从像素类型到实现此接口的对象的全局映射

      typedef std::unordered_map<PixelFormat::Enum, Interface*> ImplMap;
      ImplMap impls;
      
    3. 将你所有的公共函数写成如下形式:

      EXPORT Point DLLAPI FindInitialEdge(const void *pixels, int stride,
                                          PixelFormat::Enum pixelFormat) {
        ImplMap::iterator i = impls.find(pixelFormat);
        if (i != impls.end()) {
          return i->FindInitialEdge(pixels, stride);
        } else {
          // error handling
        }
      }
      
    4. 现在您可以让您的EdgeTracer 模板继承自Interface 并匹配虚函数定义,或者编写模板绑定。第一个是打字少了一点

    5. 最后,要注册所有具体的实现,你只需要一个地方来做类似的事情

      template <PixelFormat::Enum PF>
      bool register_format() {
        impls[PF] = new EdgeTracer<PF>;
      }
      
      bool initialized = register_format<PixelFormat::Format8bppIndexed>()
                       | register_format<PixelFormat::Format16bppRgb565>()
                       | register_format<PixelFormat::Format24bppRgb888>();
      

      只需确保您在某处使用了 initialized 的值,这样就不会对其进行优化,并且您可以在一个地方集中注册新格式。

    【讨论】:

    • 这是一个很好的解决方案。虚拟方法只需要包含公共(高级)方法,并允许编译器希望内联和优化下面的代码路径:)
    【解决方案2】:

    一些肮脏的工作

    这是一个相当肮脏的解决方法,但你能不能试试:

    第一:

    你需要所有课程的提醒

    定义可以解决问题:

    # define ALL_MY_CLASS A,B,C //ect....
    

    其次:

    你所有的类都应该有相同的结构:

    A 类{//B、C 类... 上市: static const PixelFormat::enum e= PixelFormat::Format8bppIndexed/* 关联的枚举值 */; void methode_a();//甚至是你的 ::FindInitialEdge };

    那么

    你应该在他们之间浏览

    int test_all_types_for_initial_edge( f) { // here you depack all your possible class
      return (test_types_for_initial_edge<ALL_MY_CLASS>(f));
    };
    
    template<typename current, typename second, typename ...elses>
    int test_types_for_initial_edge(PixelFormat::enum ee) {
      if (ee == current ::e)//you test if the current tested class  match the enum
       return (EdgeTracer< current >::FindInitalEdge(/* do your stuff */);
     return (test_types_for_initial_edge(ee));
    }
    
    template<typename current>
    int test_types_for_initial_edge(PixelFormat::enum ee) {
      if (ee == current ::e)//you test if the current tested class  match the enum
       return (EdgeTracer< current >::FindInitalEdge(/* do your stuff */);
     return (DEFAULT_VALUE); //or throw whatever
    }
    

    这个(相当肮脏的)解决方法有什么改进

    我认为一些 sfinae 并在预编译时检查(如果类作为 static_const 枚举......或想要的方法......等等......)可能会很好...... 但!如果你想保持更新你的代码,你所要做的就是修改ALL_MY_CLASS定义!

    顺便说一句……

    如果您需要其他帮助(例如添加 type_traits 并稍微改进一下这个工作,请询问它,这主要是正确的,但有些部分是伪代码),我不认为这是完全编译的 ^^)

    【讨论】:

      【解决方案3】:

      也许问题出在“void* 像素”上。如果您的像素具有特定类型(8 位、RGBA 等),您可以移除开关并为每种类型实现一个函数。

      如果你使用模板函数,编译器会在你使用指针时自动解析类型,并调用正确的函数。

      【讨论】:

      • 抱歉,没有。有多种像素格式共享相同的指针类型(例如 16 bpp 灰度、16 bpp 5-5-5、16 bpp 5-6-5)。如果每种像素格式都有唯一的数据类型(可以通过自定义像素结构来实现),那么我将不得不为每种支持的像素格式导出一个单独的 C 函数,这将进一步增加添加新像素格式事件的工作量;-)
      • 只是为了提供更多解释,一般的想法是只编写一次像素处理方法(使用 void * 或 std::uint8_t * 或其他),然后在像素格式之间做不同的事情通过一个可以轻松添加新像素格式的精简结构:pastebin.com/mrxirQHD
      猜你喜欢
      • 1970-01-01
      • 2012-06-16
      • 2020-02-07
      • 2017-12-30
      • 1970-01-01
      • 2021-09-02
      • 1970-01-01
      • 2021-11-01
      • 1970-01-01
      相关资源
      最近更新 更多