LINUX.ORG.RU

Субботний C++ (как правильно сообщать о не успехе)

 


0

2

Продолжаю осваивать С++. И возник вопрос как сделать правильно следующую вещь…

Есть некий метод некоего объекта. Метод в случае успеха создает некий другой объект. Ну и естественно хочется возвращать его через return.

Но возможен другой ход развития событий, когда успеха не было. Это штатная ситуация. В си я бы похожую задачу решал бы посредством возвращения указателя на структуру. Либо указатель на созданную, либо null. Снаружи типа анализируем, и решаем что делать дальше.

А вот как такое правильно делать in cpp-way?

Все классы и методы мои, могу написать что захочу. А вот какие варианты идейно верные?

★★★

а) Поступить как в си, только разве что поиграться с управлением памятью через «умные» указатели

б) Ввести для возвращаемых объектов общего предка, например позволяющего проверить его состояние и на всякий случай сделать виртуальные методы, что при обращении к ним не от допустимого(рабочего) потомка будут как-то уведомлять разработчика чем нибудь привлекающим внимание будь то исключение или жирный красный текст с надписью «Самостирание ПК через 3 … 2 … 1 …»

В целом единого способа нет - всё зависит от задачи и навыков разработчика, как по мне чем предсказуемее - тем лучше.

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

Это штатная ситуация

Если это штатная ситуация, то возвращаемый объект должен быть с состоянии её отражать. Т.е. объект возвращается в любом случае, но потом при вызове его методов, вместо работы он сообщит, что облом.

no-such-file ★★★★★
()

Объект создаётся на стеке, я так понимаю, иначе также можешь вернуть nullptr. Можно так:

#include <utility>
#include <iostream>

std::pair<bool, int> fn(bool er)
{
	if (er)
		return {false, {}};
	else
		return {true, {}};
}

int main() {
	auto [error, data] = fn(true);
	if (!error)
		std::cout << data << std::endl;
	return 0;
}

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

Никто не запрещает тебе сделать тоже самое как и на си. Но если этот «некий другой объект» живет только в пределах стека есть std::optional.

Ну и естественно хочется возвращать его через return. Возвращаешь пустой обьект. Напишешь для него метод isEmpty. Если прямо так уж нужно через return.

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

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

ИМХНО классы не отвергаю, но зачастую их можно/нужно не использовать.

Много больших проектов разработано с использованием C.
Казалось бы «Как эти бедняги без классов живут?», ан «живут припеваючи»

Владимир

anonymous
()

https://www.stroustrup.com/

https://isocpp.org/

«in cpp-way» принято большим контекстом определяться ибо тут многое(не всё) тоже закат солнца в ручную.

зависит от стиля - например если цепоченый(конвейер из методов) - то удобней «вариант» с состояниями неуспеха

и да как выше тут отметили для плюсов классы не обязательны - в плюсах важнее(имхо) способность подстроить поведение типа(в том числе класса) и в дальнейшим опираться на то что обычный(в смысле старого_си) синтаксис будет компилироваться в тобой(шире автором типа) определённое поведение.

qulinxao3 ★★
()

Всем спасибо!

Буду разбираться и думать… ;-)

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

It depends.

  • unique/shared ptr
  • optional
  • exceptions

И да, error_code by reference. Нынче модно стало в std::fs :)

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

std::optional, или написать что-то типа result<R, ERR>, или std::unique_ptr с нулевым или ненулевым значением. Или внутри возвращаемого класса сделать isValid.

anonymous
()

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

Либо исключения, почему нет.

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

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

В том, что C++ я не знаю и на вопрос ТСа ответить не могу, так языком почесать зашел. А вообще да, логично было бы предположить существование аналога.

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

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

А потом оказывается что долбоящеры все.

MOPKOBKA ★★★★
()

Instance* OtherClass::Create(int23_t& err_code_ret);

Это если исключения не нужны. С исключениями - можешь унутри Create их гененрировать.

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

Да, вариант с дополнительным кодом ошибки у меня тоже просился в комментарий, но я его счёл менее элегантным (хотя если вариантов ошибки может быть больше одного — вполне себе альтернатива исключениям).

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

Ага, обычно для вариантов ошибок приняты соглашения навроде err == 0 - «все полностью хорошо, продолжаем», err < 0 - «критичная ошибка», err > 0 - «не всё идеально, но можем продолжить выполнение».

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

Так тут вопрос дизайна (или понимания архитектором контекста в котором возникает ошибка): а) ошибка (состояние ошибки) связано с одной единственной функцией, б) с целой подсистемой (семейством функций). Понятно, что для случая а) достаточно и bool, но тогда при написании всяких обработчиков нужно их плодить разными на каждую прихоть дизайнера ошибок. В случае же системного подхода к обработке ошибок - возникают коды состояний и единообразная схема работы с ними (как правило - конечный автомат).

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

