首页 > 解决方案 > 如何将常量数组作为参数传递给 C++ 函数/方法

问题描述

问题:
我有一个 C++11 方法(CImg 类),它输入一个unsigned char[3]数组来指定颜色。我正在使用 mingw 编译器和-std=gnu++11选项进行编译。
作为参考,这是我需要调用的方法:

template<typename tc>
CImg<T>& draw_line(int x0, int y0, int x1, int y1, const tc *const color, const float opacity=1, const unsigned int pattern=~0U, const bool init_hatch=true);

如果我尝试在本地构建数组,我会收到以下错误。数组在被传递之前被创建和销毁,使得调用函数时参数无效。

image.draw_line( 20, 90, 190, 10, (unsigned char[3]){0,255,0}, 0.9 );
"ERROR: taking address of temporary array"

尝试 2:在函数调用之前使用本地 var 声明本地数组
研究这个问题,最常见的解决方案是创建一个显式本地 var。它显然可以工作,因为它稍后会在超出范围时被销毁。它显然有效,但我真的觉得应该有一种更易读、更简洁的方法。

unsigned char my_temp_color[3] = {0,255,0};
image.draw_line( 20, 90, 190, 10, my_temp_color, 0.9 );

尝试 3:常量本地数组
我用不同的方式来传递数组。我尝试了结构等...我的 mingw 编译器很高兴传递一个本地常量数组,并且没有给出任何警告或错误。

image.draw_line( 20, 90, 190, 10, (const unsigned char[3]){0,255,0}, 0.9 );

建议的解决方案 1:std::array
如果我尝试传递一个裸 std::array,编译器无法将类型解析为 unsigned char[3]

image.draw_line( 20, 90, 190, 10, std::array<unsigned char,3>{ {0,255,0} }, 0.9 );
"error: no matching function for call to 'cimg_library::CImg<float>::draw_line(int, int, int, int, std::array<unsigned char, 3>, double)"

如果我使用 .data() 方法传递本地 std::array 来获取指针,它会起作用,但它比常规数组更冗长。

std::array<unsigned char,3> my_temp_color = { {0,255,0} };
image.draw_line( 20, 90, 190, 10, my_temp_color.data(), 0.9 );

如果我全部内联,它会起作用。有了这个解决方案,我不确定对象是在哪里创建/销毁的,我担心指针变得无效并静默传递垃圾数据。

image.draw_line( 20, 90, 190, 10, std::array<unsigned char,3>{ {0,255,0} }.data(), 0.9 );

建议的解决方案 2:std::string
使用 std::string 更难读取初始化。如果我尝试传递一个裸 std::string,编译器无法将类型解析为 unsigned char[3]

image.draw_line( 20, 90, 190, 10, std::string("/0/255/0"), 0.9 );
"no matching function for call to 'cimg_library::CImg<float>::draw_line(int, int, int, int, std::__cxx11::string, double)'"

就像 std::array 一样,如果我使用 .data() 方法,它会起作用,但我也有同样的担忧。

提出的解决方案3:包装器+结构
@jarod42提出的第三种解决方案是围绕第三方库创建包装器,并使用更方便和可读的界面。能够更改接口允许使用解决范围和临时值的所有问题的结构。它还具有添加抽象层并使未来更改更容易的额外好处。我决定将包装器和结构封装为类中的静态方法。我真的很喜欢这个解决方案。

using语句可用于使代码不那么冗长。我的编译器还可以自动从括号中推断出类型。对于这个问题的范围,我将其扩展以了解发生了什么。

//Build option:
//-std=gnu++11

//STD
#include <string>
//CImg
#define cimg_use_png
#define cimg_display 0
#include "CImg.h"
using namespace cimg_library;
//PNG library
#include "png.h"

//CImg wrapper. Add an abstraction layer to CImg to use less verbose Point and Color structures
class CImg_wrapper
{
    public:
        //2D Point
        typedef struct _Point_2d
        {
            int g_s32_x, g_s32_y;
        } Point_2d;
        //3 channel color
        typedef struct _Color_3u8
        {
            unsigned char g_u8p_rgb[3];
        } Color_3u8;
        //Draw text on an image
        template<typename T>
        static void draw_text(CImg<T>& image, Point_2d origin, std::string text, Color_3u8 foreground_color, float opacity, int font_size )
        {
            image.draw_text(origin.g_s32_x, origin.g_s32_y, text.c_str(), foreground_color.g_u8p_rgb, 0, opacity, font_size);
            return;
        }
        //Draw a line on an image
        template<typename T>
        static void draw_line(CImg<T>& image, Point_2d p1, Point_2d p2, Color_3u8 color, float transparency)
        {
            image.draw_line(p1.g_s32_x, p1.g_s32_y, p2.g_s32_x, p2.g_s32_y, color.g_u8p_rgb, transparency);
            return;
        }
};  //CImg_wrapper

//DEMO
int main(int argc, char** argv)
{
    //Create image
    CImg<float> image
    (
        //width
        200,
        //height
        100,
        //Depth. 1 for a 2D image
        1,
        //Number of channels
        3
    );
    //draw text on the image
    CImg_wrapper::draw_text(image, (CImg_wrapper::Point_2d){20, 10}, std::string("Shaka"), (CImg_wrapper::Color_3u8){0,0,255}, 0.9f, 24 );
    //draw a line on the image
    CImg_wrapper::draw_line(image, (CImg_wrapper::Point_2d){20, 90}, (CImg_wrapper::Point_2d){190, 10}, (CImg_wrapper::Color_3u8){0,255,0}, 0.9f );
    //The compiler is able to deduce the type from just the brackets if needed
    //CImg_wrapper::draw_line(image, {20, 90}, {190, 10}, {0,255,0}, 0.9f );
    //Save image on file
    image.save("file.png");

    return 0;
}

输出:
输出PNG图像

源代码:
https ://github.com/OrsoEric/2021-02-02-CImg-PNG-Test

问题:

  1. 是通过(const unsigned char[3]){0,255,0}C++11 兼容还是只是 mingw 编译器的一个怪癖使它工作?
  2. 还有另一种我没有考虑过声明并将数组作为参数传递的方法吗?
  3. 通过以下调用,临时对象是在语句之​​后(安全)还是在调用之前(导致指针可能变得无效)销毁?
image.draw_line( 20, 90, 190, 10, std::array<unsigned char,3>{ {0,255,0} }.data(), 0.9 );
image.draw_line( 20, 90, 190, 10, std::string("/0/255/0").data(), 0.9 );

标签: c++arraysargumentscimg

解决方案


使用第三个库时,通常最好围绕它们编写包装器。

  • 所以你可能有你想要的界面,
  • 如果需要,您可以轻松更改/升级第三个库(您只需调整包装器):
struct Point
{
   int x = 0;
   int y = 0;
}

struct Color
{
    Color(unsigned char r, unsigned char g, unsigned char b, unsigned char a = 0) :
        rgba{r, g, b, a}
    {}

    // ...
    unsigned char rgba[4]{};
};

template<typename T>
void draw_line(CImg<T>& img, Point p1, Point p2, Color color)
{
    img.draw_line(p1.x, p1.y, p2.x, p2.y, color.rgba);
}

所以你可以使用:

draw_line(img, Point{20, 90}, Point{190, 10}, Color{0,255,0});
draw_line(img, {20, 90}, {190, 10}, {0,255,0});

注意:您甚至可以隐藏CImg<T>在自己的类/界面中。


推荐阅读