I am currently experimenting with the following approach to finalize processing some data (pseudo-code).
void run_processing(container datas)
{
// runs some conversion on data and sends it somewhere.
}
struct process_item
{
container datas;
process_item(const char* data)
{
datas.add(data);
}
~process_item()
{
run_processing(datas);
}
operator <<(const char* data)
{
datas.add(data);
}
}
process_item create_item(const char* data)
{
process_item item(data);
// Possible additional calls to setup item.
return item;
}
In my tests this allows for a call.
create_item("my_data") << "additional data" << "even more data";
// Once this piece executes the destructor for process_item is assumed to be called.
The pseudo code doesn't really show the intended benefits but that is besides my question.
I am trying this because I have some restrictions; The processing can't start until all data has been input, and it is not known when that's the case other than when the scope of process_item has ended. For simplicities sake, assume I can't call an additional method or add a flag after the last data is added.
Can the destructor be assumed to be called at the expected time, or is this too risky having the option to differ depending on optimization level and compiler?
Clarification
"At the right time" meaning before the next line, the one after the call to create_item
executed.
CodePudding user response:
A problem with your strategy is that at destruction time, you have no (safe) ability to
Throw an exception to report an error, or
Return a result
Throwing exceptions from destructor is a really, really bad idea, because destructors are run during stack unwinding if someone else throws an exception, and if you throw an exception during stack unwinding, your program is terminated.
There are ways around it, but they involve ... not throwing an exception during stack unwinding. So any error is lost.
Also, you run into the possibility that an exception causes the action you intend to do to be partially prepared, then executed. This can ruin the coherence of your data.
One approach to deal with this is:
[[nodiscard]] process_item& operator <<(const char* data)
{
datas.add(data);
return *this;
}
then provide an operator error_code()
or whatever to force the user to assign the entire <<
chain to an error variable. Is is that operator error_code()
that causes the code to be executed.
Under this model, an exception thrown on the line of <<
doesn't cause the operation to run, because the operator error_code()
is skipped.
Sadly, [[nodiscard]]
isn't c 11.
...
Other than that, yes, temporary objects are destroyed at the end of the full expression they are created in. There are a few exceptions involving elision and argument passing, but those are unlikely to apply in code like you describe.
As you have written a destructor, you need to obey the rule of 5. This means you need to delete or implement a copy/move assign/constructor (delete is the easy option).
If you delete copy/move operations, you may run into problems in create_item
, as NRVO elision is not guaranteed. It will work in c 17 if you use RVO elision (as that is now guaranteed). Another option is to make your class move-friendly (including moving the "prepared to execute" state) so that a moved-from instance doesn't run the operation.
I strongly suggest you block copy operations. It would be rather easy to accidentally make 3 copies of some operation you want to run once. It is cool to be able to copy an operation half way through, but the C copy semantics are a bit too easy to engage: if you want to be able to copy, but not have it happen accidentally, add a .copy()const
method that returns a prvalue.
CodePudding user response:
glog do something like this, run process in destructor will be ok. And if you want desctuctor run fast, run process in anthor thread.
LOG(INFO) << "hello" << "world";
LOG(INFO) is a macro which create a stream object accpet message, when object is destructed, it will write message to log file. Read the code of glog may help you