I am struggling with removing a unique_ptr from vector. I have a vector:
std::vector<std::unique_ptr<Agent>>;
which I am filling with certain number of Agents. I do it in the following way:
In class constructor list:
agentsVec(std::accumulate(agentsQuantity.begin(), agentsQuantity.end(), 0,
[](int value, const QuantityMap::value_type& p) { return value + p.second; }))
And in a function responsible for generating agents:
auto agentVecIter = agentsVec.begin();
std::for_each(agentsQuantity.begin(), agentsQuantity.end(), [this, &agentVecIter](const QuantityMap::value_type& p)
{
std::generate_n(agentVecIter, p.second, [this, &p]()
{
auto agent = factory.createAgent(p.first);
changeAgentOnLattice(generatePosition(), agent->getID());
return agent;
});
std::advance(agentVecIter, p.second);
});
agentsQuantity is a map which denotes number of agents of certain type. Agent factory returns std::unique_ptr;
During the program there can be a need to delete some Agent from vector, based on it's ID (Agent's ID is then returned to freeID's stack).
Kill agent:
std::experimental::erase_if(agentsVec, [this, &positionID](auto const& agent) {
auto agentID = agent->getID();
if (positionID == agentID) {
Utils::addIDToStack(agentID);
return true;
}
return false;
});
To test that, I added 5 agents which will be "killed". In about 40% of cases I got:
Debug Assertion Failed! Vector iterator not dereferencable
What am I doing wrong?
EDIT: More code for clarification. Lattice of agents (which contains agentsVec) is created in Environment class. Environment then step into infinite loop and iterates through all positions in lattice and check if something exists there. If yes -> it moves the agent and updates its health:
Environment::Health update:
for (int i = 0; i < latticeSize; i++) {
for (int j = 0; j < latticeSize; j++) {
if (lattice->getAgentID(Position(i, j))) {
Agent* currentAgent = lattice->getAgentInstance(Position(i, j));
currentAgent->updateHealth();
if (currentAgent->getAgentType() == Enums::AgentType::Predator && currentAgent->getHealth() <= -10) {
lattice->killAgent(Position(i, j));
}
}
}
}
Lattice::Move agent:
const auto originRow = Utils::BoundaryCondition(origin.first, latticeSize);
const auto originCol = Utils::BoundaryCondition(origin.second, latticeSize);
const auto destRow = Utils::BoundaryCondition(destination.first, latticeSize);
const auto destCol = Utils::BoundaryCondition(destination.second, latticeSize);
if (getAgentID(origin) == static_cast<int>(Enums::AgentType::Field)) {
Logger::getInstance().Log("lattice", "\tCannot move - there is no moving creature on origin position");
return false;
}
if (getAgentID(destination) != static_cast<int>(Enums::AgentType::Field)) {
Logger::getInstance().Log("lattice", "\tCannot move - destination position is occupied");
return false;
}
latticeMap[destRow][destCol] = static_cast<int>(getAgentID(origin));
latticeMap[originRow][originCol] = static_cast<int>(Enums::AgentType::Field);
Logger::getInstance().Log("lattice", "\tMoved succesfully");
return true;
Agent getter (used in Environment to get Agent pointer from Lattice::agentsVec)
Agent* Lattice::getAgentInstance(Position position) {
int ID = getAgentID(position);
auto it = std::find_if(std::execution::par, agentsVec.begin(), agentsVec.end(),
[=](std::unique_ptr<Agent>& agent) { return agent->getID() == ID; });
if (it != agentsVec.end()) {
return it->get();
}
return nullptr;
}
And AgentType getter:
Enums::AgentType Lattice::checkAgentType(int ID)
{
auto it = std::find_if(std::execution::par, agentsVec.begin(), agentsVec.end(),
[=](std::unique_ptr<Agent>& agent) { return agent->getID() == ID; });
if(it != agentsVec.end()) {
return it->get()->getAgentType();
}
return Enums::AgentType::Unknown;
}
Related
I am writing currently an importer for Labplot to support BLF files. The importer works fine, but when looking at the performance it is visible that there is a lot of room to improve. It is visible that adding the data to the datacontainer consumes the most of the computational power. I tried already for testing using std::vector, but it has not that big impact.
I am not able to use a static array, because the actual number of messages is unknown (only upper limit is known), because if the dbcParser is not able to parse the message it will be skipped. So at the end of the import I have to resize the array.
Are there any recomandations how to improve the performance of the code?
Definition of v: QVector<const Vector::BLF::ObjectHeaderBase*> v;
bool firstMessageValid = false;
for (const auto ohb : v) {
int id;
std::vector<double> values;
if (ohb->objectType == Vector::BLF::ObjectType::CAN_MESSAGE) {
const auto message = reinterpret_cast<const Vector::BLF::CanMessage*>(ohb);
id = message->id;
m_dbcParser.parseMessage(message->id, message->data, values);
} else if (ohb->objectType == Vector::BLF::ObjectType::CAN_MESSAGE2) {
const auto message = reinterpret_cast<const Vector::BLF::CanMessage2*>(ohb);
id = message->id;
m_dbcParser.parseMessage(message->id, message->data, values);
} else
return 0;
if (values.size() == 0) {
// id is not available in the dbc file, so it is not possible to decode
DEBUG("Unable to decode message: " << id);
continue;
}
uint64_t timestamp;
timeInNS = getTime(ohb, timestamp);
if (convertTimeToSeconds) {
double timestamp_seconds;
if (timeInNS)
timestamp_seconds = (double)timestamp / pow(10, 9); // TimeOneNans
else
timestamp_seconds = (double)timestamp / pow(10, 5); // TimeTenMics
m_DataContainer.setData<double>(0, message_index, timestamp_seconds);
} else
m_DataContainer.setData<qint64>(0, message_index, timestamp);
if (firstMessageValid) {
const auto startIndex = idIndexTable.value(id) + 1; // +1 because of time
for (std::vector<double>::size_type i = 1; i < startIndex; i++) {
const auto prevValue = m_DataContainer.data<double>(i, message_index - 1);
m_DataContainer.setData<double>(i, message_index, prevValue);
}
for (std::vector<double>::size_type i = startIndex; i < startIndex + values.size(); i++) {
m_DataContainer.setData<double>(i, message_index, values.at(i - startIndex));
}
for (std::vector<double>::size_type i = startIndex + values.size(); i < m_DataContainer.size(); i++) {
const auto prevValue = m_DataContainer.data<double>(i, message_index - 1);
m_DataContainer.setData<double>(i, message_index, prevValue);
}
} else {
const auto startIndex = idIndexTable.value(id) + 1; // +1 because of time
for (std::vector<double>::size_type i = 1; i < startIndex; i++) {
m_DataContainer.setData<double>(i, message_index, 0);
}
for (std::vector<double>::size_type i = startIndex; i < startIndex + values.size(); i++) {
m_DataContainer.setData<double>(i, message_index, values.at(i - startIndex));
}
for (std::vector<double>::size_type i = startIndex + values.size(); i < m_DataContainer.size(); i++) {
m_DataContainer.setData<double>(i, message_index, 0);
}
firstMessageValid = true;
}
message_index++;
}
struct DataContainer {
void clear();
template<class T>
void appendVector(QVector<T>* data, AbstractColumn::ColumnMode cm) {
m_dataContainer.push_back(data);
m_columnModes.append(cm);
};
template<class T>
void setData(int indexDataContainer, int indexData, T value) {
auto* v = static_cast<QVector<T>*>(m_dataContainer.at(indexDataContainer));
v->operator[](indexData) = value;
}
template<class T>
T data(int indexDataContainer, int indexData) {
auto* v = static_cast<QVector<T>*>(m_dataContainer.at(indexDataContainer));
return v->at(indexData);
}
int size() const;
const QVector<AbstractColumn::ColumnMode> columnModes() const;
/*!
* \brief dataContainer
* Do not modify outside as long as DataContainer exists!
* \return
*/
std::vector<void*> dataContainer() const;
AbstractColumn::ColumnMode columnMode(int index) const;
const void* datas(int index) const;
bool resize(uint32_t) const;
private:
QVector<AbstractColumn::ColumnMode> m_columnModes;
std::vector<void*> m_dataContainer; // pointers to the actual data containers
};
Edit
Before the loop I resize every vector to the absolute maximum number of messages.
if (convertTimeToSeconds) {
auto* vector = new QVector<double>();
vector->resize(message_counter);
m_DataContainer.appendVector<double>(vector, AbstractColumn::ColumnMode::Double);
} else {
auto* vector = new QVector<qint64>();
vector->resize(message_counter);
m_DataContainer.appendVector<qint64>(vector, AbstractColumn::ColumnMode::BigInt); // BigInt is qint64 and not quint64!
}
for (int i = 0; i < vectorNames.length(); i++) {
auto* vector = new QVector<double>();
vector->resize(message_counter);
m_DataContainer.appendVector(vector, AbstractColumn::ColumnMode::Double);
}
During parsing I discard messages if I am not able to parse them, therefore message_index <= message_counter. So when having 100k messages, but I parse only 50k of them, I have to resize the array at the end to not waste memory.
m_DataContainer.resize(message_index);
Edit2
Replacing
auto* v = static_cast<QVector<T>*>(m_dataContainer.at(indexDataContainer));
v->operator[](indexData) = value;
by
static_cast<QVector<T>*>(m_dataContainer.at(indexDataContainer))->operator[](indexData) = value;
and replacing
auto* v = static_cast<QVector<T>*>(m_dataContainer.at(indexDataContainer));
return v->at(indexData);
by
return static_cast<QVector<T>*>(m_dataContainer.at(indexDataContainer))->at(indexData);
brought about 20%. I thought it will be optimized out at -O2 but was not.
With -O2 moving from QVector to std::vector was again an improvement of around 25%
I am trying to build an inventory system in C++ for a game that I am working on. However, there is a bug in the inventory system when I call Inventory::AddItem(Item i), no item gets added and that slot still stays blank. Currently, I handle the inventory through std::vector<Item>, where Item is a struct containing the type, if it is stackable, the maximum number of blocks in a stack, the current number of blocks in the stack, and a couple of objects for animation. Moreover, I automatically fill the inventory in with 40 slots of air blocks, which have the ID of INVENTORY_EMTPY_SLOT_ID.
Here is the code:
typedef struct item {
int type; // this is whether the block is a foreground of background tile
int id; // use this for the id of objects
bool stackable; // true indicates that the block can be stacked
int max_num; // maximum number of blocks in a stack
int num; // the current number of blocks in the stack
Animation* use_animation; // the animation of the block or item when it is being used
Animation* other_animation; // secondary animation of item in case it is necessary
} Item;
How I initialize empty slots:
for (size_t x = 0; x < INVENTORY_MAX_SLOTS; x++) {
Item i = {0, INVENTORY_EMPTY_SLOT_ID, true, 1, 1, NULL, NULL};
this->items.push_back(i);
}
Adding items
/*********BUG HERE:******************/
void Inventory::AddItem(Item item) {
// find all indexes with the same item.id
std::vector<size_t> indexes_w_same_item;
for (size_t i = 0; i < this->items.size(); i++) {
if (this->items[i].id == item.id) {
indexes_w_same_item.push_back(i);
}
}
// find the next empty slot
int next_empty_slot = -1;
for (size_t i = 0; i < this->items.size(); i++) {
if (this->items[i].id == INVENTORY_EMPTY_SLOT_ID) {
next_empty_slot = i;
}
}
// go through all the indexes with the same item.id
// and see if at least one of them is empty.
// if one is empty and has sufficient capacity,
// add the item and return. if it isn't, keep moving forward
for (size_t x = 0; x < indexes_w_same_item.size(); x++) {
if (item.id == this->items[indexes_w_same_item[x]].id) {
if (this->items[indexes_w_same_item[x]].num + item.num <= this->items[indexes_w_same_item[x]].max_num) {
this->items[indexes_w_same_item[x]].num += item.num;
return;
}
}
}
// if there is an empty slot, make a new stack
if (next_empty_slot >= 0) {
this->items[next_empty_slot].id = item.id;
this->items[next_empty_slot].max_num = item.max_num;
// clamp item.num so it doesn't exceed item.max_num
if (item.max_num > item.num) {
this->items[next_empty_slot].num = item.num;
} else {
this->items[next_empty_slot].num = item.max_num;
}
}
}
I know you have found the error, but there are many issues in your code that lead to this error, and I wanted to help you understand how to write better code, so next time it will be easier for you to find it (and maybe even avoid it!).
You should divide the logic into as small pieces as possible - modularity is a key for more clear and clean code, which was helping you to understand the error much faster.
Instead of making a clear flow, you made two distinct flows on and off. The code is much clearer when you exhaust one possible flow, and only then start the other (look at the functions add_item_to_existing_stack_if_possible and add_item_to_new_stack_if_possible.
Your variables/functions/classes names must represent what they are standing for, it wasn't the case with the original code! Look at the Item struct now - it is much clearer what each member represents, without comments! (personally, I am not using comments at all)
C++ is not C with classes - things like typedef should not appear in your code, you should use operator<< to std::cout instead of printf and so on.
Make sure you add const specifiers as possible, it can help find many mistakes on compile time (and make your program run faster).
Performance related - you should pass objects as references as much as possible, it is much faster to pass an uint64 (memory location) than copy your entire Item object.
#include <vector>
#include <array>
#include <iostream>
struct Animation;
struct Item {
int type;
int id;
bool is_stackable;
int max_num_blocks_in_stack;
int curr_num_of_blocks_in_stack;
Animation* used_animation; // if it is non nullable, you should consider to use it without a pointer (possibly a reference)
Animation* secondary_animation; // nullable - can be a pointer or std::optional
};
class Inventory
{
public:
bool add_item(Item&& item);
private:
bool is_slot_empty(size_t idx) const { return items[idx].id == INVENTORY_EMPTY_SLOT_ID; }
std::vector<size_t> find_indexes_of(const Item& item) const;
size_t find_next_empty_slot() const;
bool add_item_to_existing_stack_if_possible(const Item& item);
bool add_item_to_new_stack_if_possible(Item&& item);
void print() const;
static constexpr size_t MAX_INV_SIZE = 40; // can transform into a class template!
std::array<Item, MAX_INV_SIZE> items;
static constexpr int INVENTORY_EMPTY_SLOT_ID = -1;
};
std::vector<size_t> Inventory::find_indexes_of(const Item& item) const
{
std::vector<size_t> indexes{};
for (size_t idx = 0; idx < MAX_INV_SIZE; ++idx)
{
if (items[idx].id == item.id)
{
indexes.push_back(idx);
}
}
return indexes;
}
size_t Inventory::find_next_empty_slot() const
{
for (size_t idx = 0; idx < MAX_INV_SIZE; ++idx)
{
if (is_slot_empty(idx))
{
return idx;
}
}
return MAX_INV_SIZE; // invalid value!
}
void Inventory::print() const
{
for (size_t i = 0; i < MAX_INV_SIZE; ++i)
{
if (this->items[i].id != INVENTORY_EMPTY_SLOT_ID)
{
std::cout << "Inventory slot: " << i << "\n"
<< "Item ID: " << items[i].id << "\n"
<< "Item Num: " << items[i].curr_num_of_blocks_in_stack << "\n"
<< "Item Max Num: " << items[i].max_num_blocks_in_stack << std::endl;
//<< "Item Texture: " << textures[items[i].id] << std::endl;
}
}
}
bool Inventory::add_item_to_existing_stack_if_possible(const Item& item)
{
auto indexes_with_same_item = find_indexes_of(item);
for (auto idx : indexes_with_same_item)
{
if (item.id == items[idx].id)
{
if (items[idx].curr_num_of_blocks_in_stack + item.curr_num_of_blocks_in_stack <=
items[idx].max_num_blocks_in_stack)
{
items[idx].curr_num_of_blocks_in_stack += item.curr_num_of_blocks_in_stack;
return true;
}
}
}
return false;
}
bool Inventory::add_item_to_new_stack_if_possible(Item&& item)
{
size_t next_empty_slot = find_next_empty_slot();
if (next_empty_slot >= 0)
{
this->items[next_empty_slot] = std::move(item);
return true;
}
return false;
}
bool Inventory::add_item(Item&& item)
{
bool was_possible_to_add_to_existing_stack = add_item_to_existing_stack_if_possible(item);
if (!was_possible_to_add_to_existing_stack)
{
return add_item_to_new_stack_if_possible(std::move(item));
}
return false;
}
Okay, I figured it out. There must be a break at the end of the second for loop, where it looks for the next empty slot, otherwise, it will detect the next empty slot as the last slot in the inventory, assuming that you are adding the first item in the inventory. Therefore, that item did not show up in the hopbar.
Here is the correct solution:
void Inventory::AddItem(Item item) {
// find all indexes with the same item.id
std::vector<size_t> indexes_w_same_item;
for (size_t i = 0; i < this->items.size(); i++) {
if (this->items[i].id == item.id) {
indexes_w_same_item.push_back(i);
}
}
// find the next empty slot
int next_empty_slot = -1;
for (size_t i = 0; i < this->items.size(); i++) {
if (this->items[i].id == INVENTORY_EMPTY_SLOT_ID) {
next_empty_slot = i;
break;
}
}
// go through all the indexes with the same item.id
// and see if at least one of them is empty.
// if one is empty and has sufficient capacity,
// add the item and return. if it isn't, keep moving forward
for (size_t x = 0; x < indexes_w_same_item.size(); x++) {
if (item.id == this->items[indexes_w_same_item[x]].id) {
if (this->items[indexes_w_same_item[x]].num + item.num <= this->items[indexes_w_same_item[x]].max_num) {
this->items[indexes_w_same_item[x]].num += item.num;
return;
}
}
}
// if there is an empty slot, make a new stack
if (next_empty_slot >= 0) {
this->items[next_empty_slot] = item;
}
for (size_t i = 0; i < INVENTORY_MAX_SLOTS; i++) {
if (this->items[i].id != '.') {
printf("\nInventory slot: %d\n", i);
printf("Item ID: %c\n", this->items[i].id);
printf("Item Num: %d\n", this->items[i].num);
printf("Item Max Num: %d\n", this->items[i].max_num);
printf("Item Texture: %x\n", this->textures[this->items[i].id]);
}
}
return;
}
I am currently trying to code a PacMan game with c++ and OpenGL. At the moment I am having some trouble when trying to delete a "Coin" pointer from a vector of "GameObject" pointers:
bool MovableObject::collisionHandling(std::vector<GameObject*>& v) {
bool deleteCoin = false;
int vectorIt;
for (auto it = begin(v); it != end(v); ++it) {
if (*it != this) {
// If there is a collision imminent:
if (collisionInt(this, *it)) {
// For PacMan:
if (this->classType == "PacMan") {
// Collision with coin:
if ((*it)->classType == "Coin") {
PacMan* ptr = static_cast<PacMan*>(this);
ptr->score += 10;
vectorIt = it - v.begin();
deleteCoin = true;
}
}
}
}
}
if (deleteCoin) {
Coin* ptr = static_cast<Coin*>(v[vectorIt]);
v.erase(v.begin() + vectorIt);
delete ptr;
}
return false;
}
This throws an exception in a vector file, which reads:
Exception thrown: read access violation.
_Mycont was nullptr. The exception is thrown on line 72, which has the following code:
_STL_VERIFY(_Ptr < _Mycont->_Mylast, "can't increment vector iterator past end")
It seems like the exception gets thrown between another for loop, where the collisionHandling function gets called from:
void GameLevel::update(GLFWwindow *window) {
std::string classType;
// Delta time:
float timeSinceStart = float(glfwGetTime());
deltaTime = timeSinceStart - oldTimeSinceStart;
oldTimeSinceStart = timeSinceStart;
if (!paused) {
std::cout << "Testing...";
// We don't need to update walls:
for (auto it = begin(objectsPtr); it != end(objectsPtr); ++it) {
classType = (*it)->classType;
if (classType == "Ghost") {
Ghost* ptr = static_cast<Ghost*>(*it);
ptr->deltaTime = deltaTime;
ptr->pickRandomOffDirection();
ptr->updateSpeed();
ptr->collisionHandling(objectsPtr);
ptr->move();
ptr->animate();
}
else if (classType == "PacMan") {
PacMan* ptr = static_cast<PacMan*>(*it);
ptr->deltaTime = deltaTime;
ptr->input(window);
ptr->updateSpeed();
paused = ptr->collisionHandling(objectsPtr);
ptr->move();
ptr->animate();
}
}
std::cout << "complete\n";
}
As I understand it, this shouldn't throw an exception. (Testing similar code with a vector of ints, where the last element was erased in the loop didn't fail.) This code also worked properly until I added the collision with coin part, so I know it has something to do with the vector loop. Additionally, this only fails when some specific coins should get deleted. Most of them get deleted without any problem.
I appreciate any feedback or attempt to help me.
Just in case you need more information, the collisionHandling function contains more code, but I don't think it has anything to do with the error. Here is the full code:
bool MovableObject::collisionHandling(std::vector<GameObject*>& v) {
bool deleteCoin = false;
int vectorIt;
for (auto it = begin(v); it != end(v); ++it) {
if (*it != this) {
// If there is a collision imminent:
if (collisionInt(this, *it)) {
// For PacMan:
if (this->classType == "PacMan") {
// Collision with wall:
if ((*it)->classType == "GameObject") {
bool xCollision = false, yCollision = false;
glm::vec2 oldSpeed = speed;
// Check x-axis:
speed.y = 0.f;
xCollision = collisionInt(this, *it);
// Check y-axis:
speed.y = oldSpeed.y;
speed.x = 0.f;
yCollision = collisionInt(this, *it);
speed = glm::vec2(!xCollision * oldSpeed.x, !yCollision * oldSpeed.y);
}
// Collision with ghost
if ((*it)->classType == "Ghost") {
std::cout << "GAME OVER\n";
return true;
}
// Collision with coin:
if ((*it)->classType == "Coin") {
PacMan* ptr = static_cast<PacMan*>(this);
ptr->score += 10;
vectorIt = it - v.begin();
deleteCoin = true;
}
}
// For Ghost:
else if (this->classType == "Ghost") {
// Collision with wall:
if ((*it)->classType == "GameObject") {
// Same as with PacMan:
bool xCollision = false, yCollision = false;
glm::vec2 oldSpeed = speed;
// Check x-axis:
speed.y = 0.f;
xCollision = collisionInt(this, *it);
// Check y-axis:
speed.y = oldSpeed.y;
speed.x = 0.f;
yCollision = collisionInt(this, *it);
speed = glm::vec2(!xCollision * oldSpeed.x, !yCollision * oldSpeed.y);
}
}
}
}
}
if (deleteCoin) {
Coin* ptr = static_cast<Coin*>(v[vectorIt]);
v.erase(v.begin() + vectorIt);
delete ptr;
}
return false;
}
Thanks to rafix07 for pointing out my mistake in a comment:
Undefined behaviour, it is invalidated in for loop inside update by collisionHandling. collisionHandling iterates over the same container, calling erase
I fixed this by deleting the desired object after the for loop inside the update method, instead of inside the collisionHandling method.
I wrote the following code to implement a concurrent queue.
template <typename T>
class ConcurrentQueue
{
// Internal storage for a queue element
struct Element
{
T m_elem;
std::mutex m_mtx;
std::condition_variable m_cv;
bool m_hasElement = false;
};
public:
// The number of enqueued elements cannot go beyond p_capacity.
ConcurrentQueue(size_t p_capacity) :
m_elements(p_capacity),
m_approxCount(0),
m_actualCount(0),
m_front(0),
m_back(0)
{}
// Enqueues an element to the queue. Returns true on success and false
// if the enqueue failed due to the capacity being reached.
bool Enqueue(T p_element)
{
if (++m_approxCount > m_elements.size())
{
--m_approxCount;
return false;
}
++m_actualCount;
size_t slot = m_back.fetch_add(1) % m_elements.size();
auto& element = m_elements[slot];
std::lock_guard<std::mutex> lk(element.m_mtx);
element.m_elem = std::move(p_element);
element.m_hasElement = true;
element.m_cv.notify_one();
return true;
}
// Dequeues an element from the queue. Returns true on success and false
// if the dequeue failed due to the queue being empty.
bool Dequeue(T& p_element)
{
size_t count = m_actualCount.load();
if (count == 0)
{
return false;
}
while (!m_actualCount.compare_exchange_strong(count, count - 1))
{
if (count == 0)
{
return false;
}
}
size_t slot = m_front.fetch_add(1) % m_elements.size();
auto& element = m_elements[slot];
std::unique_lock<std::mutex> lk(element.m_mtx);
element.m_cv.wait(lk, [&element] { return element.m_hasElement; });
p_element = std::move(element.m_elem);
element.m_hasElement = false;
--m_approxCount;
return true;
}
private:
// Fixed size vector that stores the elements
std::vector<Element> m_elements;
// Approx count of number of elements in the queue.
std::atomic<size_t> m_approxCount;
// Actual count of the number of elements in the queue
std::atomic<size_t> m_actualCount;
// Index to the front of the queue
std::atomic<size_t> m_front;
// Index to the back of the queue
std::atomic<size_t> m_back;
};
and the following test to verify its functionality
int main()
{
int numElements = 1000;
int numThreads = 10;
ConcurrentQueue<int> q(numElements * numThreads / 2);
std::vector<std::thread> enqueueThreads;
for (int i = 0; i < numThreads; ++i)
{
enqueueThreads.emplace_back([&q, i, numElements]
{
for (int j = 0; j < numElements; ++j)
{
while (!q.Enqueue(i * numElements + j));
}
});
}
std::atomic<int> toDequeue = numElements * numThreads;
std::vector<std::thread> dequeueThreads;
for (int i = 0; i < numThreads; ++i)
{
dequeueThreads.emplace_back([&q, &toDequeue]
{
while (toDequeue > 0)
{
int element;
if (q.Dequeue(element))
{
--toDequeue;
}
}
});
}
for (auto& t : enqueueThreads)
{
t.join();
}
for (auto& t : dequeueThreads)
{
t.join();
}
}
In the debug build (VS2017), the test runs fine, but in the retail build, the main function doesn't return (the Dequeue threads don't complete) indicating a bug in the ConcurrentQueue implementation. What is the bug in the Enqueue or Dequeue method?
The Enqueue method needs to wait for slot to be free if the dequeuer hasn't freed it up.
The following code fixed the problem.
template <typename T>
bool ConcurrentQueue<T>::Enqueue(T p_element)
{
if (++m_approxCount > m_elements.size())
{
--m_approxCount;
return false;
}
size_t slot = m_back.fetch_add(1) % m_elements.size();
auto& element = m_elements[slot];
{
std::unique_lock<std::mutex> lk(element.m_mtx);
element.m_cv.wait(lk, [&element] { return !element.m_hasElement; });
element.m_elem = std::move(p_element);
element.m_hasElement = true;
element.m_cv.notify_all();
}
++m_actualCount;
return true;
}
template <typename T>
bool ConcurrentQueue<T>::Dequeue(T& p_element)
{
size_t count = UINT64_MAX;
while (!m_actualCount.compare_exchange_strong(count, count - 1))
{
if (count == 0)
{
return false;
}
}
size_t slot = m_front.fetch_add(1) % m_elements.size();
auto& element = m_elements[slot];
{
std::unique_lock<std::mutex> lk(element.m_mtx);
element.m_cv.wait(lk, [&element] { return element.m_hasElement; });
p_element = std::move(element.m_elem);
element.m_hasElement = false;
element.m_cv.notify_all();
}
--m_approxCount;
return true;
}
I am learning C++ and I have written the following code:
#include <cstdio>
#include <string>
#include <unordered_map>
#include <vector>
using namespace std;
unordered_map<int, vector<uint8_t>> m;
vector<uint8_t> getBitVec(int id)
{
unordered_map<int, vector<uint8_t>>::const_iterator it = m.find(id);
if (it != m.cend()) {
return it->second;
}
return vector<uint8_t>();
}
void aND(vector<uint8_t>& dst, const vector<uint8_t>& src)
{
for (auto i = 0U; i != dst.size(); i++) {
dst[i] = dst[i] & src[i];
}
}
// Get a resultant vector of line_item ids from a bit vector of line items
vector<uint16_t> getVec(const vector<uint8_t>& s)
{
vector<uint16_t> result;
for (auto i = 0U; i != s.size(); i++) {
uint8_t temp = (1<<7);
for (int j = 0; j < 8; j++) {
if (s[i] & (temp >> j)) {
result.push_back(i * 8 + j);
}
}
}
return result;
}
vector<uint16_t> find2(const vector<int>& k)
{
vector<uint8_t> result;
bool flag = true;
for (auto i = 0U; i != k.size(); i++) {
const vector<uint8_t>& v = getBitVec(k[i]);
if (v.empty()) {
return vector<uint16_t>();
}
if (flag == true) {
//result = move(v);
result = v;
flag = false;
continue;
}
aND(result, v);
}
return getVec(result);
}
void init()
{
m[1].push_back(0xff);
m[1].push_back(0xfe);
m[2].push_back(0xf8);
m[2].push_back(0xf9);
m[3].push_back(0xf1);
m[3].push_back(0xf2);
}
int main()
{
init();
const auto& t = find2(vector<int>{1, 2, 3});
for (auto i = 0U; i != t.size(); i++) {
fprintf(stderr, "\n%d\n", t[i]);
}
return 0;
}
Basically I want to write a function which either returns a reference to a non-local "heavy" object or a copy of the empty object.
The function I am implementing is getBitVec (as in the URL).
Can someone tell if the aND will operate on a "temporary copy" of the vector returned or no copy will be created.
I believe that aND function will operate on the copy and I want to get away with the overhead of copy as clearly, the code doesn't need a copy. But can I do this without changing the interface of getBitVec?
This is not a puzzle or hypothetical question and I am not looking for tricks like storing a sentinel key in "m" (with value = vector()) and returning the reference to it, thereby changing the signature to
vector& getBitVect(const vector&)
Is this code idiomatic in C++11?
Do I have no choice but to use pointer as return type for getBitVec?
Thanks for your patience.
If you need a const reference, instead of returning copy of an empty object, you can create a static local instance of the empty object in the function (so it is created once first time function is called and stays alive until the program terminates), and return reference to this static empty object.
If you intend to return non-const reference (the caller of the function may modify the object), then it is best to return pointer to the object instead of reference, and nullptr if object is not found.
const vector<uint8_t>& getBitVec(int id)
{
unordered_map<int, vector<uint8_t>>::const_iterator it = m.find(id);
if (it != m.cend()) {
return it->second;
}
static const vector<uint8_t> empty_vec;
return empty_vec;
}
Or
vector<uint8_t>* getBitVec(int id)
{
unordered_map<int, vector<uint8_t>>::const_iterator it = m.find(id);
if (it != m.cend()) {
return &(it->second);
}
return nullptr;
}