Как передать постоянный массив в качестве аргумента функции/метода С++

Проблема.
У меня есть метод 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,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)"

Это работает, если я передаю локальный std::array с помощью метода .data() для получения указателя, но это даже более подробно, чем обычный массив.

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} С++ 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 );

person 05032 Mendicant Bias    schedule 01.02.2021    source источник
comment
А как насчет std::array?   -  person churill    schedule 01.02.2021
comment
Я бы подумал о добавлении класса для Color, Point.   -  person Jarod42    schedule 01.02.2021
comment
Спасибо. 'tc' - это просто шаблон, я забыл его добавить. Я отредактировал вопрос.   -  person 05032 Mendicant Bias    schedule 01.02.2021
comment
Я только что попробовал std::array error: no matching function for call to 'cimg_library::CImg<float>::draw_line(int, int, int, int, std::array<unsigned char, 3>, double)'   -  person 05032 Mendicant Bias    schedule 01.02.2021
comment
@Jarod42 Jarod42 Я не могу изменить класс CImg, чтобы он принимал другой тип, и разве при использовании объекта не возникнет та же проблема, связанная с его уничтожением до того, как его можно будет использовать?   -  person 05032 Mendicant Bias    schedule 01.02.2021
comment
Затем напишите обертку: template<typename T> draw_line(CImg<T>&, Point p1, Point p2, Color);   -  person Jarod42    schedule 01.02.2021
comment
@Jarod42 Jarod42 Я могу написать как точку Classes, так и Color, а также написать оболочки для нужных мне методов, но разве создание объектов Points внутри вызова не будет иметь ту же проблему, что и уничтожение перед вызовом? draw_line_wrapper(image, Point(20,90), Point( 190,10), Color(0,255,0) );   -  person 05032 Mendicant Bias    schedule 01.02.2021
comment
Такие вещи, как {0,255,0}, должны быть названы. Не используйте магические константы в коде. Как насчет Green?   -  person n. 1.8e9-where's-my-share m.    schedule 01.02.2021
comment
@н. 'местоимения' m Я тоже большой поклонник перечисления. НАПРИМЕР. Мне нравится добавлять typedef enum _Config { MY_PARAM=42 } Config; для хранения параметров класса. Для этого вопроса я вырезал нерелевантный код.   -  person 05032 Mendicant Bias    schedule 01.02.2021


Ответы (2)


При работе с 3-й библиотекой обычно хорошо писать обертку вокруг них.

  • Таким образом, у вас может быть интерфейс, который вы хотите,
  • и вы можете легко изменить/обновить 3-ю библиотеку, если это необходимо (вам нужно только адаптировать оболочки):
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> в своем собственном классе/интерфейсе.

person Jarod42    schedule 01.02.2021
comment
Это работает! Я отредактировал вопрос, чтобы добавить свою реализацию. Я отказался от использования конструктора внутри структур и решил инкапсулировать обертки как статические методы внутри класса-оболочки. Спасибо, это решает мою проблему! - person 05032 Mendicant Bias; 02.02.2021

Мой простой ответ:

Забудьте о старых массивах в стиле C. Не используйте их вообще.

Используйте std::array, если вы заранее знаете размер, и std::vector, если нет.

Особенно: используйте std::string вместо char[] или char*.

И передайте переменные, используя ссылки.

person U. W.    schedule 01.02.2021
comment
Использование std::string вместо char[] или char* не имеет смысла в контексте кода операции. В стандартном C++ нет типа int8, так что char — просто следующая лучшая вещь. - person George; 01.02.2021
comment
Я попробовал это, и, кажется, это работает image.draw_line( 20, 90, 190, 10, std::array<unsigned char,3>{ {0,255,0} }.data(), 0.9 ); Это правильное использование? Я не уверен, где объект создается/уничтожается и где он выходит за рамки. - person 05032 Mendicant Bias; 01.02.2021
comment
уничтожены быстро здесь, к сожалению. - person Jarod42; 01.02.2021
comment
@George Есть (необязательный, но часто реализуемый) std::(u)int8_t, а начиная с С++ 20 есть char8_t, так что я не уверен, о чем вы пытаетесь спорить здесь ... - person rubenvb; 01.02.2021
comment
Совет по использованию std::array хорош, но я не понимаю, как это решит проблему встроенного объявления, являющегося временным объектом, который уничтожается слишком рано. - person 05032 Mendicant Bias; 01.02.2021
comment
@rubenvb Попытка утверждать, что в контексте кода операции использование std::string - плохой совет. - person George; 01.02.2021