I'm returning a vector by reference as shown below and it is getting bit ugly when I want to return an empty vector when there is no item in the map. The following gives warning (returning address of local variable) and to fix it, I have another private member variable vector<ClassA> empty_ and I could return it to avoid this.
I am wondering if there is elegant way to implement this.
const std::vector<ClassA>& GeVector(const std::string& class_id) {
auto iter = class_map_.find(class_id);
if (iter != class_map_.end())
return iter->second;
return {}; // return empty_;
}
private:
std::unordered_map<std::string, std::vector<ClassA>> class_map_;
vector<ClassA> empty_;
You could use a static variable:
static const std::vector<ClassA> empty;
return empty;
If your method support the option of failure you could throw a exception instead of returning an empty vector.
const std::vector<ClassA>& GeVector(const std::string& class_id) {
auto iter = class_map_.find(class_id);
if (iter != class_map_.end())
return iter->second;
throw std::exception("Element not found"); // or similar
}
Related
I want to implement a function which iterates over a list and returns a pointer in case of a match. I wrote:
std::list<JobEntry> jobs;
JobsList::JobEntry *JobsList::getJobById(int jobId) {
for (auto const& job : jobs) {
if (job.pid==jobId) {
return std::addressof(*job);
}
}
return nullptr;
}
But this doesn't work, how can I do it?
getJobById() is declared to return a pointer to a non-const JobEntry object, but job is a reference to a const JobEntry object. You can't assign a pointer-to-const to a pointer-to-non-const. So drop the const.
Also, a range-based for loop handles iterators internally, so job is the actual object you want to return a pointer to, not an iterator to the object, so don't try to dereference it.
JobsList::JobEntry* JobsList::getJobById(int jobId) {
for (auto &job : jobs) {
if (job.pid == jobId) {
return std::addressof(job);
}
}
return nullptr;
}
That being said, consider using the standard std::find_if() algorithm instead of a manual loop:
#include <algorithm>
JobsList::JobEntry* JobsList::getJobById(int jobId) {
auto iter = std::find_if(jobs.begin(), jobs.end(),
[=](const JobEntry &job) { return job.pid == jobId; }
);
if (iter != jobs.end())
return std::addressof(*iter);
return nullptr;
}
I have this function below with wordcount_map being an std::unordered_map <std::string, int> type. I'm using the count function to see if the key is in the map and if it is to return the value stored at that key. However, I'm getting an error because map is marked as const and [] operator is not allowed. Please advise!
int lookupWithFallback(const StringIntMap& wordcount_map, const std::string& key, int fallbackVal) {
if (wordcount_map.count(key)){
return wordcount_map[key];
}
else {
return fallbackVal;
}
}
Use the method "find" which is const :
https://en.cppreference.com/w/cpp/container/unordered_map/find
In your case, something like :
int lookupWithFallback(const StringIntMap& wordcount_map, const std::string& key, int fallbackVal) {
auto it = wordcount_map.find(key);
if(it != wordcount_map.end())
return it->second;
else
return fallbackVal;
}
The operator [] is not const because if the key doesn't exist, it will be created.
https://en.cppreference.com/w/cpp/container/unordered_map/operator_at
Use the find member function:
int lookupWithFallback(const StringIntMap& wordcount_map,
const std::string& key,
int fallbackVal)
{
auto it = wordcount_map.find(key);
return it != wordcount_map.end() ? it->second : fallbackVal;
}
This method will also only do one lookup, and can be useful even when you do want to modify the map.
Even if your map were non-const, your algorithm requires a redundant lookup.
You can either use std::unordered_map::find as suggested in other answers:
int lookupWithFallback(const StringIntMap& wordcount_map, const std::string& key, int fallbackVal) {
if (auto const it = wordcount_map.find(key); it != wordcount_map.end()) {
return it->second;
} else {
return fallbackVal;
}
}
or you can use std::unordered_map::at and catch an exception instead of passing a fallbackVal:
try {
return wordcount_map.at(key);
} catch (std::out_of_range const& oor) {
/* handle error in a sensible way or: */
return fallbackVal;
}
Passing default values is a problem for types more complicated than int so you may want to consider handling a non-existent value as an error. However, exceptions should not be used for expected cases. This depends on your setting.
I have an std::unordered_map<uint64_t, Object> _map and I'd like to provide a read-only getter for clients.
Originally I thought of this:
bool foundItem(const uint64_t key, Object& object) const
{
if(_map.find(key) != _map.end())
{
object = _map.at(key);
return true;
}
else
{
return false;
}
}
but obviously that isn't read-only, so I changed the signature to return the object and the bool can be passed by reference:
const Object& foundItem(const uint64_t key, bool& found) const
{
if(_map.find(key) != _map.end())
{
found = true;
return _map.at(key);
}
else
{
found = false;
// What can I return here??
}
}
but now I have no type to return if the key is not found.
What is the best way to allow the user read-only access to the (maybe) returned object?
You could return a pointer:
// Returns nullptr if not found
const Object* getItem(const uint64_t key) const
{
auto it = _map.find(key);
if (it == _map.end())
return nullptr;
return &it->second;
}
Or you could return a std::optional, or you could return const Object& but throw an exception on lookup failure.
In all cases, be careful to document the expected lifetime of the result. In particular, you should comment which member functions will result in the result of this function being invalidated. Usually that'll be "any non-const function", like how std::string::c_str() is valid until you do mutatey things on the string.
I have a std::vector with thousands of objects, stored as shared_ptr. Since the object has many attributes that can be used for searching, I maintain multiple indexes to this std::vector on std::map and std::multimap using weak_ptr.
std::vector<std::shared_ptr<Object>> Objects;
std::map<int,std::weak_ptr<Object>> IndexByEmployeeId;
std::multimap<std::string,std::weak_ptr<Object>> IndexByName;
Since the map and multimap are balanced binary trees, the search/modify are super fast. However, I am a bit foxed about delete. I want to delete after looking up the object via one of the indexes. Locking the weak_ptr gives me shared_ptr, but it doesn't allow me to destroy object on the vector. Is there any way to delete the original object on the vector?
It seems from what you've posted that your data structures are inappropriate for what you want to do.
shared_ptr should only be used to express shared ownership. However, your posted code and your desire to delete the objects pointed to indicate that Objects is in fact the sole owner of its objects.
Your vector<shared_ptr<object>> appears to be used only as data storage container (the desired functionality of searching by id or name is implemented elsewhere), but removing elements from a vector is typically expensive, so you may better use another type of container.
If there are no other shared_ptr to your objects, then your design is poor. In this case you shouldn't use any smart pointers but simply container<object> and maps into the that. For example something like this (not tested not even compiled):
struct data_t { std::string name; /* more data */ };
using objt_map = std::map<std::size_t,data_t>;
using object = objt_map::value_type; // pair<const size_t,data_t>
using obj_it = objt_map::iterator;
using name_map = std::multimap<std::string, obj_it>;
objt_map Objects;
name_map NameMap;
std::forward_list<obj_it> find_by_name(std::string const&name) const
{
auto range = NameMap.equal_range(name);
std::forward_list<obj_it> result;
for(auto it=range.first; it!=range.second; ++it)
result.push_front(it->second);
return result;
}
std::forward_list<obj_it> find_by_id(std::size_t id) const
{
auto it = Objects.find(name);
return {it == Objects.end()? 0:1, it};
}
void insert_object(std::size_t id, data_t const&data)
{
auto it = Objects.find(id);
if(it != Objects.end())
throw std::runtime_error("id '"+std::to_string(id)+"' already known");
Objects[id] = data;
NameMap.emplace(data.name, Objects.find(id));
}
void delete_object(obj_it it)
{
if(it==Objects.end())
throw std::runtime_error("attempt to delete invalid object");
auto range = NameMap.equal_range(it->second.name);
for(auto i=range.first; i!=range.second; ++i)
if(i->second==it) {
NameMap.erase(i);
break;
}
Objects.erase(it);
}
Note iterators of std::map remain valid upon insertion and deletion (of other objects), such that the returns from the finders are not invalidated by insertion and deletion. I use std::forward_list<obj_it> to return objects to allow for returning none or several.
This can be a use case where std::set is the appropriate choice over std::vector. std::set guarantees lookup, insertion and removal in logarithmic time. So you can lookup an object by index in one of your maps objects and then delete it in any container with log(N) performance.
I would suggest this approach if insertion/removal operations represent the performance bottleneck in your application.
By the way, consider carefully the actual need for shared_ptr, because shared_ptr comes with a certain performance overhead as opposed to unique_ptr for example. Your owner container could use unique_ptr and the various maps could simply use raw pointers.
So, here is another option, based on importing the objects by moving std::unique_ptr<>. Unfortunately, unique_ptrs are not useful keys for std::set (since they are unique), unless you have C++14, when set::find() can take another argument than a key (see below).
For a C++11 approach, one must therefore use std::map to store the unique_ptrs, which requires doubling the id and name entries: once in data_t and once as keys in the maps. Here is a sketch.
struct data_t {
const std::size_t id; // changing these would
const std::string name; // lead to confusion
/* more data */
};
using data_ptr = std::unique_ptr<data_t>;
using data_map = std::map<std::size_t,data_ptr>;
using obj_it = data_map::iterator;
using name_map = std::multimap<std::string,obj_it>;
data_map DataSet;
name_map NameMap;
std::vector<data_t*> find_by_name(std::string const&name) const
{
auto range = NameMap.equal_range(name);
std::vector<data_t*> result;
result.reserve(std::distance(range.first,range.second));
for(auto it=range.first; it!=range.second; ++it)
result.push_back(it->second->get());
return result;
}
data_t* find_by_id(std::size_t id) const
{
auto it = DataSet.find(id);
return it == DataSet.end()? nullptr : it->second.get();
}
// transfer ownership here
void insert_object(data_ptr&&ptr)
{
const auto id = ptr->id;
if(DataSet.count(id))
throw std::runtime_error("id '"+std::to_string(id)+"' already known");
auto itb = DataSet.emplace(id,std::move(ptr));
auto err = itb.second;
if(!err)
err = NameMap.emplace(itb.first->name,itb.first).second;
if(err)
throw std::runtime_error("couldn't insert id "+std::to_string(id));
}
// remove object given an observing pointer; invalidates ptr
void delete_object(data_t*ptr)
{
if(ptr==nullptr)
return; // issue warning or throw ?
auto it = DataSet.find(ptr->id);
if(it==DataSet.end())
throw std::runtime_error("attempt to delete an unknown object");
auto range = NameMap.equal_range(it->second->name);
for(auto i=range.first; i!=range.second; ++i)
if(i->second==it) {
NameMap.erase(i);
break;
}
DataSet.erase(it);
}
Here is a sketch for a C++14 solution, which avoids the duplication of the id and name data in the maps, but requires/assumes that data_t::id and data_t::name are invariable.
struct data_t {
const std::size_t id; // used as key in set & multiset:
const std::string name; // must never be changed
/* more data */
};
using data_ptr = std::unique_ptr<data_t>;
struct compare_id {
using is_transparent = std::size_t;
static bool operator(data_ptr const&l, data_ptr const&r)
{ return l->id < r->id; }
static bool operator(data_ptr const&l, std::size_t r)
{ return l->id < r; }
static bool operator(std::size_t l, data_ptr const&r)
{ return l < r->id; }
};
using data_set = std::set<data_ptr,compare_id>;
using data_it = data_set::const_iterator;
struct compare_name {
using is_transparent = std::string;
static bool operator(data_it l, data_it r)
{ return (*l)->name < (*r)->name; }
static bool operator(data_it l, std::string const&r)
{ return (*l)->name < r; }
static bool operator(std::string const&l, data_it r)
{ return l < (*r)->name; }
};
using name_set = std::multiset<data_it,compare_name>;
data_set DataSet;
name_set NameSet;
std::vector<data_t*> find_by_name(std::string const&name) const
{
auto range = NameSet.equal_range(name);
std::vector<data_t*> result;
result.reserve(std::distance(range.first,range.second));
for(auto it=range.first; it!=range.second; ++it)
result.push_back((*it)->get());
return result;
}
data_t* find_by_id(std::size_t id) const
{
auto it = DataSet.find(id);
return it == DataSet.end()? nullptr : it->get();
}
// transfer ownership here
void insert_object(data_ptr&&ptr)
{
const auto id = ptr->id;
if(DataSet.count(id))
throw std::runtime_error("id '"+std::to_string(id)+"' already known");
auto itb = DataSet.emplace(std::move(ptr));
auto err = itb.second;
if(!err)
err = NameSet.emplace(itb.first).second;
if(err)
throw std::runtime_error("couldn't insert id "+std::to_string(id));
}
// remove object given an observing pointer; invalidates ptr
void delete_object(data_t*ptr)
{
if(ptr==nullptr)
return; // issue warning or throw ?
auto it = DataSet.find(ptr->id);
if(it==DataSet.end())
throw std::runtime_error("attempt to delete an unknown object");
auto range = NameSet.equal_range(ptr->name);
for(auto i=range.first; i!=range.second; ++i)
if((*i)==it) {
NameSet.erase(i);
break;
}
DataSet.erase(it);
}
There may well be some bugs in here, in particular errors with dereferencing the various iterator and pointer types (though once it compiles these should be okay).
Here is my code:
typedef map<string, unique_ptr<MyClass> > DB;
const unique_ptr<MyClass>>& find_it(const string &key, const DB &db)
{
auto itr = db.find(key);
if (itr == db.end()) {
return nullptr;
}
return itr->second;
}
That return statement causes a compiler warning: returning reference to local temporary object [-Wreturn-stack-address].
Yes, I can understand that returning a reference to local temp variable is bad, but I wonder what is the simplest fix here given the following:
1. Do not expose the map to the callers of find_it (I use typedef here is just for asking this question, in real code it is wrapped inside an interface).
2. Using an iterator kind of thing to indicate the position of the searched item is a hassle to me, I like to avoid it.
Given these, the best I can come up is to break find_it() to 2 functions:
bool exists(const string &key, const DB &db)
{
auto itr = db.find(key);
return itr != db.end();
}
const unique_ptr<MyClass>>& find_it(const string &key, const DB &db)
{
auto itr = db.find(key);
assert(itr != db.end());
return itr->second;
}
Any suggestions?
The return nullptr statement is implicitly constructing a unique_ptr<MyClass> instance and you're then returning a reference to it, hence the warning. A simple fix is to define a static unique_ptr<MyClass> that holds nullptr and return a reference to that instead.
const unique_ptr<MyClass>& find_it(const string &key, const DB &db)
{
static unique_ptr<MyClass> not_found;
auto itr = db.find(key);
if (itr == db.end()) {
return not_found;
}
return itr->second;
}
A better fix might be to use boost::optional. Change the return type to boost::optional<std::unique_ptr<MyClass>> const&, and then return boost::none if the object is not found.