C++ lambda函数返回值的有效性

C++ lambda函数返回值的有效性,c++,c++11,lambda,arduino,esp8266,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(" : ")

多年后,我又回到了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());
  }    
}

log.c_str()的有效性得到保证,直到addLog结束,或者如果有什么东西中断了正常的程序流(任何事件处理程序),string对象被销毁?

这实际上取决于
string
是什么,但最有可能的是,
log.c_str()
的返回值只有在
log
本身有效时才有效(对于
std::string
),情况肯定是这样的。这意味着在您的情况下,它根本不可用:当lambda返回时,
log
被销毁,因此从lambda返回的指针已经挂起


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

这是Arduino API吗?这样您会导致UB,字符串在退出闭包时销毁其资源

从技术上讲,如果你重新设计类型

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

如果编译器不支持命名返回值优化,则将其转换为一行代码以减少复制操作量。

String
是lambda的局部变量,因此(假设它或多或少类似于
std::String
)您不能使用
c_str
作为返回值,因为当调用者访问它时,本地已经死了


另一个潜在的问题是,您正在使用
[&]<代码>变量>代码> PNSCL和<代码> PUDOUT 。如果lambda被存储掉,在这两个变量的生命周期结束后,它的生命周期结束,它也将是未定义的行为。

< P>欢迎回到C++!本世纪它已经有了很大的变化,而且更好。

对象“字符串日志”只在Adlog调用期间才存在。不能返回log .cyString(),因为这将返回一个悬空指针到返回后不再存在的对象。解决方案很简单——只返回“日志”本身。这个函数(和GetMessageLog)返回的不是旧的C“char”,而是一个现代C++。“std::string”

在旧C++中,从函数返回STD::string通常是不赞成的,因为它总是涉及复制该字符串,有时是多次的。这是不再正确的,随着移动构造函数的出现(这可能是C++ 11中最重要的新特性)。。函数生成一个字符串,当返回该字符串时,该字符串不会被复制,而是被“移动”——这涉及到只复制它持有的指向其数据数组的指针,而不会复制数组本身


在现代C++中,很少使用像你在这个例子中使用char *的旧样式的裸指针。你通常会使用像STD::string代替char *之类的对象,比如STD::vector,而不是int *,像STD::UnQuyJ-PTR而不是T*的智能指针。所有这些对象比裸露指针更安全,因为它们给了你更少的代码。E将对象的生命周期弄乱,并且是异常安全的(即,如果在代码的中间出现异常,您不会忘记释放您分配的内存)。

不,<代码> STD::String < /C> >不受所有平台支持(ISO实际上不需要实现所有类)String是一个嵌入式平台的东西。如果是那个平台,那么代码> UNQuyGyPTR < /C> >根本不存在。STD::String绝对是C++ 11标准的一部分。STD::UnQuyJ.PTR。如果你不使用标准C++,而不是使用全套库,而是使用一些不同类的修剪版本,那么n也许你没有,但很可能你有非标准的变体,比如“字符串”“,这可能类似于std::string,并且它有一个move构造函数,我上面所说的一切也适用于它。这是该平台的供应商标准,它在这方面的行为是相同的,尽管实现不同(并且不太可能在PC平台上移植).没有move构造函数..除非在新版本中有所更改,move在Arduino上并没有那么有效。我能想到的最接近的是旧的Delphi类集,它们在接口上是相同的(对于进行格式化的构造函数)感谢您的回复。pinSCL和pinDOUT是调用addLog的函数的本地函数。在addLog的调用函数结束之前,C++不保证有效性?@Wee否,因为lambda是单独的作用域。它是一个闭包,本质上是您使用
操作符()创建了一个对象
并且包含成员变量-对捕获的变量的引用,lambda表达式的主体就是该运算符。另外,如果addLog实际异步调用lambda,则必须小心,当本地函数退出且lambda将invalid@6502如此定义的addLog的目的是lambda方法只有在满足logLevel条件的情况下才能使用。addLog在许多具有异构信息的方法中都被调用。您建议我如何实现它?@Wee有效性问题:a)lambda对象构造需要时间。b) 您的
addLog
编写方式可能会调用get()三次-除非这些级别是独占的-如果它们是独占的,则会忘记
else
?检查是否需要任何日志记录级别是谨慎的,并且调用GET()一次?@ Wee:不幸的是C++没有垃圾收集器,并且需要通过引用来完全实现lambDas捕获,并且超出创建它们的范围(这被称为文献中的“向上的FunARG问题”)。您可以做的是复制lambda函数中的值,而不是使用引用,但这并不总是实用的(您也可以尝试使用基于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;
});