LINUX.ORG.RU

Приведите доводы использовать выходные параметры у функций

 , , чистый код


0

5

Серьезно. В той же OpenCV ехал выходной параметр через выходной параметр. В итоге: непонятно, что же именно функция возвращает, надо объявлять все «возвращаемые значения» заранее. Есть риск при этом ошибиться с типом, ибо функция при компиляции может молча проглотить предполагаемый тип, а уже на этапе выполнения сказать «Нет, хозяин, не могу». Впрочем, такой подход еще много где видел. Зачем и почему?

Ответ на: комментарий от anonymous

В фантазиях например. Максимум на какой-то лапше компилятор затупить может, но не более.

да вы прям тут депутат хамзаев, избранный от всех компиляторов. что вы вообще про них знаете-то?

alysnix ★★★
()
Ответ на: комментарий от Kogrom

А ему и не надо быть ключевым словом, свои задачи обертка решает, читателю понятно. Без обертки - ошибка компиляции. Кому надо - сделает выразительное api, кому нет - не будет заниматься этой дрочкой с доп словами.

anonymous
()
Ответ на: комментарий от anonymous

Да и причём здесь pair?

struct MyStruct
{
    MyStruct(){ cout << "MyStruct constructor" << endl;}
    MyStruct(const MyStruct &other){ cout << "MyStruct copy constructor" << endl;}
    MyStruct(const MyStruct &&other){ cout << "MyStruct move constructor" << endl;}
    MyStruct& operator=(const MyStruct& other){ cout << "MyStruct = constructor" << endl;}
    MyStruct& operator=(const MyStruct&& other){ cout << "MyStruct = constructor" << endl;}

};

pair<MyStruct, MyStruct> func()
{
    MyStruct a;
    MyStruct b;
    return pair<MyStruct, MyStruct>(a, b);
}

int main()
{
    auto c = func();
    return 0;
}

Здесь конструкторы копирования срабатывают. Есть std::make_pair, но вроде он тут не спасает.

Kogrom
()
Ответ на: комментарий от anonymous

сказал уже - чистый out параметр, то есть читать нельзя, писать можно (даже нужно, компилятор проверит) - это для возврата множества результатов. альтернатив нет, только неэффективный тупл. если только возврат тупла не релизован, как скрытые out параметры :)

чистый in параметр - только для читки с гарантией неизменения.

in-out - подстановка по ссылке в общем смысле. пригодна для всех случаев жизни.

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от alysnix

если только возврат тупла не релизован, как скрытые out параметры :)

А, ну да. Код вообще весь неэффективный. Если только не писать его нормально. Языками вообще пользоваться невозможно по причине неэффективности. Если только не включать оптимизации.

Какие манёвры. Все неудобные случаи не считаются. Ну бывает.

anonymous
()
Ответ на: комментарий от Kogrom

Это то. Да и я не вижу этого заполнения в исходном примере. Зачем придумывать новые условия? Почему ты сразу о заполнении не подумал?

Как вернуть заполненные - также, как и в остальных случаях.

a{...} => x{a{...}, ...} // либо emplace
a = ... => x.first = ...
a.f(...) => x.first(...)
anonymous
()
Ответ на: комментарий от anonymous

А, ну да. Код вообще весь неэффективный. Если только не писать его нормально. Языками вообще пользоваться невозможно по причине неэффективности. Если только не включать оптимизации.

бубубу… а где доводы-то? если не делать возврат тупла через скрытый out параметр, то его нужно возвращать значением через временную переменную, в общем случае - не стеке. а потом присваивать в приемник.

то есть имеем послеовательность -

резервируем место под значение тупла на стеке - temp_var. 
вызываем функцию. 
пишем в temp_var значение результата. 
выходим из функции,  
пишем dst := temp_var;

естессно это затратней, чем сразу писать в приемник из функции, имея адрес приемника. единственная затрата тут - на передачу адреса.

