Maybe it's my sinuses and that I fact that I just started learning about smart pointers today I'm trying to do the following:
Push to the queue
Get the element in the front
Pop the element (I think it will automatically deque once the address out of scope)
Here is the error
main.cpp:50:25: error: cannot convert ‘std::remove_reference&>::type’ {aka ‘std::unique_ptr’} to ‘std::unique_ptr*’ in assignment
50 | inputFrame = std::move(PacketQueue.front());
| ~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~
| |
| std::remove_reference<std::unique_ptr<MyObject::Packet>&>::type {aka std::unique_ptr<MyObject::Packet>}
Here is the code
#include <iostream>
#include <memory>
#include <queue>
using namespace std;
class MyObject
{
public:
struct Packet
{
uint8_t message;
uint8_t index;
};
void pushToQueue(void);
void FrontOfQueue(std::unique_ptr<Packet> *inputFrame);
private:
std::queue<std::unique_ptr<Packet>> PacketQueue;
};
void MyObject::pushToQueue(void)
{
Packet frame;
static int counter = 1;
frame.message = counter;
frame.index =counter;
counter++;
std::unique_ptr<Packet> passthru_ptr = std::make_unique<Packet>(std::move(frame));
PacketQueue.push(std::move(passthru_ptr));
cout<<"Pushed to queue\n" ;
}
void MyObject::FrontOfQueue(std::unique_ptr<Packet> *inputFrame)
{
inputFrame = std::move(PacketQueue.front());
}
int main()
{
cout<<"Hello World\n";
MyObject object;
object.pushToQueue();
object.pushToQueue();
{
// Scope
std::unique_ptr<MyObject::Packet> *frame;
object.FrontOfQueue(frame);
cout<< frame << endl;
}
{
// Scope
std::unique_ptr<MyObject::Packet> *frame2;
object.FrontOfQueue(frame2);
cout<< frame2 << endl;
}
return 0;
}
Link to the code (Online Compiler)
If I got your aim correctly, you definitely want
std::unique_ptr<MyObject::Packet> MyObject::FrontOfQueue()
{
auto rv = std::move(PacketQueue.front());
PacketQueue.pop();
return rv;
}
// ...
std::unique_ptr<MyObject::Packet> frame = object.FrontOfQueue();
Notice, no raw pointers are used.
I think it will automatically deque once the address out of scope.
This assumption is wrong. Nothing is dequeued until .pop() is called.
Here is my example with some extra logging to show whats going on.
includes an introduction of returning const references as well.
Live demo : https://onlinegdb.com/P2nFkdMy0
#include <iostream>
#include <memory>
#include <queue>
#include <string>
//-----------------------------------------------------------------------------
// do NOT use : using namespace std;
//-----------------------------------------------------------------------------
struct Packet
{
// moved to uint32_t for std::cout reasons.
// uint8_t is displayed as(special) characters
std::uint32_t index;
std::uint32_t message;
Packet() :
index{ next_index() },
message{ index }
{
std::cout << "created packet : " << index << "\n";
}
~Packet()
{
std::cout << "destroyed packet : " << index << "\n";
}
// small helper to not have to declare the static variable seperatly
static std::uint8_t next_index()
{
static int counter;
return counter++;
}
};
//-----------------------------------------------------------------------------
class MyObject
{
public:
void push_packet();
std::unique_ptr<Packet> pop_packet();
// this function returns a const reference (observation only)
// of the packet at the front of the queue
// while leaving the unique pointer on the queue (no moves needed
// packet will still be owned by the queue)
const Packet& front();
private:
std::queue<std::unique_ptr<Packet>> m_queue;
};
void MyObject::push_packet()
{
std::cout << "push_packet\n";
// push a packet
m_queue.push(std::make_unique<Packet>());
std::cout << "push_packet done...\n";
}
std::unique_ptr<Packet> MyObject::pop_packet()
{
std::unique_ptr<Packet> packet = std::move(m_queue.front());
m_queue.pop();
return packet;
}
const Packet& MyObject::front()
{
return *m_queue.front();
}
//-----------------------------------------------------------------------------
int main()
{
const std::size_t n_packets = 3ul;
MyObject object;
for (std::size_t n = 0; n < n_packets; ++n)
{
std::cout << "pushing packet\n";
object.push_packet();
}
for (std::size_t n = 0; n < n_packets; ++n)
{
std::cout << "packet at front : ";
std::cout << object.front().index << "\n";
std::cout << "popping front\n";
auto packet_ptr = object.pop_packet();
std::cout << "popped packet : " << packet_ptr->index << "\n";
}
return 0;
}
Related
I'm writing an async gRPC server in C++ (on windows). I'd like to use the boost intrusive pointer type for the 'tag' value - the pointer to the RPC handler objects which are returned during the completion queue 'Next()' method.
The gRPC async service requires passing a void* to the handler object so it can call the handler when the associated event occurs. The problem is that I'm unable to find a way to convert my boost intrusive pointer to a void* in a way that preserves and uses the reference count.
Is it possible? Or would it only work if the method I pass the pointer to expects a boost pointer?
Let's say we have a third party library that takes static callback functions, which can use a void* userdata to pass user-defined state around:
namespace SomeAPI {
typedef void(*Callback)(int, void* userdata);
struct Registration;
Registration const* register_callback(Callback cb, void* userdata);
size_t deregister_callback(Callback cb, void* userdata);
void some_operation_invoking_callbacks();
}
A minimalist implementation of this fake API is e.g.:
struct Registration {
Callback cb;
void* userdata;
};
std::list<Registration> s_callbacks;
Registration const* register_callback(Callback cb, void* userdata) {
s_callbacks.push_back({cb, userdata});
return &s_callbacks.back();
}
size_t deregister_callback(Callback cb, void* userdata) {
auto oldsize = s_callbacks.size(); // c++20 makes this unnecessary
s_callbacks.remove_if([&](Registration const& r) {
return std::tie(r.cb, r.userdata) == std::tie(cb, userdata);
});
return oldsize - s_callbacks.size();
}
void some_operation_invoking_callbacks() {
static int s_counter = 0;
for (auto& reg : s_callbacks) {
reg.cb(++s_counter, reg.userdata);
}
}
Let's Have A Client
The Client owns state which is managed by some shared pointer:
struct MyClient {
struct State {
std::string greeting;
void foo(int i) {
std::cout
<< "State::foo with i:" << i
<< " and greeting:" << std::quoted(greeting)
<< "\n";
}
};
using SharedState = std::shared_ptr<State>;
SharedState state_;
Now, we want the State::foo member to be registered as a callback, and the state_ should be passed as the user-data:
MyClient(std::string g) : state_(std::make_shared<State>(State{g})) {
SomeAPI::register_callback(static_handler, &state_);
}
~MyClient() noexcept {
SomeAPI::deregister_callback(static_handler, &state_);
}
static void static_handler(int i, void* userdata) {
auto& state = *static_cast<SharedState*>(userdata);
state->foo(i);
}
};
Now to exercise the Client a bit:
Live On Coliru
int main() {
MyClient client1("Foo");
std::cout << " ------- operation start\n";
SomeAPI::some_operation_invoking_callbacks();
{
MyClient client2("Bar");
std::cout << " ------- operation start\n";
SomeAPI::some_operation_invoking_callbacks();
}
std::cout << " ------- operation start\n";
SomeAPI::some_operation_invoking_callbacks();
}
Prints:
------- operation start
State::foo with i:1 and greeting:"Foo"
------- operation start
State::foo with i:2 and greeting:"Foo"
State::foo with i:3 and greeting:"Bar"
------- operation start
State::foo with i:4 and greeting:"Foo"
Tight-Rope Act
If you actually want to pass ownership to the API, in the sense that it keeps the state around even if the Client instance is gone, you will, by definition, leak that state.
The only solution to this would be if the API had some kind of callback to signal that it should cleanup up. I've never seen such an API design, but here is what it would look like for this:
enum { TEARDOWN_MAGIC_VALUE = -1 };
void InitAPI() {}
void ShutdownAPI() {
for (auto it = s_callbacks.begin(); it != s_callbacks.end();) {
it->cb(TEARDOWN_MAGIC_VALUE, it->userdata);
it = s_callbacks.erase(it);
}
}
Now we can pass a dynamically allocated copy of the shared_ptr to the call-back as user-data (instead of a raw pointer to the owned copy of the shared pointer):
SomeAPI::register_callback(static_handler, new SharedState(state_));
Note that because SomeAPI now has a copy of the shared-pointer, the refcount has increaed. In fact, we don't have to deregister the callback anymore because the State will stay valid, until SomeAPI actually shuts down:
static void static_handler(int i, void* userdata) {
auto* sharedstate = static_cast<SharedState*>(userdata);
if (i == SomeAPI::TEARDOWN_MAGIC_VALUE) {
delete sharedstate; // decreases refcount
return;
} else {
(*sharedstate)->foo(i);
}
}
The main program is basically un-altered, but for InitAPI() and ShutdownAPI() calls:
int main() {
SomeAPI::InitAPI();
MyClient client1("Foo");
std::cout << " ------- operation start\n";
SomeAPI::some_operation_invoking_callbacks();
{
MyClient client2("Bar");
std::cout << " ------- operation start\n";
SomeAPI::some_operation_invoking_callbacks();
}
std::cout << " ------- operation start\n";
SomeAPI::some_operation_invoking_callbacks();
SomeAPI::ShutdownAPI();
}
Through some clever tracing of destructors, you can verify that lifetime of State is now actually governed/shared by ShutdownAPI:
Live On Coliru
------- operation start
State::foo with i:1 and greeting:"Foo"
------- operation start
State::foo with i:2 and greeting:"Foo"
State::foo with i:3 and greeting:"Bar"
~MyClient (Bar)
------- operation start
State::foo with i:4 and greeting:"Foo"
State::foo with i:5 and greeting:"Bar"
~State (Bar)
~MyClient (Foo)
~State (Foo)
Full Listing (Classic)
Live On Coliru
#include <memory>
#include <list>
#include <string>
#include <iostream>
#include <iomanip>
namespace SomeAPI {
using Callback = void(*)(int, void* userdata);
struct Registration {
Callback cb;
void* userdata;
};
std::list<Registration> s_callbacks;
Registration const* register_callback(Callback cb, void* userdata) {
s_callbacks.push_back({cb, userdata});
return &s_callbacks.back();
}
size_t deregister_callback(Callback cb, void* userdata) {
auto oldsize = s_callbacks.size(); // c++20 makes this unnecessary
s_callbacks.remove_if([&](Registration const& r) {
return std::tie(r.cb, r.userdata) == std::tie(cb, userdata);
});
return oldsize - s_callbacks.size();
}
void some_operation_invoking_callbacks() {
static int s_counter = 0;
for (auto& reg : s_callbacks) {
reg.cb(++s_counter, reg.userdata);
}
}
}
struct MyClient {
struct State {
std::string greeting;
void foo(int i) {
std::cout
<< "State::foo with i:" << i
<< " and greeting:" << std::quoted(greeting)
<< "\n";
}
};
using SharedState = std::shared_ptr<State>;
SharedState state_;
MyClient(std::string g) : state_(std::make_shared<State>(State{g})) {
SomeAPI::register_callback(static_handler, &state_);
}
~MyClient() noexcept {
SomeAPI::deregister_callback(static_handler, &state_);
}
static void static_handler(int i, void* userdata) {
auto& state = *static_cast<SharedState*>(userdata);
state->foo(i);
}
};
int main() {
MyClient client1("Foo");
std::cout << " ------- operation start\n";
SomeAPI::some_operation_invoking_callbacks();
{
MyClient client2("Bar");
std::cout << " ------- operation start\n";
SomeAPI::some_operation_invoking_callbacks();
}
std::cout << " ------- operation start\n";
SomeAPI::some_operation_invoking_callbacks();
}
Full Listing (Contrived)
Live On Coliru
#include <memory>
#include <list>
#include <string>
#include <iostream>
#include <iomanip>
namespace SomeAPI {
enum { TEARDOWN_MAGIC_VALUE = -1 };
using Callback = void(*)(int, void* userdata);
struct Registration {
Callback cb;
void* userdata;
};
std::list<Registration> s_callbacks;
Registration const* register_callback(Callback cb, void* userdata) {
s_callbacks.push_back({cb, userdata});
return &s_callbacks.back();
}
size_t deregister_callback(Callback cb, void* userdata) {
auto oldsize = s_callbacks.size(); // c++20 makes this unnecessary
s_callbacks.remove_if([&](Registration const& r) {
bool const matched = std::tie(r.cb, r.userdata) == std::tie(cb, userdata);
if (matched) {
r.cb(TEARDOWN_MAGIC_VALUE, r.userdata);
}
return matched;
});
return oldsize - s_callbacks.size();
}
void some_operation_invoking_callbacks() {
static int s_counter = 0;
for (auto& reg : s_callbacks) {
reg.cb(++s_counter, reg.userdata);
}
}
void InitAPI() {}
void ShutdownAPI() {
for (auto it = s_callbacks.begin(); it != s_callbacks.end();) {
it->cb(TEARDOWN_MAGIC_VALUE, it->userdata);
it = s_callbacks.erase(it);
}
}
}
struct MyClient {
struct State {
std::string greeting;
State(std::string g) : greeting(std::move(g)) {}
void foo(int i) {
std::cout
<< "State::foo with i:" << i
<< " and greeting:" << std::quoted(greeting)
<< "\n";
}
~State() noexcept {
std::cout << "~State (" << greeting << ")\n";
}
};
using SharedState = std::shared_ptr<State>;
SharedState state_;
MyClient(std::string g) : state_(std::make_shared<State>(std::move(g))) {
SomeAPI::register_callback(static_handler, new SharedState(state_));
}
~MyClient() {
std::cout << "~MyClient (" << state_->greeting << ")\n";
}
static void static_handler(int i, void* userdata) {
auto* sharedstate = static_cast<SharedState*>(userdata);
if (i == SomeAPI::TEARDOWN_MAGIC_VALUE) {
delete sharedstate; // decreases refcount
return;
} else {
(*sharedstate)->foo(i);
}
}
};
int main() {
SomeAPI::InitAPI();
MyClient client1("Foo");
std::cout << " ------- operation start\n";
SomeAPI::some_operation_invoking_callbacks();
{
MyClient client2("Bar");
std::cout << " ------- operation start\n";
SomeAPI::some_operation_invoking_callbacks();
}
std::cout << " ------- operation start\n";
SomeAPI::some_operation_invoking_callbacks();
SomeAPI::ShutdownAPI();
}
I am trying to implement observer design pattern in C++ as below
#include <iostream>
#include <vector>
using namespace std;
class observer
{
public:
observer() = default;
~observer() = default;
virtual void notify() = 0;
};
class subject
{
vector <observer *> vec;
public:
subject() = default;
~subject() = default;
void _register(observer *obj)
{
vec.push_back(obj);
}
void unregister(observer *obj)
{
int i;
for(i = 0; i < vec.size(); i++)
{
if(vec[i] == obj)
{
cout << "found elem. unregistering" << endl;
vec.erase(vec.begin() + i);
break;
}
}
if(i == vec.size())
{
cout << "elem not found to unregister" << endl;
}
}
void notify()
{
vector <observer *>::iterator it = vec.begin();
while(it != vec.end())
{
(*it)->notify();
it ++;
}
}
};
class obsone : public observer
{
void notify()
{
cout << "in obsone notify" << endl;
}
};
class obstwo : public observer
{
void notify()
{
cout << "in obstwo notify" << endl;
}
};
int main()
{
subject sub;
obsone *one = new obsone();
obstwo *two = new obstwo();
sub._register(one);
sub._register(two);
sub.notify();
sub.unregister(one);
sub.notify();
//delete two;
//sub.notify();
return 0;
}
I am registering the objects with the subject explicitly. Is it the correct way of doing it or do I need to register through observer class only. Are there any problems with the above approach?
Here's an example of doing the callbacks with lambdas and function objects in the callback collection.
The details can vary greatly! So, this code is not “the” way, but just your code rewritten in one specific way, out of a myriad possibilities. But it hopefully shows the general idea in modern C++.
#include <iostream>
#include <functional> // std::function
#include <stdint.h> // uint64_t
#include <unordered_map> // std::unordered_map
#include <utility> // std::move
#include <vector> // std::vector
using namespace std;
namespace my
{
using Callback = function<void()>;
template< class Key, class Value > using Map_ = unordered_map<Key, Value>;
class Subject
{
public:
enum Id: uint64_t {};
private:
Map_<uint64_t, Callback> m_callbacks;
static auto id_value()
-> uint64_t&
{
static uint64_t the_id;
return the_id;
}
public:
auto add_listener( Callback cb )
-> Id
{
const auto id = Id( ++id_value() );
m_callbacks.emplace( id, move( cb ) );
return id;
}
auto remove_listener( const Id id )
-> bool
{
const auto it = m_callbacks.find( id );
if( it == m_callbacks.end() )
{
return false;
}
m_callbacks.erase( it );
return true;
}
void notify_all() const
{
for( const auto& pair : m_callbacks )
{
pair.second();
}
}
};
}
struct Observer_1
{
void notify() { cout << "Observer_1::notify() called." << endl; }
};
struct Observer_2
{
void notify() { cout << "Observer_2::notify() called." << endl; }
};
auto main()
-> int
{
my::Subject subject;
Observer_1 one;
Observer_2 two;
using Id = my::Subject::Id;
const Id listener_id_1 = subject.add_listener( [&]{ one.notify(); } );
const Id listener_id_2 = subject.add_listener( [&]{ two.notify(); } );
cout << "After adding two listeners:" << endl;
subject.notify_all();
cout << endl;
subject.remove_listener( listener_id_1 )
and (cout << "Removed listener 1." << endl)
or (cout << "Did not find registration of listener 1." << endl);
cout << endl;
cout << "After removing or attempting to remove listener 1:" << endl;
subject.notify_all();
}
I have a list of objects, each object has member variables which are calculated by an "update" function. I want to update the objects in parallel, that is I want to create a thread for each object to execute it's update function.
Is this a reasonable thing to do? Any reasons why this may not be a good idea?
Below is a program which attempts to do what I described, this is a complete program so you should be able to run it (I'm using VS2015). The goal is to update each object in parallel. The problem is that once the update function completes, the thread throws an "resource dead lock would occur" exception and aborts.
Where am I going wrong?
#include <iostream>
#include <thread>
#include <vector>
#include <algorithm>
#include <thread>
#include <mutex>
#include <chrono>
class Object
{
public:
Object(int sleepTime, unsigned int id)
: m_pSleepTime(sleepTime), m_pId(id), m_pValue(0) {}
void update()
{
if (!isLocked()) // if an object is not locked
{
// create a thread to perform it's update
m_pThread.reset(new std::thread(&Object::_update, this));
}
}
unsigned int getId()
{
return m_pId;
}
unsigned int getValue()
{
return m_pValue;
}
bool isLocked()
{
bool mutexStatus = m_pMutex.try_lock();
if (mutexStatus) // if mutex is locked successfully (meaning it was unlocked)
{
m_pMutex.unlock();
return false;
}
else // if mutex is locked
{
return true;
}
}
private:
// private update function which actually does work
void _update()
{
m_pMutex.lock();
{
std::cout << "thread " << m_pId << " sleeping for " << m_pSleepTime << std::endl;
std::chrono::milliseconds duration(m_pSleepTime);
std::this_thread::sleep_for(duration);
m_pValue = m_pId * 10;
}
m_pMutex.unlock();
try
{
m_pThread->join();
}
catch (const std::exception& e)
{
std::cout << e.what() << std::endl; // throws "resource dead lock would occur"
}
}
unsigned int m_pSleepTime;
unsigned int m_pId;
unsigned int m_pValue;
std::mutex m_pMutex;
std::shared_ptr<std::thread> m_pThread; // store reference to thread so it doesn't go out of scope when update() returns
};
typedef std::shared_ptr<Object> ObjectPtr;
class ObjectManager
{
public:
ObjectManager()
: m_pNumObjects(0){}
void updateObjects()
{
for (int i = 0; i < m_pNumObjects; ++i)
{
m_pObjects[i]->update();
}
}
void removeObjectByIndex(int index)
{
m_pObjects.erase(m_pObjects.begin() + index);
}
void addObject(ObjectPtr objPtr)
{
m_pObjects.push_back(objPtr);
m_pNumObjects++;
}
ObjectPtr getObjectByIndex(unsigned int index)
{
return m_pObjects[index];
}
private:
std::vector<ObjectPtr> m_pObjects;
int m_pNumObjects;
};
void main()
{
int numObjects = 2;
// Generate sleep time for each object
std::vector<int> objectSleepTimes;
objectSleepTimes.reserve(numObjects);
for (int i = 0; i < numObjects; ++i)
objectSleepTimes.push_back(rand());
ObjectManager mgr;
// Create some objects
for (int i = 0; i < numObjects; ++i)
mgr.addObject(std::make_shared<Object>(objectSleepTimes[i], i));
// Print expected object completion order
// Sort from smallest to largest
std::sort(objectSleepTimes.begin(), objectSleepTimes.end());
for (int i = 0; i < numObjects; ++i)
std::cout << objectSleepTimes[i] << ", ";
std::cout << std::endl;
// Update objects
mgr.updateObjects();
int numCompleted = 0; // number of objects which finished updating
while (numCompleted != numObjects)
{
for (int i = 0; i < numObjects; ++i)
{
auto objectRef = mgr.getObjectByIndex(i);
if (!objectRef->isLocked()) // if object is not locked, it is finished updating
{
std::cout << "Object " << objectRef->getId() << " completed. Value = " << objectRef->getValue() << std::endl;
mgr.removeObjectByIndex(i);
numCompleted++;
}
}
}
system("pause");
}
Looks like you've got a thread that is trying to join itself.
While I was trying to understand your solution I was simplifying it a lot. And I come to point that you use std::thread::join() method in a wrong way.
std::thread provide capabilities to wait for it completion (non-spin wait) -- In your example you wait for thread completion in infinite loop (snip wait) that will consume CPU time heavily.
You should call std::thread::join() from other thread to wait for thread completion. Mutex in Object in your example is not necessary. Moreover, you missed one mutex to synchronize access to std::cout, which is not thread-safe. I hope the example below will help.
#include <iostream>
#include <thread>
#include <vector>
#include <algorithm>
#include <thread>
#include <mutex>
#include <chrono>
#include <cassert>
// cout is not thread-safe
std::recursive_mutex cout_mutex;
class Object {
public:
Object(int sleepTime, unsigned int id)
: _sleepTime(sleepTime), _id(id), _value(0) {}
void runUpdate() {
if (!_thread.joinable())
_thread = std::thread(&Object::_update, this);
}
void waitForResult() {
_thread.join();
}
unsigned int getId() const { return _id; }
unsigned int getValue() const { return _value; }
private:
void _update() {
{
{
std::lock_guard<std::recursive_mutex> lock(cout_mutex);
std::cout << "thread " << _id << " sleeping for " << _sleepTime << std::endl;
}
std::this_thread::sleep_for(std::chrono::seconds(_sleepTime));
_value = _id * 10;
}
std::lock_guard<std::recursive_mutex> lock(cout_mutex);
std::cout << "Object " << getId() << " completed. Value = " << getValue() << std::endl;
}
unsigned int _sleepTime;
unsigned int _id;
unsigned int _value;
std::thread _thread;
};
class ObjectManager : public std::vector<std::shared_ptr<Object>> {
public:
void runUpdate() {
for (auto it = this->begin(); it != this->end(); ++it)
(*it)->runUpdate();
}
void waitForAll() {
auto it = this->begin();
while (it != this->end()) {
(*it)->waitForResult();
it = this->erase(it);
}
}
};
int main(int argc, char* argv[]) {
enum {
TEST_OBJECTS_NUM = 2,
};
srand(static_cast<unsigned int>(time(nullptr)));
ObjectManager mgr;
// Generate sleep time for each object
std::vector<int> objectSleepTimes;
objectSleepTimes.reserve(TEST_OBJECTS_NUM);
for (int i = 0; i < TEST_OBJECTS_NUM; ++i)
objectSleepTimes.push_back(rand() * 9 / RAND_MAX + 1); // 1..10 seconds
// Create some objects
for (int i = 0; i < TEST_OBJECTS_NUM; ++i)
mgr.push_back(std::make_shared<Object>(objectSleepTimes[i], i));
assert(mgr.size() == TEST_OBJECTS_NUM);
// Print expected object completion order
// Sort from smallest to largest
std::sort(objectSleepTimes.begin(), objectSleepTimes.end());
for (size_t i = 0; i < mgr.size(); ++i)
std::cout << objectSleepTimes[i] << ", ";
std::cout << std::endl;
// Update objects
mgr.runUpdate();
mgr.waitForAll();
//system("pause"); // use Ctrl+F5 to run the app instead. That's more reliable in case of sudden app exit.
}
About is it a reasonable thing to do...
A better approach is to create an object update queue. Objects that need to be updated are added to this queue, which can be fulfilled by a group of threads instead of one thread per object.
The benefits are:
No 1-to-1 correspondence between thread and objects. Creating a thread is a heavy operation, probably more expensive than most update code for a single object.
Supports thousands of objects: with your solution you would need to create thousands of threads, which you will find exceeds your OS capacity.
Can support additional features like declaring dependencies between objects or updating a group of related objects as one operation.
first i used flyweight for string which works fine, but when i use flyweight for a struct. it doesn't work.
the first test case for string is:
static void testflyweightString()
{
char tmp[0];
vector<boost::flyweight<string>> boost_v;
for(int i=0;i<10000000;i++)
{
sprintf(tmp,"zws_%d",i/1000);
boost_v.pushback(boost::flyweight<string>(tmp));
}
return;
}
then i defined a struct A, some properties in A i used flyweight.
testcase2 is as below:
static void testflyweightA()
{
vector<A> boost_v;
for(int i=0;i<10000000;i++)
{
A a();//here new some A;
boost_v.pushback(a);
}
return;
}
but it doesn't have any change for memory used whether i used flyweight in A or not.
First off:
A a();//here new some A;
This is: Most vexing parse: why doesn't A a(()); work?
I prepared this test program:
Live On Coliru
#include <boost/flyweight.hpp>
#include <vector>
#include <iostream>
static void testflyweightString() {
std::cout << __FUNCTION__ << "\n";
std::vector<boost::flyweight<std::string> > boost_v;
for (int i = 0; i < 10000000; i++) {
boost_v.emplace_back("zws_" + std::to_string(i/1000));
}
}
struct A {
boost::flyweight<std::string> s;
A(std::string const& s) : s(s) { }
};
static void testflyweightA() {
std::cout << __FUNCTION__ << "\n";
std::vector<A> boost_v;
for (int i = 0; i < 10000000; i++) {
boost_v.push_back("zws_" + std::to_string(i/1000));
}
}
int main() {
testflyweightString();
testflyweightA();
std::cout << "Done\n";
}
Its memory usage looked ok using valgrind --tool=massif:
I'll go straight to an example, I think it is easier to underestand.
Music Cd has Tracks. How can I access A TrackInfo vector (XTrackInfo) data "inside" Music Cd class?
I want to print and even change values, I don't figure out how.
Thanks
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <iterator>
#include <numeric>
class XTrackInfo
{
std::string m_TrackName;
int m_Length;
public:
XTrackInfo() {}
XTrackInfo(std::string TrackName, int Length):
m_TrackName(std::move(TrackName)),
m_Length(Length)
{}
void SetTrackName(std::string TrackName) { m_TrackName = std::move(TrackName); }
void SetTrackLength(int Length) { m_Length = Length; }
const std::string& GetTrackName() const { return m_TrackName; }
int GetTrackLength() const { return m_Length; }
};
class XMusicCd
{
private:
std::string m_Author;
std::vector<XTrackInfo> m_TrackInfo;
public:
XMusicCd() {}
XMusicCd(std::string Author, std::vector<XTrackInfo> Tracks):
m_Author(std::move(Author)),
m_TrackInfo(std::move(Tracks))
{}
void SetAuthor(std::string Author) { m_Author = std::move(Author); }
const std::string& GetAuthor() const { return m_Author; }
const std::vector<XTrackInfo> GetTracks() const { return m_TrackInfo;}
int GetLength() const; // Left incomplete on purpose; you will implement it later
void AddTrack(XTrackInfo NewTrack){
m_TrackInfo.emplace_back(std::move(NewTrack));
}
};
void PrintCdContents(const XMusicCd& Cd)
{
std::cout << "Author : " << Cd.GetAuthor() << "\n";
std::cout << "\n" << std::endl;
std::cout << "Track Info" << std::endl;
//problems here :)
}
int main()
{
// You may not change this function
XMusicCd MyCd;
MyCd.SetAuthor("Hello World");
MyCd.AddTrack(XTrackInfo("This is a test", 100));
MyCd.AddTrack(XTrackInfo("This is a test 2", 200));
PrintCdContents(MyCd);
}
Use iterators:
std::vector<XTrackInfo> tracks = Cd.GetTracks();
for (std::vector<XTrackInfo>::const_iterator it = tracks.begin(); it != tracks.end(); ++it) {
std::cout << it->GetTrackName() << std::endl;
}
Or indexes:
std::vector<XTrackInfo> tracks = Cd.GetTracks();
for (unsigned i = 0; i < tracks.size(); ++i) {
std::cout << tracks.at(i).GetTrackName() << std::endl;
}