Reputation: 21
I'm back to programming in C ++ after many years and I have some doubts.
I created this function:
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());
}
}
I would like to use it as follows:
addLog(LOG_LEVEL_INFO, [&]()
{
String log = F("HX711: GPIO: SCL=");
log += pinSCL;
log += F(" DOUT=");
log += pinDOUT;
return log.c_str();
});
The validity of log.c_str () is guaranteed until addLog ends or if something interrupts the normal program flow (any event handler), the string object is destroyed?
Upvotes: 2
Views: 314
Reputation: 14613
Is that Arduino API? That way you cause UB, String demolishes its resources on exit from closure.
Technically if you redesign type
typedef std::function<String(void)> GetMessageLog;
then you can write
addLog(LOG_LEVEL_INFO, [&]() -> String
{
String log = F("HX711: GPIO: SCL=");
log += pinSCL;
log += F(" DOUT=");
log += pinDOUT;
return log;
});
If compiler doesn't support named return value optimization, convert that in a one-liner to reduce amount of copy operations.
Upvotes: 1
Reputation: 13751
Welcome back to C++! You'll find that it has changed quite a bit in this century, and for the better.
The object "String log" lives only during the call of addLog. You cannot return log.c_str() because this will return a dangling pointer to an object which will no longer exist after the return. The solution is simple - just return "log" itself. Have this function (and GetMessageLog) return not an old-style C "char *", but a modern C++ "std::string".
In old C++, returning an std::string from a function was often frowned upon, because it always involved copying of that string, sometimes multiple times. This is no longer true, with the advent of move constructors (which are probably the most important new feature in C++11). The function builds a string, and when returning it, the string is not copied but rather "moved" - which involves copying just the pointer which it holds to its data array, but not copying the array itself.
In modern C++, you will very rarely use old-style bare pointers like you used char* in this example. You will typically use objects like std::string instead of char*, containers like std::vector instead of int*, smart pointers like std::unique_ptr instead of T*. All these objects are safer than the bare pointers because they give you less chance to mess up the lifetime of the object, and are exception-safe (i.e., if an exception occurs in the middle of the code, you won't forget to free memory you allocated).
Upvotes: 1
Reputation: 114559
String
is a local to the lambda so (assuming it's more or less like std::string
) you cannot use c_str
as return value because when the caller will access it the local is already dead.
One more potential problem is that you're capturing by reference with [&]
the variables pinSCL
and pinDOUT
. If the lambda is stored away and its lifetime ends after the lifetime of those two variables then calling it is also undefined behavior.
Upvotes: 1
Reputation: 171167
That actually depends on what String
is, but most likely, the return value of log.c_str()
is valid only as long as log
itself is (that would certainly be the case with std::string
). Which means that in your case, it's not usable at all: when the lambda returns, log
is destroyed, so the pointer returned from the lambda is dangling already.
Fortunately, the solution is simple. Change the lambda's return type to String
and have it return log
itself instead. If you need a const char*
eventually, you can call c_str()
on the return value, where you will have much better control over the lifetime.
Upvotes: 4