alysnix ★★★
()
Ответ на: комментарий от LamerOk

Конкретно тут сделано так, чтобы можно было передать один из множества разных контейнеров, каких именно - можно посмотреть среди конструкторов OutputArray (https://docs.opencv.org/3.4/d2/d9e/classcv_1_1__OutputArray.html).

По этой же причине, например, std::transform принимает выходной контейнер (точнее, итераторы на него) как параметр. Управление возвращаемым типом в цпп неэргономично.

unC0Rr ★★★★★
()

Выходных параметров несколько, а результат у функции один (и понадобится лишняя операция упаковки нескольких значений в кортеж, а затем распаковки из кортежа). Это усугубляется тем, что кортежи не описаны в ABI и везде разные, это мешает интеграции библиотек.

Shushundr ★★★★
()
Ответ на: комментарий от LamerOk

И думаю, проблемы будут именно рантаймовые, если взять и в качестве каждого из массивов передать std::vector<bool>, сигнатура вроде этого не запрещает.

unC0Rr ★★★★★
()
Последнее исправление: unC0Rr (всего исправлений: 2)
Ответ на: комментарий от unC0Rr

если взять и в качестве каждого из массивов передать std::vector, сигнатура вроде этого не запрещает.

Конструктор шаблонный, и вектор должен быть инстанцирован корректным типом, чтобы компиляция прошла. Но во время выполнения проходят проверки на размерность матриц и ассёрт выплёвывает конкретное нарушенное условие.

ОП просто брутфорсит код, в надежде, что opencv сдаться первым.

LamerOk ★★★★★
()
Ответ на: комментарий от unC0Rr

если взять и в качестве каждого из массивов передать std::vector<bool>

Как раз нет, специализация на std::vector<bool> явно удалена:

_InputArray(const std::vector<std::vector<bool> >&) = delete;
LamerOk ★★★★★
()
Последнее исправление: LamerOk (всего исправлений: 1)

Есть вот например istream& getline(istream&, string&), можно вообразить функцию с похожей семантикой и сигнатурой вроде string getline(istream&). Подразумевается, что используется такая функция в каком-нибудь цикле — читает строку, как-то её обрабатывает, переходит к следующей. Первая сигнатура при этом позволяет переиспользовать одну и ту же строку, у которой буфер быстро вырастет до нужного размера и больше не будет переаллоцироваться; вторая сигнатура требует создания новой строки каждый раз.

Но часто дело и правда в сишных дедах, которые по-другому не умеют.

Softwayer ★★
()
Последнее исправление: Softwayer (всего исправлений: 1)

А как иначе? Когда возвращаешь - там непонятно, кто что выделил, как освобождать, всякие там конструкторы копирования и прочая муть. А через указатель передал и всё, всё просто и понятно. Я другой подход себе и не представляю. Ну вернуть там простой int ещё ладно, а какой-то нетривиальный объект - проще через указатель, чем всё усложнять на ровном месте.

vbr ★★★★
()
Последнее исправление: vbr (всего исправлений: 1)
Ответ на: комментарий от vbr

Да через указатель приблизительно ничем не лучше, т.к. надо гадать надо, где указатель передаётся просто на посмотреть, а где нужно ожидать изменения.

Да, есть const, но как много людей им правильно пользуется на указателях? В C/CPP нет явного маркера мутабельности.

Bfgeshka ★★★★★
()
Ответ на: комментарий от thunar

возвращаемые значения — это создание новых объектов и возвращение их из функции

Это от реализации зависит.

можно int foo() трактовать как сахарок для foo(int&)

Psilocybe ★★★★
()

В итоге: непонятно, что же именно функция возвращает

В WinApi и Pascal есть обозначения для таких параметров. Тут скорее проблема OpenCV если там нету.

https://learn.microsoft.com/ru-ru/cpp/code-quality/best-practices-and-example...

Зачем и почему?

1. Может быть желание возвращать несколько значений, и необязательно все, а только те что не NULL.

2. Не надо конструировать значение в функции, можно передать это пользователю, полезно если у него уже есть объекты под заполнение.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 1)

