C++ 如何在C++; 背景

C++ 如何在C++; 背景,c++,templates,colors,embedded,C++,Templates,Colors,Embedded,我正在使用许多不同的显示器(硬件)和不同的画布(这是画布的复数形式吗?)。每个都可以使用不同的颜色。示例案例: RGB16画布->RGB16显示(这是直截了当的,但我不想只使用异国情调的组合) RGB16画布->RGB24显示 单色画布->RGB16显示,其中“true”必须在运行时显示为颜色集 目前,我确实正在使用mono->rgb16组合(3),以使所有内容都显示为红色且可调光。可能出现的每个显示器的颜色类型也可能略有不同 我想要什么 我想要一组易于扩展的颜色类(C++)。我的目标是能够写简

我正在使用许多不同的显示器(硬件)和不同的画布(这是画布的复数形式吗?)。每个都可以使用不同的颜色。示例案例:

  • RGB16画布->RGB16显示(这是直截了当的,但我不想只使用异国情调的组合)
  • RGB16画布->RGB24显示
  • 单色画布->RGB16显示,其中“true”必须在运行时显示为颜色集
  • 目前,我确实正在使用mono->rgb16组合(3),以使所有内容都显示为红色且可调光。可能出现的每个显示器的颜色类型也可能略有不同

    我想要什么 我想要一组易于扩展的颜色类(C++)。我的目标是能够写简单的作业,如

    Monochrome m; // default value set at runtime
    RGB556 rgb; // default value set at runtime
    
    rgb = m; // conversion function known at compile time
    
    而且

    pixelBuffer<Monochrome,w*h> src;
    pixelBuffer<RGB556,w*h> dest;
    
    std::copy(src.begin(), src.end(), dest.begin());
    
    以及输出:

    Abstract Interface:
    time = 1241 us
    rgb[N/2].r() = 0x00
    copies: 1000
    
    Direct copy:
    time = 157 us
    rgb[N/2].r() = 0x00
    copies: 2000
    
    当我外推第一个数字1241微秒时,对于每秒25帧的128*128显示,仅在颜色之间转换就需要50%的CPU时间。计算结果为:
    128*128*25像素每秒*1241 us/1000像素=0.51秒
    。为这种情况编写的画布/驱动程序组合可以在大约0.1秒内完成,而且它确实必须每秒复制和转换这么多像素,因为整个显示在每一帧中绘制

    这种比较可能有点不公平,但请容忍我。我在分析方面不是很有经验;而编写代码来公平比较我所拥有的和我想要拥有的是不可能的。关键是单色本质上只是一个bool,当我有合适的代码时,编译器应该能够对此进行优化


    实验2:使用模板转换函数分离类 正如安德烈所建议的,我编写了一个RGB24类,将其定义为
    MostPreciseFormat
    ,以及一个模板化的免费
    convert
    函数。也就是说,我不确定这是否正是他的意思:

    class RGB24
    {
      public:
        RGB24() : r_(0), g_(0), b_(0) {}
        uint8_t r_, g_, b_;
    
        uint8_t r() const {return r_;}
        uint8_t g() const {return g_;}
        uint8_t b() const {return b_;}
    
        void setR(const uint8_t& r) {r_ = r;}
        void setG(const uint8_t& g) {g_ = g;}
        void setB(const uint8_t& b) {b_ = b;}
    
        template<typename Other>
        RGB24(const Other& other)
        {
          convert(*this, other);
        }
    
        template <typename Other>
        RGB24& operator=(const Other& other)
        {
          convert(*this, other);
          return *this;
        }
    };
    
    typedef RGB24 MostPreciseFormat;
    
    template <typename To, typename From>
    void convert (To& to, const From& from)
    {
    //  Serial.println("Convert() called"); Serial.flush();
      MostPreciseFormat precise;
      precise.setR(from.r());
      precise.setG(from.g());
      precise.setB(from.b());
      to = precise;
    }
    
    template <>
    void convert(RGB24& to, const bool& from)
    {
      if (from)
      {
        to.setR(0xFF);
        to.setG(0xFF);
        to.setB(0xFF);
      }
      else
      {
        to.setR(0);
        to.setG(0);
        to.setB(0);
      }
    }
    
    RGB24类
    {
    公众:
    RGB24():r_0,g_0,b_0{}
    uint8_t r_,g_,b_;
    uint8_t r()常量{return r_;}
    uint8_t g()常量{return g_;}
    uint8_t b()常量{return b_;}
    void setR(const uint8_t&r){r_=r;}
    void setG(const uint8_t&g){g_=g;}
    void setB(const uint8_t&b){b_=b;}
    模板
    RGB24(常数其他和其他)
    {
    转换(*本,其他);
    }
    模板
    RGB24和运算符=(常量其他和其他)
    {
    转换(*本,其他);
    归还*这个;
    }
    };
    typedef RGB24最精确的格式;
    模板
    无效转换(到和到,常量从和从)
    {
    //Serial.println(“Convert()调用”);Serial.flush();
    最精确的格式;
    precise.setR(from.r());
    precise.setG(from.g());
    精确的.setB(from.b());
    to=精确;
    }
    模板
    无效转换(RGB24和to、常量布尔和from)
    {
    如果(从)
    {
    to.setR(0xFF);
    to.setG(0xFF);
    to.setB(0xFF);
    }
    其他的
    {
    至.setR(0);
    至.setG(0);
    至0.setB(0);
    }
    }
    
    1000像素的转换需要209微秒,这似乎是合理的。但我做对了吗


    我现在有什么 根据安德烈的回答,这是预期的结果。它有一些问题,可能需要在这里和那里进行一些重组。我还没有查看它所需的CPU时间:

    #include <bitset>
    #include <iostream>
    #include <stdint.h>
    
    using namespace std;
    
    namespace channel
    {
    
    static constexpr struct left_aligned_t {} left_aligned = left_aligned_t();
    static constexpr struct right_aligned_t {} right_aligned = right_aligned_t();
    
    template<typename T, unsigned int Offset_, unsigned int Width_>
    class Proxy
    {
      public:
        /* Some checks and typedefs */
        static_assert(std::is_unsigned<T>::value, "ChannelProxy: T must be an unsigned arithmetic type.");
        typedef T data_type;
        static constexpr unsigned int Width = Width_;
        static_assert(Width <= 8, "ChannelProxy: Width must be <= 8.");
        static constexpr unsigned int Offset = Offset_;
        static_assert((Offset + Width) <= 8*sizeof(T), "ChannelProxy: Channel is out of the data type's bounds. Check data type, offset and width.");
    
        Proxy(T& data) : data_(data) {}
    
        uint8_t read(right_aligned_t) const
        {
          return ((data_ & read_mask) >> Offset);
        }
    
        uint8_t read(left_aligned_t) const
        {
          return read(right_aligned) << (8-Width);
        }
    
        void write(const uint8_t& value, right_aligned_t)
        {
          // input data is right aligned
          data_ = (data_ & write_mask) | ((value & value_mask) << Offset);
        }
    
        void write(const uint8_t& value, left_aligned_t)
        {
          // input data is left aligned, so shift right to right align, then write
          write(value >> (8-Width), right_aligned);
        }
    
      private:
        static constexpr uint8_t value_mask = (uint8_t)((1<<Width)-1);
        static constexpr T read_mask = (value_mask << Offset);
        static constexpr T write_mask = (T)~read_mask;
        T& data_;
    };
    
    } // namespace channel
    
    struct RGB24
    {
      typedef channel::Proxy<uint8_t, 0, 8> proxy;
      typedef channel::Proxy<const uint8_t, 0, 8> const_proxy;
    
      RGB24() : r_(0), g_(0), b_(0) {}
      RGB24(const uint8_t& r, const uint8_t& g, const uint8_t& b)
        : r_(r), g_(g), b_(b) {}
    
      // unfortunately, we need different proxies for read and write access (data_type constness)
      const_proxy r() const {return const_proxy(r_);}
      proxy r() {return proxy(r_);}
      const_proxy g() const {return const_proxy(g_);}
      proxy g() {return proxy(g_);}
      const_proxy b() const {return const_proxy(b_);}
      proxy b() {return proxy(b_);}
    
      template <typename From>
      RGB24& operator=(const From& from)
      {
        convert(*this, from);
        return *this;
      }
    
      uint8_t r_;
      uint8_t g_;
      uint8_t b_;
    };
    
    struct RGB565 // 16 bits: MSB | RRRRR GGGGGG BBBBB | LSB
    {
      typedef uint16_t data_type;
    
      typedef channel::Proxy<data_type, 0, 5> b_proxy;
      typedef channel::Proxy<const data_type, 0, 5> const_b_proxy;
      typedef channel::Proxy<data_type, 5, 6> g_proxy;
      typedef channel::Proxy<const data_type, 5, 6> const_g_proxy;
      typedef channel::Proxy<data_type, 11, 5> r_proxy;
      typedef channel::Proxy<const data_type, 11, 5> const_r_proxy;
    
      RGB565() : data_(0) {}
    
      template <typename alignment_type = channel::right_aligned_t>
      RGB565(const uint8_t& r_, const uint8_t& g_, const uint8_t& b_, alignment_type = alignment_type())
      {
        alignment_type alignment;
        r().write(r_, alignment);
        g().write(g_, alignment);
        b().write(b_, alignment);
      }
    
      template <typename From>
      RGB565& operator=(const From& from)
      {
        convert(*this, from);
        return *this;
      }
    
      const_r_proxy r() const {return const_r_proxy(data_);}
      r_proxy r() {return r_proxy(data_);}
      const_g_proxy g() const {return const_g_proxy(data_);}
      g_proxy g() {return g_proxy(data_);}
      const_b_proxy b() const {return const_b_proxy(data_);}
      b_proxy b() {return b_proxy(data_);}
    
      data_type data_;
    };
    
    typedef bool Monochrome;
    
    template <typename To, typename From>
    void convert(To& to, const From& from)
    {
      to.r().write(from.r().read(channel::left_aligned), channel::left_aligned);
      to.g().write(from.g().read(channel::left_aligned), channel::left_aligned);
      to.b().write(from.b().read(channel::left_aligned), channel::left_aligned);
    }
    
    /* bool to RGB565 wouldn't work without this: */
    template <>
    void convert<RGB565, Monochrome>(RGB565& to, const Monochrome& from)
    {
      to.data_ = from ? 0xFFFF : 0;
    }
    
    int main()
    {
      cout << "Initializing RGB24 color0(0b11111101, 0, 0)\n\n";
      RGB24 color0(0b11111101, 0, 0);
    
      cout << "Initializing RGB24 color1(default)\n\n";
      RGB24 color1;
    
      cout << "color 1 = color0\n";
      color1 = color0;
      cout << "color1.r() = " << std::bitset<8*sizeof(uint8_t)>(color1.r().read(channel::right_aligned)) << "\n";
      cout << "color1.g() = " << std::bitset<8*sizeof(uint8_t)>(color1.g().read(channel::right_aligned)) << "\n";
      cout << "color1.b() = " << std::bitset<8*sizeof(uint8_t)>(color1.b().read(channel::right_aligned)) << "\n\n";
    
      cout << "Initializing RGB565 color2(0b10001, 0b100100, 0b10100)\n";
      RGB565 color2(0b10001, 0b100100, 0b10100);
      cout << "color2.data = " << std::bitset<8*sizeof(uint16_t)>(color2.data_) << "\n";
      cout << "color2.b(right aligned) = " << std::bitset<8*sizeof(uint8_t)>(color2.b().read(channel::right_aligned)) << "\n";
      cout << "color2.b(left aligned) = " << std::bitset<8*sizeof(uint8_t)>(color2.b().read(channel::left_aligned)) << "\n\n";
    
      cout << "color 0 = color2\n";
      color0 = color2;
      cout << "color0.b(right aligned) = " << std::bitset<8*sizeof(uint8_t)>(color0.b().read(channel::right_aligned)) << "\n";
      cout << "color0.b(left aligned) = " << std::bitset<8*sizeof(uint8_t)>(color0.b().read(channel::left_aligned)) << "\n\n";
    
      cout << "Initializing Monochrome color3(true)\n\n";
      Monochrome color3 = true;
    
      cout << "color 2 = color3\n";
      color2 = color3;
      cout << "color2.data = " << std::bitset<8*sizeof(uint16_t)>(color2.data_) << "\n";
      cout << "color2.b(right aligned) = " << std::bitset<8*sizeof(uint8_t)>(color2.b().read(channel::right_aligned)) << "\n";
      cout << "color2.b(left aligned) = " << std::bitset<8*sizeof(uint8_t)>(color2.b().read(channel::left_aligned)) << "\n\n";
    
      return 0;
    }
    
    #包括
    #包括
    #包括
    使用名称空间std;
    名称空间通道
    {
    静态constexpr struct left_aligned_t{}left_aligned=left_aligned_t();
    静态constexpr struct right_aligned_t{}right_aligned=right_aligned_t();
    模板
    类代理
    {
    公众:
    /*一些支票和打字机*/
    static_assert(std::is_unsigned::value,“ChannelProxy:T必须是无符号算术类型”);
    typedef T数据类型;
    static constexpr unsigned int Width=Width;
    静态(宽度偏移);
    }
    uint8读取(左对齐)常数
    {
    返回读取(右对齐)(8宽),右对齐);
    }
    私人:
    
    静态constexpr uint8\u t value\u mask=(uint8\u t)((1使用setter/getter方法创建颜色界面

    class Color {
    public:
        void setR(unsigned char )=0;
        unsigned char getR()=0;
        ...
    }
    
    并根据需要使用位字段数据和重写的方法将其继承到自定义颜色类

    class RGB556 : public Color {
        unsigned char r:5;
        unsigned char g:5;
        unsigned char b:6;
    public:
        void setR(unsigned char  r) { this->r=r; }
        unsigned char getR() { return r; }
        ...
    }
    

    首先,从问题来看,我认为解决方案需要非常快的执行时间,因此这一要求应该推动我们尝试解决问题的方法

    这导致我们的结论是,我们应该避免在每像素的基础上调用虚拟函数,否则CPU将不得不为每个像素进行一个额外的不必要的间接寻址。然而,我们根本不应该避免虚拟函数,因为在每画布操作上使用它们是完全可以接受的

    因此,我建议的一般解决方案是关注画布类的运行时灵活性,例如,您可以对每个画布类型使用继承,并关注像素操作的编译时绑定

    这个问题表明,颜色类要解决的最重要的功能是它们之间的颜色格式转换,因此我现在将重点介绍这一点。您可以使用三种方法实现类型之间的转换函数:

    • 星形:每种颜色格式都可以转换为最精确的格式,也可以转换为最精确的格式。要将A转换为B,首先将A转换为最精确的格式,然后再从该格式转换为B。这很简单,而且扩展性很强,因为添加一种新格式只需要定义另外两个函数,即函数的数量与函数的数量呈线性增长格式(O(N))
    • 完全连接:每种颜色都可以与其他颜色格式进行转换。这要快得多,因为只需要一次转换,优化的工作量最小,潜力最大。但是,函数的数量是O(N2)
    • 混合:如果定义了直接转换,则使用它,否则,使用最精确的格式作为中介
    为了让编译器选择正确的转换函数,模板是我能找到的最优雅的解决方案。
    class Color {
    public:
        void setR(unsigned char )=0;
        unsigned char getR()=0;
        ...
    }
    
    class RGB556 : public Color {
        unsigned char r:5;
        unsigned char g:5;
        unsigned char b:6;
    public:
        void setR(unsigned char  r) { this->r=r; }
        unsigned char getR() { return r; }
        ...
    }
    
    // Complete and repeat the class definition below for every color format.
    // There is no specific interface to follow, but all classes must have a
    // template constructor and a template assignment operator to convert from
    // other color formats.
    class ColorXYZ {
    public:
        ...
        template <class Other>
        ColorXYZ(const Other& other) {
            convert(*this, other);
        }
        template <class Other>
        ColorXYZ& operator=(const Other& other) {
            convert(*this, other);
            return *this;
        }
        ...
    };
    
    // These should be class definitions, not just forward declarations:
    class ColorMono;
    class ColorRGB16;
    class ColorRGB24;
    
    // Every format must be able to convert to and from the MostPreciseFormat
    typedef ColorRGB24 MostPreciseFormat;
    
    // Generic conversion of color formats that converts to MostPreciseFormat and
    // then to the required format.
    template <class To, class From>
    void convert(To& to, const From& from) {
        MostPreciseFormat precise(from);
        convert(to, precise);
    }
    
    // Specialization to convert from Mono to RGB24.
    template <>
    void convert<ColorRGB24, ColorMono>(ColorRGB24& to, const ColorMono& from) {
        // specific code to convert from mono to RGB24.
        to.setR(from.value() ? 255 : 0);
        to.setG(from.value() ? 255 : 0);
        to.setB(from.value() ? 255 : 0);
    }
    
    ... // A lot of other specializations of convert here.
    
    convert<ColorRGB24, ColorMono>
    convert<ColorRGB24, ColorRGB16>
    convert<ColorRGB24, ColorRGB24>
    convert<ColorRGB24, ColorXYZ>
    convert<ColorMono,  ColorRGB24>
    convert<ColorRGB16, ColorRGB24>
    convert<ColorRGB24, ColorRGB24>
    convert<ColorXYZ,   ColorRGB24>
    
    class Canvas {
    public:
        ...
        virtual void setPixel(unsigned int index, ColorRGB24 color) = 0;
        ...
    };
    
    class CanvasMono {
    public:
        ...
        virtual void setPixel(unsigned int index, ColorRGB24 color) {
            pixel[index] = color; // converting from RGB24 to mono
        }
        ...
    private:
        ColorMono* pixel;
    };