Blog Archive for / 2019 / 03 /
Get the element index when iterating with an indexed_view
Monday, 25 March 2019
One crucial difference between using an index-based for
loop and a range-based for
loop is that
the former allows you to use the index for something other than just identifying the element,
whereas the latter does not provide you with access to the index at all.
The difference between index-based for
loops and range-based for
loops means that some people
are unable to use simple range-based for
loops in some cases, because they need the index.
For example, you might be initializing a set of worker threads in a thread pool, and each thread needs to know it's own index:
std::vector<std::thread> workers;
void setup_workers(unsigned num_threads){
workers.resize(num_threads);
for(unsigned i=0;i<num_threads;++i){
workers[i]=std::thread(&my_worker_thread_func,i);
}
}
Even though workers
has a fixed size in the loop, we need the loop index to pass to the thread
function, so we cannot use range-based for
. This requires that we duplicate num_threads
,
adding the potential for error as we must ensure that it is correctly updated in both places if we
ever change it.
jss::indexed_view
to the rescue
jss::indexed_view
provides a means of obtaining
that index with a range-based for
loop: it creates a new view range which wraps the original
range, where each element holds the loop index, as well as a reference to the element of the
original range.
With jss::indexed_view
, we can avoid the duplication from the previous example and use the
range-based for
:
std::vector<std::thread> workers;
void setup_workers(unsigned num_threads){
workers.resize(num_threads);
for(auto entry: jss::indexed_view(workers)){
entry.value=std::thread(&my_worker_thread_func,entry.index);
}
}
As you can see from this example, the value
field is writable: it is a reference to the underlying
value if the iterator on the source range is a reference. This allows you to use it to modify the
elements in the source range if they are non-const
.
jss::indexed_view
also works with iterator-based ranges, so if you have a pair of iterators, then
you can still use range-based for
loops. For example, the following code processes the elements up
to the first zero in the supplied vector, or the whole vector if there is no zero.
void foo(std::vector<int> const& v){
auto end=std::find(v.begin(),v.end(),0);
for(auto entry: jss::indexed_view(v.begin(),end)){
process(entry.index,entry.value);
}
}
Finally, jss::indexed_view
can also be used with algorithms that require iterator-based ranges,
so our first example could also be written as:
std::vector<std::thread> workers;
void setup_workers(unsigned num_threads){
workers.resize(num_threads);
auto view=jss::indexed_view(workers);
std::for_each(view.begin(),view.end(),[](auto entry){
entry.value=std::thread(&my_worker_thread_func,entry.index);
});
}
Final words
Having to use non-ranged for
loop to get the loop index introduces a potential source of error: it
is easy to mistype the loop index either in the for
-loop header, or when using it to get the
indexed element, especially in nested loops.
By using jss::indexed_view
to wrap the range, you can eliminate this particular source of error,
as well as making it clear that you are iterating across the entire range, and that you need the
index.
Get the source from github and use it in your project now.
Posted by Anthony Williams
[/ cplusplus /] permanent link
Tags: cplusplus, memory, safety, undefined, crash, security
Stumble It! | Submit to Reddit | Submit to DZone
If you liked this post, why not subscribe to the RSS feed or Follow me on Twitter? You can also subscribe to this blog by email using the form on the left.
object_ptr - a safer replacement for raw pointers
Thursday, 21 March 2019
Yesterday I uploaded my object_ptr<T>
implementation
to github under the Boost
Software License.
This is an implementation of a class similar
to
std::experimental::observer_ptr<T>
from
the Library Fundamentals TS 2, but with various
improvements suggested in WG21 email discussions of the feature.
The idea
of
std::experimental::observer_ptr<T>
is
that it provides a pointer-like object that does not own the pointee, and thus
can be used in place of a raw pointer, but does not allow pointer arithmetic or
use of delete
, or pointer arithmetic, so is not as dangerous as a raw
pointer. Its use also serves as documentation: this object is owned elsewhere,
so explicitly check the lifetime of the pointed-to object — there is
nothing to prevent a dangling std::experimental::observer_ptr<T>
.
My
implementation of this concept
has a different name (object_ptr<T>
). I feel that observer_ptr
is a bad
name, because it conjures up the idea of the Observer pattern, but it doesn't
really "observe" anything. I believe object_ptr
is better: it is a pointer to
an object, so doesn't have any array-related functionality such as pointer
arithmetic, but it doesn't tell you anything about ownership.
It also has slightly different semantics to std::experimental::observer_ptr
:
it allows incoming implicit conversions, and drops the release()
member
function. The implicit conversions make it easier to use as a function
parameter, without losing any safety, as you can freely pass a
std::shared_ptr<T>
, std::unique_ptr<T>
, or even a raw pointer to a function
accepting object_ptr<T>
. It makes it much easier to use jss::object_ptr<T>
as a drop-in replacement for T*
in function parameters. There is nothing you
can do with a jss::object_ptr<T>
that you can't do with a T*
, and in fact
there is considerably less that you can do: without explicitly requesting the
stored T*
, you can only use it to access the pointed-to object, or compare it
with other pointers. The same applies with std::shared_ptr<T>
and
std::unique_ptr<T>
: you are reducing functionality, so this is safe, and
reducing typing for safe operations is a good thing.
I strongly recommend using object_ptr<T>
or an equivalent implementation of
the observer_ptr
concept anywhere you have a non-owning raw pointer in your
codebase that points to a single object.
If you have a raw pointer that does own its pointee, then I would strongly
suggest finding a smart pointer class to use as a wrapper to encapsulate that
ownership. For example, std::unique_ptr
or std::shared_ptr
with a custom
deleter might well do the job.
Posted by Anthony Williams
[/ cplusplus /] permanent link
Tags: cplusplus, memory, safety
Stumble It! | Submit to Reddit | Submit to DZone
If you liked this post, why not subscribe to the RSS feed or Follow me on Twitter? You can also subscribe to this blog by email using the form on the left.
Design and Content Copyright © 2005-2024 Just Software Solutions Ltd. All rights reserved. | Privacy Policy