смотри эволюцию прикручивания «грязных» функций (в восходе времён рутины над общем океаном слов)

вопрос(ваш/наш) из разряда кто чистит стек от входных аргументов и в целом каков протокол декомпозиции

на днях случился инсайт от фразы:

прикручивание хранимой программы вынудило вычислители(эвм-лампы) стать последовательными устройствами

по out-параметрам - в «аде» было - «деды плохо не»

уменьшение копирования например - и да такой параметр создаёт дополнительный буккипинг в случаях когда это создание out-хранилища

опятже out - это не inout - т.е вариант допускается что в этом хранилище предназначеном на экспорт из рутины возможно нарушены (на входе) инварианты типа возващаемого - т.е произвольный(в рамках следа размера типа (не рекурсивного :) ) кусок памяти

если совсем абстрактно out -это попытка оптимизации наложением ограничений на параметр

qulinxao3 ★★
()
Последнее исправление: qulinxao3 (всего исправлений: 1)
Ответ на: комментарий от Norgat

Если там не просто число, а матрица какая-нибудь, то это делается чтобы разработчик мог на этапе инициализации выделить все буфферы, а потом с этими буфферами работать без дополнительных операций выделения памяти.

Да, память кушает только в путь. resize решает.

LongLiveUbuntu ★★★★★
() автор топика

Хорошо, с потреблением памяти разобрались. Тогда вопрос, чтобы темы не плодить: вот я стильный, молодежный (нет), как мне сделать возврат значений через предоставленные мне таплы, чтобы расход памяти был наименьшим?

LongLiveUbuntu ★★★★★
() автор топика

Затем, что далеко не всегда одного возвращаемого результата достаточно. Приходится либо рожать контейнер в возвращаемом значении с риском получить deep copy, либо разбивать функцию на 2, делающие частично одну и ту же работу (для функций, работающих с изображениями, это может оказаться овердофига, например).

А с несколькими выходными параметрами красота – явно объявил их как неконстантные ссылки и работай.

hobbit ★★★★★
()
Последнее исправление: hobbit (всего исправлений: 1)
Ответ на: комментарий от LamerOk

Прежде чем брызгать слюной на людей, ты бы почитал матчасть и увидел, что в openCV это довольно-таки абстрактный тип, вместо которого в фактические параметры обычно подставляют что-то другое, и понимание, что именно, там не всегда тривиально.

К примеру, в API для метода Хафа в прототипе объявлены InputArray и OutputArray, а подставляют в реальной жизни обычно cv::Mat и vector<Vec3f> (а в ещё более реальной жизни эту написанную на C++ библиотеку дёргают из программ на Питоне, авторам которых на это вот всё вообще параллельно).

Так что да, в openCV не всё удачно сделано (но это не значит, что сама идея выходных параметров плоха).

hobbit ★★★★★
()
Последнее исправление: hobbit (всего исправлений: 4)
Ответ на: комментарий от alysnix

бубубу… а где доводы-то?

Так показывай доводы у себя. Ты утверждаешь наличие разницы, поэтому доводы должны быть у тебя. Вот берёшь простой пример и показываешь неэффективность return относительно out. Фантазии выше мимо.

anonymous
()
Ответ на: комментарий от hobbit

Ну вот: связался с компьютерным зрением, а там OpenCV в мейнстриме, а в ней вот так. А я человек, который прошел через Паскаль и Боба Мартина и душа моя восстает при виде вот такого использования параметров функций. Понимаю, что можно, и сам так делал, но... Тут еще и golang повлиял, после знакомства с ним. Вопрос в том, как в таком случае минимизировать накладные расходы ибо сотни нефти гигабайт памяти у работодателя не попросишь.

