I have a problem where I'm having to instantiate instances of objects earlier than I would like to do so because I need to connect signal slots through some deep ownership, and I'd like to come up with a way of storing and forwarding the slots so that I can construct objects closer to their use site, instead of doing so as member variables.
My basic problem is that I have a process that will download an update file on a separate thread and send a progress signal to anyone who is interested. The signal is essentially:
typedef boost::signals2::signal<void (double)> DownloadProgress;
Assume that the implementation of the progress
function mentioned
below conforms to this; the nature of the signal itself isn't very
important (although I am using functors for the most part).
The signal is set and the code is called something like this:
Updater updater;
updater.onDownloadProgress(&progress);
updater.runDownloadTask();
When you call updater.runDownloadTask()
, it will start the
UpdaterDownloadTask
, which starts an HTTPRequest
and returns an
HTTPResponse
. The HTTPResponse
is the piece which interacts with the
network layer and receives the data and contains the DownloadProgress
signal. With this, my implementation looks a bit like (bottom-up from
HTTPResponse
, heavily abbreviated to elide methods that aren't
particularly illustrative):
class HTTPResponse
{
public:
// this will be called for every "chunk" the underlying HTTP
// library receives
void processData(const char* data, size_t size)
{
// process the data and then send the progress signal
// assume that currentSize_ and totalSize_ are properly set
progressSignal_(currentSize_ * 100.0 / totalSize_);
}
void onDownloadProgress(const DownloadProgress::slot_type& slot)
{
progressSignal_.connect(slot);
}
private:
DownloadProgress progressSignal_;
};
class HTTPRequest
{
public:
HTTPRequest() : response_(new HTTPResponse) { }
void onDownloadProgress(const DownloadProgress::slot_type& slot)
{
response_->connect(slot);
}
boost::shared_ptr<HTTPResponse> perform()
{
// start the request, which operates on response_.
return response_;
}
private:
boost::shared_ptr<HTTPResponse> response_;
};
class UpdaterDownloadTask : public AsyncTask
{
public:
DownloadTask() : request_(new HTTPRequest) { }
void onDownloadProgress(const DownloadProgress::slot_type& slot)
{
request_->connect(slot);
}
void run()
{
// set up the request_ and:
request_>perform();
}
private:
boost::shared_ptr<HTTPRequest> request_;
};
class Updater
{
public:
Updater() : downloadTask_(new UpdaterDownloadTask) { }
void onDownloadProgress(const DownloadProgress::slot_type& slot)
{
downloadTask_->onDownloadProgress(slot);
}
void runDownloadTask() { downloadTask_.submit() }
private:
boost::shared_ptr<UpdaterDownloadTask> downloadTask_;
};
So, my Updater has to have an instance of UpdaterDownloadTask
that's
always around, which has an instance of HTTPRequest
, which has an
instance of HTTPResponse
—just because I have to forward the slot
connection from Updater
(the public API entry point) to HTTPResponse
(where the signal belongs).
I would rather implement UpdaterDownloadTask::run()
like so:
void run()
{
HTTPRequest request;
request.onDownloadProgress(slots_);
#if 0
// The above is more or less equivalent to
BOOST_FOREACH(const DownloadProgress::slot_type& slot, slots_)
{
request.onDownloadProgress(slot);
}
#endif
request.perform();
}
This would have similar implications at the HTTPRequest level (so I
don't have to construct the HTTPResponse until I perform the request)
and overall make for a nicer data flow with strong RAII semantics. I've
previously tried defining the slots_
variable as a vector:
std::vector<DownloadProgress::slot_type> slots_;
Yet I can only get this to work if I force the callers to call
onDownloadProgress(boost::ref(slot));
.
Has anyone done this successfully, or have a good suggestion on how to store and forward other than what I'm doing?
I think storing the slots in a vector should work ok. If you want to get rid of the need for boost::ref(...)
you can remove the &
from the onDownloadProgress
parameter (since slot_type
is copyable).
Alternatively, you could have your signal inside HTTPResponse
fire and in turn fire a signal in HTTPRequest
, doing that, you could connect all the slots to the signal in HTTPRequest
, then once the HTTPResponse
is created, you connect to the response signal onDownloadProgress(request.signalname)
. Where signalname
is the signal your client.
pseudocode:
Request request;
request.onProgress(myProgressBarCallback);
//calls: this.signal.connect(myProgressBarCallback);
request.go();
//calls: Response response;
// and: response.onProgress(this.signal);
I hope that helps.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With