допустимость возвращаемого значения лямбда-функции - PullRequest
0 голосов
/ 16 мая 2018

Я вернулся к программированию на C ++ спустя много лет, и у меня есть некоторые сомнения.

Я создал эту функцию:

typedef  std::function<const char *(void)> GetMessageLog;

void addLog(byte logLevel, GetMessageLog get)
{
  if (loglevelActiveFor(LOG_TO_SERIAL, logLevel)) {
    Serial.print(millis());
    Serial.print(F(" : "));
    Serial.println(get());
  }
  if (loglevelActiveFor(LOG_TO_SYSLOG, logLevel)) {
    syslog(logLevel, get());
  }
  if (loglevelActiveFor(LOG_TO_WEBLOG, logLevel)) {
    Logging.add(logLevel, get());
  }    
}

Я бы хотел использовать его следующим образом:

addLog(LOG_LEVEL_INFO, [&]()
{
  String log = F("HX711: GPIO: SCL=");
  log += pinSCL;
  log += F(" DOUT=");
  log += pinDOUT;
  return log.c_str();
});

Действительность log.c_str () гарантируется до тех пор, пока addLog не закончится или если что-то прерывает нормальный программный поток (любой обработчик события), строковый объект уничтожается?

Ответы [ 4 ]

0 голосов
/ 16 мая 2018

Добро пожаловать в C ++!Вы обнаружите, что в этом столетии он немного изменился, и в лучшую сторону.

Объект "String log" живет только во время вызова addLog.Вы не можете вернуть log.c_str (), потому что это вернет висячий указатель на объект, который больше не будет существовать после возврата.Решение простое - просто верните сам «лог».Пусть эта функция (и GetMessageLog) возвращают не "char *" старого C, а современный "std :: string" C ++.

В старом C ++ возвращение std :: string из функции былочасто осуждают, потому что это всегда включает копирование этой строки, иногда несколько раз.Это больше не соответствует действительности с появлением конструкторов перемещения (которые, вероятно, являются наиболее важной новой функцией в C ++ 11).Функция создает строку, и при ее возврате строка не копируется, а скорее «перемещается», что включает в себя копирование только указателя, который она содержит, в свой массив данных, но не копирует сам массив.

Inв современном C ++ вы очень редко будете использовать голые указатели старого стиля, как в этом примере использовали char *.Обычно вы будете использовать объекты, такие как std :: string вместо char *, контейнеры, такие как std :: vector вместо int *, умные указатели, такие как std :: unique_ptr вместо T *.Все эти объекты безопаснее, чем простые указатели, потому что они дают вам меньше шансов испортить время жизни объекта и безопасны для исключений (т. Е. Если в середине кода возникает исключение, вы не забудете освободить его).память, которую вы выделили).

0 голосов
/ 16 мая 2018

Это API Arduino? Таким образом, вы вызываете UB, String уничтожает свои ресурсы при выходе из закрытия.

Технически, если вы измените тип

typedef  std::function<String(void)> GetMessageLog;

тогда вы можете написать

addLog(LOG_LEVEL_INFO, [&]() -> String
{
  String log = F("HX711: GPIO: SCL=");
  log += pinSCL;
  log += F(" DOUT=");
  log += pinDOUT;
  return log;
});

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

0 голосов
/ 16 мая 2018

String является локальным для лямбды, поэтому (при условии, что он более или менее похож на std::string), вы не можете использовать c_str в качестве возвращаемого значения, потому что, когда вызывающая сторона получит к нему доступ, локальный объект уже мертв.

Еще одна потенциальная проблема заключается в том, что вы захватываете по ссылке с помощью [&] переменных pinSCL и pinDOUT. Если лямбда хранится отдельно и ее время жизни истекает после времени жизни этих двух переменных, то вызывать ее также будет неопределенным поведением.

0 голосов
/ 16 мая 2018

Это на самом деле зависит от того, что такое String, но, скорее всего, возвращаемое значение log.c_str() действительно только в том случае, если сам log (это, безусловно, будет в случае std::string). Это означает, что в вашем случае его вообще нельзя использовать: когда возвращается лямбда, log уничтожается, поэтому указатель, возвращаемый из лямбды, уже болтается.

К счастью, решение простое. Измените тип возвращаемого значения лямбды на String и вместо него верните log. Если вам в конечном итоге понадобится const char*, вы можете вызвать c_str() для возвращаемого значения, где у вас будет намного лучший контроль над временем жизни.

...