LongLiveUbuntu ★★★★★
() автор топика

Discalmer 1: Ниже я предполагаю что речь идёт о возвращении из функции значения, а не указателей, ссылок и т. п.

Discalmer 2: Я надеюсь ТС понимает, что С++ - это язык, который будет делать ровно то, что напишет программист; что есть благословение и проклятье (в зависимости от уровня компетенции этого программиста).

  1. Ограничения памяти. Как правило значение возвращается через стек. Если нужно вернуть большой объём данных, можно упереться в размер стека - stack overflow.

  2. Быстродейстие. Когда ты возвращаешь через return, то возникает копирование. Для сложных объектов это требует дополнительной логики, как минимум убедиться, что конструктор копирования работает как нужно, а синглтонами вообще будет весело. Ну и это занимает время.

  3. Возвращение нескольких значений. В С++ нет синтаксиса для возврата нескольких значений. (идеи вроде tuple не в счёт, так как это скорее workaround, и я бы не назвал читабельными). И на мой взгляд это неспроста. Функция должна работать с чем-то одним, и это одно возвращать. Если несколько значений между собой связаны - то это объект, пожалуйста, объявляйте, обзывайте это так, чтобы было понятно что это, задавайте правила консистенции этих значений через методы; и это будет намного читабельней, чем пачка значений из функции. Оставшийся сценарий - это возврат кода ошибки; ну так для этого есть исключения. Вобщем, если у человека в голове порядок, то это прекрасно ложится на синтаксис С++.

Kroz ★★★★★
()
Ответ на: комментарий от Kroz

То есть мне проще обернуть эти значения в класс, сделать там конструктор перемещения и возвращать как анонимус выше. Или сдаться и возвращать через стек параметров.

LongLiveUbuntu ★★★★★
() автор топика
Ответ на: комментарий от LongLiveUbuntu

Это если есть MyStruct(MyStruct&& other)

Он есть по определению (в исходном сообщении)

и оно не делает лишних копий, как я понимаю.

Всё зависит от кривизны рук.

anonymous
()
Ответ на: комментарий от vromanov

Не осилил прочитать комментарии? Там не так всё просто.

Характерно, кстати, что в оф.документации прототип той же HoughCircles() даже не приводят, а сразу показывают правильный пример вызова с cv::Mat. И неспроста. Но по-хорошему ведь сам прототип должен намекать.

hobbit ★★★★★
()
Ответ на: комментарий от LongLiveUbuntu

Конкретно по OpenCV –делать по примерам.

А если свою библиотеку функций делать или просто большой проект – при необходимости сложных выходных параметров объявлять их как неконстантные ссылки, будет и всё оптимально.

hobbit ★★★★★
()
Ответ на: комментарий от LongLiveUbuntu

То есть мне проще обернуть эти значения в класс

Может и не «проще», но зато читабельний и надёжней.

В нормальном коде нет сценария когда возвращается N несвязанных между собой параметра.

Kroz ★★★★★
()
Ответ на: комментарий от Ygor

сделать структуру с нужными полями и возвращать ее.

Это плюс память.

Разумеется возврат значений по ссылке в общем случае это самый экономный вариант, особенно если надо вернуть че то толстое вроде вектора. Все остальные варианты компалйер может конечно заоптимизировать, а может и нет.

