c++ - 如何将常量数组作为参数传递给 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;
}
源代码:
https ://github.com/OrsoEric/2021-02-02-CImg-PNG-Test
问题:
- 是通过
(const unsigned char[3]){0,255,0}
C++11 兼容还是只是 mingw 编译器的一个怪癖使它工作? - 还有另一种我没有考虑过声明并将数组作为参数传递的方法吗?
- 通过以下调用,临时对象是在语句之后(安全)还是在调用之前(导致指针可能变得无效)销毁?
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 );
解决方案
使用第三个库时,通常最好围绕它们编写包装器。
- 所以你可能有你想要的界面,
- 如果需要,您可以轻松更改/升级第三个库(您只需调整包装器):
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>
在自己的类/界面中。