В си я бы похожую задачу решал бы посредством возвращения указателя на структуру. Либо указатель на созданную, либо null.

Делать также и не заморачиваться. Функционал C++ надо использовать только если в нём есть необходимость, фичи ради фич не нужны.

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

Instance* OtherClass::Create(int32_t& err_code_ret);

Почему так? Может лучше int32_t OtherClass::Create(Instance* obj)? То есть объект передаём по указателю или ссылке, а код ошибки возвращаем. Такой способ лучше тем, что для кода ошибки можно не заводить переменную, прямо отправляя результат функции в какой-нибудь case.

Если непременно надо возвращать объект, то лучше тогда через исключения действовать.

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

Вот это. Либо std::variant где второй тип — класс ошибки.

+1.

std::optional либо std::variant

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

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

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

Есть некий метод некоего объекта. Метод в случае успеха создает некий другой объект. Ну и естественно хочется возвращать его через return.

Но возможен другой ход развития событий, когда успеха не было. Это штатная ситуация. В си я бы похожую задачу решал бы посредством возвращения указателя на структуру. Либо указатель на созданную, либо null. Снаружи типа анализируем, и решаем что делать дальше.

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

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

Может возвращать std::optional?

это если прям так уж захотелось возвращать по значению. технически std::optional, это копия с приделанным к нему булом. зачем этот наворот, если у него уже есть указатель? например optional от указателя, это вообще тавтология, поскольку указатель итак имеет легальное нулевое значение.

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

это если прям так уж захотелось возвращать по значению. технически std::optional, это копия с приделанным к нему булом. зачем этот наворот, если у него уже есть указатель?

Чтобы не обосраться на ровном месте. Тысячи программистов пишут код, разыменовывающий нулевой указатель. Вот чтобы так не делать, изобрели std::optional.

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

Имеет, только его лучше выпилить и забыть о его существовании.

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

Чтобы не обосраться на ровном месте. Тысячи программистов пишут код, разыменовывающий нулевой указатель. Вот чтобы так не делать, изобрели std::optional.

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

пусть пишут на питоне, если не отличают нулевого указателя от валидного.

обращение по нулевому указателю будет ровно также обнаружено системой эксепшеном, как и облом в рантайме при использовании optional c невалидным значением.

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

Почему так? Может лучше int32_t OtherClass::Create(Instance* obj)?

Потому, что ваш пример не работает, чтоб он заработал нужно делать так int32_t OtherClass::Create(Instance** obj); Или если это ++ - то возвращать указатель по ссылке.

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

Вот набор функций из OpenCL: clCreateBuffer, clCreateContext, … (вообще любая clCreate*).

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

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

Именно поэтому C и C++ нужно отправить на помойку истории!

обращение по нулевому указателю будет ровно также обнаружено системой эксепшеном, как и облом в рантайме при использовании optional c невалидным значением.

Обращение по нулевому указателю – undefined behaviour. Компиляторы очень часто генерят код, расчитывая, что указатель не нулевой, и это порождает просто море лулзов при дебаге. Можешь сам поискать примеры.

Так что исключение из std::optional – гораздо лучше, потому что оно всеми компиляторами во всех случаях будет сгенерировано одинаково. Но вообще за прямое разыменование optional надо по рукам бить. Для нормальных людей есть std::visit.

пусть пишут на питоне, если не отличают нулевого указателя от валидного.

С твоей уверенностью в своих силах, я очень надеюсь, что ты пишешь на пистоне :)

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

Обращение по нулевому указателю – undefined behaviour. Компиляторы очень часто генерят код, расчитывая, что указатель не нулевой, и это порождает просто море лулзов при дебаге.

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

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

поскольку указатель итак имеет легальное нулевое значение

Я несколько раз так уже попадал (в своем и чужом коде) и потом долго матюгался) То, что ты говоришь - это смешивание разных множеств значений. Нулевой указатель это может быть не просто отсутствие значения, это еще может быть непредвиденная ошибка (в логике например). В случаи std::optional у нас либо явно есть инстационированный объект, либо явно его нет.

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

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

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

Ты не понял)

Смотри, возможны три ситуации:

  1. Вернули действительный указатель на объект, все хорошо.
  2. Не смогли подключится к серверу, файла нужного не нашли и т.д. - вернули нулевой указатель. По сути это рантаймовая ошибка.
  3. Косяк в коде (вот только не надо говорить, что настоящие программисты сразу пишут правильно, ага) - вернулся нулевой указатель. А вот это уже ошибка логики.