Но выглядит возврат по ссылке крайне убого;-(

AntonI ★★★★★
()
Последнее исправление: AntonI (всего исправлений: 1)
Ответ на: комментарий от Kroz

Возвращение нескольких значений. В С++ нет синтаксиса для возврата нескольких значений. (идеи вроде tuple не в счёт, так как это скорее workaround, и я бы не назвал читабельными). И на мой взгляд это неспроста.

Ага. Плюсовый комитет аки птица говорун отличается умом и сообразительностью, но весьма нетороплив.

Функция должна работать с чем-то одним, и это одно возвращать.

Схерали, пардонте?! Возврат нескольких значений из функции это стандартная ситуация, а вариант возврата только по ссылкам/одной структурой просто костыль. Не надо оправдывать слоупочность разработчиков новых фич в плюсах некой гипотетической заботой о пользователях и чистоте кода которые эти пользователи пишут.

AntonI ★★★★★
()
Ответ на: комментарий от hobbit

Прежде чем брызгать слюной на людей, ты бы почитал матчасть и увидел, что в openCV это довольно-таки абстрактный тип,

Абстрактный тип в C++ - это класс с чисто виртуальной функцией. А InputOutputArray - это абсолютно конкретный класс, экземпляр которого можно создать.

подставляют в реальной жизни обычно cv::Mat и vector<Vec3f>

Да. И? Перегруженные конструкторы позволяют прозрачно создать временную копию. Каким образом это превращает класс в «абстрактный»?

Что за гуманитарное развозюкивание ты тут устраиваешь с «довольно таки абстрактными» типами?

ты бы почитал матчасть

Это не мне надо читать матчасть, а ОПу, чтобы понять, как пользоваться интерфейсом.

А тебе надо прекратить сердобольные бабушкины охания при обсуждении технических вопросов. Пользы от них людям никакой, а вред весьма конкретный.

LamerOk ★★★★★
()
Ответ на: комментарий от LongLiveUbuntu

cv::calibrateCamera(objectPoints, imagePoints, frameSize, cameraMatrix, distCoeffs, rvecs, tvecs);

Догадайся какой тип должен иметь каждый параметр.

Посмотри код этих функции.

Если не нужно, rvecs или tvecs то можно передать noArray(). И их заполнение будет пропущено.

Впрочем, такой подход еще много где видел. Зачем и почему?

  • Как пример выше, опциональный выходной параметр.
  • API пришедшее из Си.
  • Микро-оптимизации.

Вот можешь поиграть c

int foo_bar_1(Foo *foo, Bar *bar);
std::optional<std::tuple<Foo,Bar>> foo_bar_2();

https://godbolt.org/z/nqG673h5d

Обрати внимание на выравнивание и что смог компилятор оптимизировать. Поиграй с уровнями оптимизации и компиляторами.

Понятно, что в обычном прикладном коде, всё это не желательно видеть.

AlexVR ★★★★★
()

Выходные параметры позволяют пользователю функции гибче управлять памятью. Т.е.

std::vector<int> vecop_1(const std::vector<int>& a, const std::vector<int>& b);
void vecop_2(const std::vector<int>& a, const std::vector<int>& b, std::vector<int>& out);
 
//

void user1() {
   for (int i = 0; i < 100500; ++i) {
       c = vecop(a, b);
   }
}

void user2() {
   std::vector<int> out;
   for (int i = 0; i < 100500; ++i) {
       out.clear();
       vecop(a, b, out);
   }
}

В первом случае у пользователя функции нет никаких других опций, кроме как аллоцировать новый вектор в куче на каждой итерации. Во втором случае он может:

  1. Переиспользовать память с прошлой итерации;
  2. Объединять результаты в один большой вектор.
snizovtsev ★★★★★
()
Ответ на: комментарий от AntonI

Схерали, пардонте?! Возврат нескольких значений из функции это стандартная ситуация, а вариант возврата только по ссылкам/одной структурой просто костыль. Не надо оправдывать слоупочность разработчиков новых фич в плюсах некой гипотетической заботой о пользователях и чистоте кода которые эти пользователи пишут.

мне интересно, а в каких ЯП из функции можно вернуть более одного значения? Я таких что-то не помню. Обычно, используют кортеж значений, но кортеж это тоже - ровно один результат функции. В языках типа питона, кортеж это тот же вектор, только без мутаторов. В других языках, это синтаксический сахар над структурами. Где она эта «стандартная ситуация» то, в каком стандарте?

FishHook
()