【问题标题】:How to pass a constant array as argument to a C++ function/method如何将常量数组作为参数传递给 C++ 函数/方法
【发布时间】:2021-02-01 09:16:32
【问题描述】:

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

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)'"

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

建议的解决方案 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;
}

输出:

源码:
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 );

【问题讨论】:

  • std::array 呢?
  • 我会考虑为 Color、Point 添加类。
  • 谢谢。 'tc' 只是一个模板,我忘了添加它。我编辑了问题。
  • 然后写一个包装器:template&lt;typename T&gt; draw_line(CImg&lt;T&gt;&amp;, Point p1, Point p2, Color);
  • {0,255,0} 之类的东西应该被命名。不要在代码中使用魔法常量。 Green 怎么样?

标签: c++ arrays arguments cimg


【解决方案1】:

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

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

【讨论】:

  • 这行得通!我编辑了问题以添加我的实现。我决定不在结构内部使用构造函数,而是决定将包装器封装为包装器类中的静态方法。谢谢,这解决了我的问题!
【解决方案2】:

我的简单回答是:

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

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

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

并使用引用传递变量。

【讨论】:

  • 使用std::string 代替char[]char* 在操作代码的上下文中没有任何意义。标准 C++ 中没有 int8 类型,所以 char 是次优的。
  • 我试过了,它似乎可以工作image.draw_line( 20, 90, 190, 10, std::array&lt;unsigned char,3&gt;{ {0,255,0} }.data(), 0.9 );这是正确的用法吗?我不确定对象是在哪里创建/销毁的以及它在哪里超出范围。
  • 不幸在这里被毁得很快。
  • @George 虽然有(可选但经常实现)std::(u)int8_t,但从 C++20 开始,有 char8_t,所以我不确定你想在这里争论什么...
  • @rubenvb 试图争辩说,在操作代码的上下文中使用std::string 是不好的建议。
猜你喜欢
  • 1970-01-01
  • 2011-09-13
  • 1970-01-01
  • 1970-01-01
  • 2019-07-11
  • 2020-03-16
  • 1970-01-01
  • 2014-02-24
  • 2015-02-12
相关资源
最近更新 更多