【问题标题】:How to write an inheritable template class with operators如何使用运算符编写可继承的模板类
【发布时间】:2019-06-06 17:19:07
【问题描述】:

我希望可以编写一个模板类,它可以被几个特定类型的子类继承。我希望继承的方法和运算符返回子类的类型而不是父模板类型。如果我只需要修改一个基类,这是希望节省大量的开发和维护工作。

这是我已有的示例:

template<typename T> struct TMonoPixel
{
    T value;

    TMonoPixel(T v) { value = v; }

    // the template has some pure virtual functions here...

    TMonoPixel operator+ (const TMonoPixel& other)
    { return TMonoPixel(value + other.value); }
}

struct Mono8Pixel : TMonoPixel<uint8_t>
{
    using TMonoPixel::TMonoPixel;    // I want to inherit the constructor
    // each pixel type implements the virtual functions in the template
}

如您所见,Mono8Pixel 结构继承了接受TMonoPixel+ 运算符,但使用此运算符返回TMonoPixel&lt;uint8_t&gt; 而不是Mono8Pixel,因为它是在基类中定义的。

我打算使用这些结构来迭代图像中的像素:

Image* img; // img has an unsigned char* pointer to its pixel data
for (int row=0; row<img->height; row++) {
    for (int col=0; col<img->width; col++) {
        int i = (row*img->width + col);
        Mono8Pixel* pixel = reinterpret_cast<Mono8Pixel*>(img->dataPtr + sizeof(unsigned char)*i);
        // modify the pixel ...
    }
}

有没有办法只更改模板类以确保Mono8Pixel(2) + Mono8Pixel(2) 返回Mono8Pixel

请注意,无论解决方案是什么,这些结构都必须保持标准布局,因为我希望使用它们。

【问题讨论】:

  • 看看 CRTP。
  • 不,运行时多态是不可能的。可以通过 CRTP 完成。

标签: c++ templates inheritance operator-overloading


【解决方案1】:

您可以使用奇怪重复的模板模式 (CRTP) 来完成您想要的事情。基本思路是这样的:

template<class Pixel> struct TMonoPixel {
    ...

    // not virtual
    std::string GetSomeProperty() const {
        return static_cast<const Pixel&>(*this).GetSomeProperty();
    }

    Pixel operator+(const TMonoPixel& other) const {
        return Pixel(value + other.value);
    }
};

struct Mono8Pixel : TMonoPixel<Mono8Pixel> {
    using TMonoPixel::TMonoPixel;

    std::string GetSomeProperty() const {
        return "My name is Mono8Pixel";
    }
};

感谢隐式派生到基的转换,现在您可以像这样使用它:

template<class T>
void foo(const TMonoPixel<T>& number) {
    std::cout << number.GetSomeProperty();    
}

Mono8Pixel i;
foo(i);

请注意,在TMonoPixel 内部,Pixel 是一个不完整的类型,因此您对如何使用它有一些限制。例如,您不能这样做:

template<class Pixel> struct TMonoPixel {
    Pixel::Type operator+(const TMonoPixel& other);
};

struct Mono8Pixel : TMonoPixel<Mono8Pixel> {
    using Type = std::uint8_t;
};

类型特征是克服这些限制的有用技术:

struct Mono8Pixel;

template<class Pixel> struct ValueType;

template<> struct ValueType<Mono8Pixel> {
    using Type = std::uint8_t;
};

template<class Pixel> struct TMonoPixel {
    using Type = typename ValueType<Pixel>::Type;
    Type value;

    TMonoPixel(Type value) : value(value)
    {}

    Pixel operator+(const TMonoPixel& other) const {
        return Pixel(value + other.value);
    }
};

struct Mono8Pixel : TMonoPixel<Mono8Pixel> {
    using TMonoPixel::TMonoPixel;
};

Mono8Pixel(2) + Mono8Pixel(2) 的类型是Mono8Pixel

所以我想我是在问这些基于 CRTP 的结构在对 value 的类型进行所有这些更改之后是否具有标准布局。

他们这样做:

static_assert(std::is_standard_layout_v<Mono8Pixel>);

完整示例:https://godbolt.org/z/8z0CKX

【讨论】:

  • @Romen,请查看更新后的答案。要使用IntNumber 中的Type,您必须使用类型特征(例如我的示例中的Value_type)。
  • 谢谢,这很有帮助!我现在想知道所有这些更改是否会影响我对此类的用例。我真的为单声道 8 位、16 位、rgb、rgba 等定义了一堆像素结构,我希望将unsigned char* 投射到这些不同的结构中以迭代像素。您是否发现为此目的使用此模式有任何问题?
  • @Romen,你说的选角是什么意思?并非所有强制转换都是有效的,请注意未定义的行为和严格的别名规则。
  • @Romen,如果我正确理解您的意图,这个问题可能会有所帮助:stackoverflow.com/questions/30617519/…
  • 如果我有 unsigned char* pixelData 并且我保证每个像素有 32 位,我希望能够将 RGBAPixel 结构与 reinterpret_cast&lt;RGBAPixel*&gt;(pixelData) 一起使用,以便我可以编辑使用这些不同的运算符在图像中的 RGBA 通道。
猜你喜欢
  • 1970-01-01
  • 2020-03-27
  • 1970-01-01
  • 1970-01-01
  • 2021-04-24
  • 2020-10-14
  • 1970-01-01
  • 1970-01-01
  • 2018-05-23
相关资源
最近更新 更多