При использовании указателя нельзя в результате вызова функции отличить вторую ситуацию от третьей. И проблема в логике маскируется под нормальную работу кода (ну не виноваты мы, что в БД нужной записи не оказалось). А потом долго сидишь и ковыряешься с отладчиком, когда это всплывет.

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

Ты не понял в чем суть уб. Когда программа потенциально дерефененсит нулл, она становится невалидной, а компилятор считает, что такую программу ему в своем уме не скормят. Поэтому он может сам себе доказать, исходя из того, что такой ptr всегда не нулл, что-то, что приведет к такому бинарнику, которого ты даже не ожидал. Это не потому что он вредный, а потому, что он пытается решить задачу на поиск некоего оптимума на основе заданных констрейнтов, которые представлены в виде кода. Чем больше констрейнтов, тем лучше оптимум, поэтому непременная валидность кода подразумевается (нет никакого смысла оптимизировать невалидную задачу, как и терять перформанс на рантайм проверках того, чего быть не должно по стандарту). Уб нихрена не легкая ошибка, если только ты не юзаешь наколеночный компилер 1:1 транслятор в машинный код из 80-х.

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

А потом долго сидишь и ковыряешься с отладчиком, когда это всплывет.

и как тебе поможет optional в этом случае? по нормальному надо делать какой-то код ошибки, и …ну например… рисовать класс(хотя и иных вариантов много)

class Result {
  ErrorKind _error; ///тут много всяких вариантов ошибки
  Value _result;
}

то есть расширять optional на разные варианты ошибки…впрочем я optional никогда не использую…может и его можно расширить на варианты ошибки. то есть тащить разные варианты ошибки вместе с результатом.

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

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

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

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

Это предлагали уже 100500 триллионов раз, называется «скучная сишка без уб». Но уб это весело, и общий консенсус по психбольнице в том, что генерить такие проверки ненужно…

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

то есть используя optional, ты везде проверяешь валидность по доп. встроенному полю…даже проверив уже раз сто. разве нет? если да… оно тебе нужно?

когда я говорил про опциональную встроенную проверку указателя, то это для параноидальной отладки, разумеется в продакшене такое оставлять не стоит

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

Ты не понял в чем суть уб. Когда программа потенциально дерефененсит нулл, она становится невалидной, а компилятор считает, что такую программу ему в своем уме не скормят.

Гонишь! программа что дереференсит нулл, называется тестом обработки системой харверного прерывания - обращение по нулевому адресу, или неверному адресу. это вполне себе полезная программа для тех, кто пишет кернел например :)

может и на ноль поделить уже нельзя??? это тест обработки системой неверной операции.

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

Прикольная оптимизация):

if (int_i != 0)
    int_q \ int_i;

Ну а чего, компилятор посмотрит, что я делю на int_i, следовательно, int_i явно не ноль, и if вырежет ). Аналогично с указателем, ну не смешите, это не место для оптимизаций.

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

Аналогично с указателем, ну не смешите, это не место для оптимизаций.

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

ну если только не было неинициализированного указателя, и последующей попытки по нему обратиться. тогда за счет попытки понять семантику хотя бы локально, он может ругнуться. например такая штука проходит и в gcc и в clang-11(хотя могла б и не проходить :) )

void ff(){
  int *ptr = nullptr;
  *ptr=0;
}

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

Потому, что ваш пример не работает, чтоб он заработал нужно делать так int32_t OtherClass::Create(Instance** obj); Или если это ++ - то возвращать указатель по ссылке.

Это придирки. Я текстом написал, что передавать по ссылке или указателю. Кроме того, можно объект конструировать не на указателе, а из пустого объекта:

struct MyObject
{
    string name;
    string surname;
};

int Create(MyObject *object)
{
    object->name = "John";
    object->surname = "Smith";
    return 1;
}

int main()
{
    MyObject object;
    if(Create(&object))
    {
        cout << object.name << " " << object.surname;
    }
    return 0;
}

Всё. Работает. И без всяких опасных нулевых указателей.

Если надо чтобы объект был не в стеке, а в куче, то использовать shared_ptr и опять будет только одна звёздочка. И это уже будет современный C++, а не просто C, с ручным управлением памятью.

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

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

Ты хоть спеку языка, на котором пишешь, читал?

http://www.open-std.org/jtc1/sc22/WG14/www/docs/n1256.pdf

Статья 6.5.3.2, пункт 4:

If an invalid value has been assigned to the pointer, the behavior of the unary * operator is undefined.87)

И ниже, сноска 87:

Among the invalid values for dereferencing a pointer by the unary * operator are a null pointer, an address inappropriately aligned for the type of object pointed to, and the address of an object after the end of its lifetime.

Компилятор генерит код в расчёте, что у тебя при разыменовании указателя в нём никогда не будет NULL. Всё. Точка.

hateyoufeel ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.