patterncppMinor
Scoped time sink implementation
Viewed 0 times
sinkimplementationscopedtime
Problem
I have written a little class that is intended to be used to spend unused time in a loop body, sleeping.
That is, I have a loop. I want every iteration to take at least X seconds. If the operations actually complete quicker, the excess time shall be spent sleeping.
My implementation is based on a class that is created in a scope, and whose destructor sleeps for the required amount of time when the object's lifetime ends.
Is this a sane approach to the problem? Are there any flaws in the implementation? Is there potential for improvements regarding coding style?
That is, I have a loop. I want every iteration to take at least X seconds. If the operations actually complete quicker, the excess time shall be spent sleeping.
My implementation is based on a class that is created in a scope, and whose destructor sleeps for the required amount of time when the object's lifetime ends.
Is this a sane approach to the problem? Are there any flaws in the implementation? Is there potential for improvements regarding coding style?
#include
#include
class TimeSink {
public:
typedef std::chrono::milliseconds duration_ms;
TimeSink(duration_ms const & min_duration)
: min_duration(min_duration), start(std::chrono::steady_clock::now()) {}
~TimeSink() {
time_point end = std::chrono::steady_clock::now();
auto elapsed = end-start;
auto remaining_time_to_sleep = min_duration - elapsed;
if (remaining_time_to_sleep > epsilon) {
std::this_thread::sleep_for(remaining_time_to_sleep);
}
}
private:
typedef std::chrono::time_point time_point;
const duration_ms epsilon = duration_ms(0);
duration_ms min_duration;
time_point start;
};
int main() {
TimeSink t(TimeSink::duration_ms(1000));
}Solution
Allow users to customize your TimeSink class
We achieve this by making it a template class:
This will allow your class to be used in different scenarios; it removes restrictions.
Since the
Your destructor can be simplified
Instead of doing...
...we can do:
I find this clearer and easier to understand.
Consider using
This stack overflow question should tell you why that's the preferred clock type for timing function execution.
Concise usage with the
This would simply make the code shorter. Note that there is a repetition, as we must define
In order to make usage fully concise (avoid repetition), we can use a template function and implement the idea proposed by 5gon12eder, in this comment which simply creates the
This allows us to create a
The improved code
Here's what the final code could look like:
Sample usage
As 5gon12eder points out, you must also ensure that you keep the return of
We achieve this by making it a template class:
template
class time_sink;This will allow your class to be used in different scenarios; it removes restrictions.
Since the
TimeUnit template parameter is the one most likely to be changed, we can provide a default argument for Clock:template
class time_sink;Your destructor can be simplified
Instead of doing...
time_point end = std::chrono::steady_clock::now();
auto elapsed = end-start;
auto remaining_time_to_sleep = min_duration - elapsed;
if (remaining_time_to_sleep > epsilon)
{
std::this_thread::sleep_for(remaining_time_to_sleep);
}...we can do:
auto time_spent = Clock::now() - begin;
if ( time_spent < min_duration )
{
std::this_thread::sleep_for( min_duration - time_spent );
}I find this clearer and easier to understand.
Consider using
std::chrono::high_resolution_clock as your default clock typeThis stack overflow question should tell you why that's the preferred clock type for timing function execution.
template
class time_sink;Concise usage with the
std::chrono_literals namespace and a template functionThis would simply make the code shorter. Note that there is a repetition, as we must define
time_sink's time unit type.using namespace std::chrono_literals;
using time_sink = time_sink;
time_sink{ 500ms };In order to make usage fully concise (avoid repetition), we can use a template function and implement the idea proposed by 5gon12eder, in this comment which simply creates the
time_sink object and uses template function type deduction to fill in the details.template
time_sink make_time_sink( TimeUnit const minimum_duration )
{
return time_sink{ minimum_duration };
}This allows us to create a
time_sink in a short and clear way:using namespace std::chrono_literals;
make_time_sink( 500ms );
// rest of function code, etc.The improved code
Here's what the final code could look like:
#include
#include
template
class time_sink
{
public:
time_sink( TimeUnit const minimum_duration ) :
min_duration{ minimum_duration },
begin{ Clock::now() }
{}
~time_sink()
{
auto time_spent = Clock::now() - begin;
if ( time_spent begin;
};
template
time_sink make_time_sink( TimeUnit const minimum_duration )
{
return time_sink{ minimum_duration };
}Sample usage
As 5gon12eder points out, you must also ensure that you keep the return of
make_time_sink in order to prevent time_sink's destructor from running early, since destructors are called at the "end of the full expression, for nameless temporaries" (source).void f()
{
using namespace std::chrono_literals;
// we keep the return of make_time_sink in order to prevent
// time_sink's destructor from executing before the end of the function
auto ts = make_time_sink( 500ms );
// ... do work ...
}
int main()
{
f();
}Code Snippets
template <typename TimeUnit, typename Clock>
class time_sink;template <typename TimeUnit, typename Clock = std::chrono::steady_clock>
class time_sink;time_point end = std::chrono::steady_clock::now();
auto elapsed = end-start;
auto remaining_time_to_sleep = min_duration - elapsed;
if (remaining_time_to_sleep > epsilon)
{
std::this_thread::sleep_for(remaining_time_to_sleep);
}auto time_spent = Clock::now() - begin;
if ( time_spent < min_duration )
{
std::this_thread::sleep_for( min_duration - time_spent );
}template <typename TimeUnit, typename Clock = std::chrono::high_resolution_clock>
class time_sink;Context
StackExchange Code Review Q#114189, answer score: 7
Revisions (0)
No revisions yet.