如何将常量数组作为参数传递给 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:使用局部变量在函数调用前声明局部数组
研究这个问题,最常见的解决方案是创建一个显式的本地变量。它显然有效,因为它稍后会在超出范围时被销毁。它显然有效,但我真的觉得应该有一种更具可读性和更少冗长的方式来做到这一点。

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

尝试 3:常量局部数组
我用不同的方式来传递数组。我尝试过结构等... 我的 mingw 编译器对传递局部常量数组感到满意,并且没有给出任何警告或错误

image.draw_line( 20,(const unsigned char[3]){0,0.9 );

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

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

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

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

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

image.draw_line( 20,0} }.data(),0.9 );

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

image.draw_line( 20,std::string("/0/255/0"),0.9 );
"no matching function for call to 'cimg_library::CImg<float>::draw_line(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,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,255},0.9f,24 );
    //draw a line on the image
    CImg_wrapper::draw_line(image,90},(CImg_wrapper::Point_2d){190,0.9f );
    //The compiler is able to deduce the type from just the brackets if needed
    //CImg_wrapper::draw_line(image,{20,{190,{0,0.9f );
    //Save image on file
    image.save("file.png");

    return 0;
}

输出

Output PNG image

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

问题:

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

解决方法

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

  • 所以你可能有你想要的界面,
  • 如果需要,您可以轻松更改/升级第三个库(您只需调整包装器):
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,{190,{0,0});

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

,

我的简单回答是:

忘记旧的 C 风格数组。根本不要使用它们。

如果您事先知道尺寸,请使用 std::array,如果您不知道,请使用 std::vector

特别是:使用 std::string 而不是 char[]char*

并使用引用传递变量。