lambda函数返回值

时间:2018-05-16 07:59:05

标签: c++ c++11 lambda arduino esp8266

我多年后回到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 :(得分:4)

这实际上取决于String是什么,但最有可能的是,log.c_str()的返回值仅在log本身有效时才有效({{{}} {1}})。这意味着在你的情况下,它根本不可用:当lambda返回时,std::string被销毁,所以从lambda返回的指针已经悬空了。

幸运的是,解决方案很简单。将lambda的返回类型更改为log,并让它自己返回String。如果您最终需要log,则可以在返回值上调用const char*,您可以更好地控制生命周期。

答案 1 :(得分:1)

是Arduino API吗?这样你就会导致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;
});

如果编译器不支持命名返回值优化,请将其转换为单行,以减少复制操作的数量。

答案 2 :(得分:1)

String是lambda的本地(假设它或多或少像std::string)你不能使用c_str作为返回值,因为当调用者访问它时,本地已经是死。

另一个潜在的问题是,您通过[&]变量pinSCLpinDOUT来参考。如果lambda被存储起来并且它的生命周期在这两个变量的生命周期之后结束,那么调用它也是未定义的行为。

答案 3 :(得分:1)

欢迎回到C ++!你会发现它在本世纪已经发生了很大变化,并且变得更好。

对象“字符串日志”仅在调用addLog期间存在。您无法返回log.c_str(),因为这将返回一个悬挂指针,该指针指向一个在返回后将不再存在的对象。解决方案很简单 - 只需返回“log”本身。让这个函数(和GetMessageLog)不是旧式C“char *”,而是现代C ++“std :: string”。

在旧的C ++中,从函数返回std :: string经常不受欢迎,因为它总是涉及复制该字符串,有时多次。随着移动构造函数的出现(这可能是C ++ 11中最重要的新特性),这已不再适用。该函数构建一个字符串,当返回它时,字符串不会被复制,而是“移动” - 这涉及只复制它保存到数据数组的指针,而不是复制数组本身。

在现代C ++中,您很少使用旧式裸指针,就像您在此示例中使用char *一样。您通常会使用像std :: string而不是char *这样的对象,像std :: vector这样的容器而不是int *,像std :: unique_ptr这样的智能指针而不是T *。所有这些对象比裸指针更安全,因为它们给你更少的机会来破坏对象的生命周期,并且是异常安全的(即,如果代码中间发生异常,你不会忘记自由你分配的记忆。)