如何将常量数组作为参数传递给 C++ function/method

How to pass a constant array as argument to a C++ function/method

问题:
我有一个 C++11 方法 (CImg class),它输入一个 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,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 );

建议方案一: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 );

如果我全部内联完成,它就可以工作。有了这个解决方案,我不确定对象在哪里created/destroyed,我担心指针变得无效并悄悄传递垃圾数据。

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

建议方案二: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 提出的第三种解决方案是围绕第三方库创建包装器并使用更方便和可读的接口。能够更改接口允许使用解决所有范围和临时值问题的结构。它还具有添加抽象层并使将来的更改更容易的额外好处。我决定将包装器和结构封装为 class 中的静态方法。我真的很喜欢这个解决方案。

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;
}

输出:

源代码:
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 风格数组吧。完全不要使用它们。

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

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

并使用引用传递变量。

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

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

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