我有以下用例。
我需要创建一个Image类。图像定义如下:
上述类型的所有组合都是可能的。
此外,
我的问题如下:我应该使用模板Image
类还是通用接口?例如:
// 'Generic' Image interface
class Image {
...
protected:
// Totally generic data container
uint8_t* data;
};
// Template Image interface
template <typename PixelType>
class Image {
...
protected:
// Template data container
PixelType* data;
};
使用模板Image
类
我现在的问题是,如果我使用模板Image
类,我的文件输入/输出将会很混乱,因为当我打开图像文件时,我不知道先验的是什么图像类型将是,因此我不知道要返回的模板类型。
这可能是最佳解决方案,如果我能想出一种创建通用函数的方法,该函数将从文件读取图像并返回通用对象,类似于
ImageType load(const char* filename);
但由于ImageType
必须是模板,我不知道如何以及是否可以这样做。
使用通用Image
类
但是,如果我使用泛型Image
类,我的所有算法都需要一个包含if / switch语句的包装函数,如:
Image applyAlgorithmWrapper(const Image& source, Arguments args) {
if (source.channels() == 1) {
if (source.type() == IMAGE_TYPE_UCHAR) {
return FilterFunction<unsigned char>(source, args);
}
else if (source.type() == IMAGE_TYPE_FLOAT) {
return FilterFunction<float>(source, args);
} else if ...
} else if (source.channels() == 3) {
if (source.type() == IMAGE_TYPE_UCHAR) {
return FilterFunction<Vec3b>(source, args);
}
...
}
(注意:Vec3b
是一个通用的3字节结构,如
struct Vec3b {
char r, g, b;
};
答案 0 :(得分:1)
在我看来,模板化的课程是首选的解决方案。
它将为您提供模板的所有优势,这基本上意味着您的代码库更清晰,更易于理解和维护。
使用模板化课程时,你所说的是一个问题并不是什么大问题。当用户想要阅读图像时,他/她应该知道他/她想要接收图像文件输出的数据类型。因此,用户应该这样做:
Image<float>* img;
LoadFromTIFF(*img, <filename>);
这与ITK等库中的方式非常相似。在您可能编写的模块中,您可以编写以从TIFF模块读取,您将执行此类型转换以确保返回用户声明的类型。
手动创建图像时,用户应执行以下操作:
Image<float>*img;
img->SetSize(<width>, <height>);
img->SetChannels(<enum_channel_type>);
从长远来看,这比使用非模板类更简单。
您可以查看ITK的源代码,了解如何在最一般意义上实现这一点,因为ITK是一个高度模板化的库。
编辑(附录) 如果您不希望用户对图像数据类型进行先验控制,则应考虑在TIFF标头中使用SMinSampleValue和SMaxSampleValue标记。这些标题存在于任何现代TIFF文件(版本6.0)中。它们旨在具有与TIFF文件中的示例数据类型匹配的TYPE。我相信会解决你的问题
答案 1 :(得分:1)
为了做出关于模板与非模板的正确决策(基于事实而不是意见),我的策略是衡量和比较两个解决方案(模板和非模板)。我想衡量以下指标:
以及其他更主观的措施,例如:
我开发了一个非常大的软件[1],基于这些测量,我的图像类不是模板。我知道其他成像库提供两种选择[2](但我不知道他们有什么机制/代码是否仍然非常清晰)。我还有一些使用各种维度点(2d,3d,... nd)运算的算法,并且对于这些算法使算法成为模板导致性能增益使其值得。
简而言之,要做出正确的决定,有明确的标准,明确的衡量方法,并在玩具示例中尝试两种选择。
答案 2 :(得分:1)
模板。还有一个变种。如果你还没有C ++ 14,还有一个'界面助手'。让我解释一下。
每当您对给定操作有一组有限的特化时,您可以将它们建模为满足接口或概念的类。如果这些可以表示为一个模板类,那么这样做。它可以帮助您的用户只需要一个特定的专业化,当您从非类型化的源(例如文件)中读取时,您所需要的只是一个工厂。请注意,您还需要一个工厂 ,只是通常可以很好地定义返回类型。这就是我们来的地方......
变体。每当您不知道返回类型,但是您知道在编译时可能的返回类型集时,请使用变量。键入你的变体,使它看起来像'基类(请注意,没有涉及继承或虚函数),然后使用访问者。在C ++ 14中编写访问者的一种特别简单的方法是通用lambda,它通过引用捕获所有内容。实质上,从代码中的那一点开始,您就拥有了特定的类型。因此,将特定/模板化的类作为函数参数。
现在,boost::variant<>
(或std::variant<>
,如果有的话)不能拥有成员函数。要么驻留在'C-API样式'泛型函数(可能只是委托给成员函数)和对称运算符;或者您有一个从您的变体类型创建的辅助类。如果你的CR允许它,你可能会从变体中下载 - 注意,有些人认为这种可怕的风格,其他人接受它作为图书馆作者的意图(因为,如果作者想要禁止继承,他们写了final
)。 / p>
代码草图,不要尝试编译:
enum PixelFormatEnum { eUChar, eVec3d, eDouble };
template<PixelFormatEnum>
struct PixelFormat;
template<>
struct PixelFormat<eUChar>
{
typedef unsigned char type;
};
// ...
template<PixelFormatEnum pf>
using PixelFormat_t = typename PixelFormat<pf>::type;
template<PixelFormatEnum pf>
struct Image
{
std::vector<std::vector<PixelFormat_t<pf> > > pixels; // or anything like that
// ...
};
typedef boost::variant< Image<eUChar>, Image<eVec3d>, Image<eDouble> > ImageVariant;
template<typename F>
struct WithImageV : boost::static_visitor<void>
{
// you could do this better, e.g. with compose(f, bsv<void>), but...
F f_;
template<PixelFormatEnum e>
void operator()(const Image<e>& img)
{
f_(img);
}
}
template<typename F>
void WithImage(const ImageVariant& imgv, F&& f)
{
WithImageV v{f};
boost::apply_visitor(v, img);
}
std::experimental::optional<ImageVariant> ImageFactory(std::istream& is)
{
switch (read_pixel_format(is))
{
case eUChar: return Image<eUchar>(is);
// ...
default: return std::experimental::nullopt;
}
}
struct MyFavoritePixelOp : public boost::static_visitor<int>
{
template<PixelFormatEnum e>
int operator()(PixelFormat_t<e> pixel) { return pixel; }
template<>
int operator()(PixelFormat_t<eVec3d> pixel) { return pixel.r + pixel.g + pixel.b; }
};
int f_for_variant(const ImageVariant& imgv)
{
// this is slooooow. Use it only if you have to, e.g., for loading.
// Move the apply_visitor out of the loop whenever you can (here you could).
int sum = 0;
for (auto&& row : imgv.pixels)
for (auto&& pixel : row)
sum += boost::apply_visitor(MyFavoritePixelOp(), pixel);
return sum;
}
template<PixelTypeEnum e>
int f_for_type(const Image<e>& img)
{
// this is faster
int sum = 0;
for (auto&& row : img)
for (auto&& pixel : row)
sum += MyFavoritePixelOp()(pixel);
return sum;
}
int main() {
// ...
if (auto imgvOpt = ImageFactory(is))
{
// 1 - variant
int res = f_for_variant(*imgvOpt);
std::cout << res;
// 2 - template
WithImage(*imgvOpt, [&](auto&& img) {
int res2 = f_for_type(img);
std::cout << res2;
});
}
}