Reputation: 13000
I'd like to have an object whose constructor acts as a begin()
and it's destructor acts as an end()
, and provides functions that are only valid between these two calls as methods. However... I also want to use named constructors, and also have functions that act as factories for this object too.
// this is the object I'm returning by value
class DrawCommand {
DrawCommand(CanvasBuffer & guts, uint32 threadID); // private
public:
// begin(); starts a draw command
static DrawCommand inMT(Canvas & canvas); // for main thread
static DrawCommand inWK(const Job & jobRef); // for worker threads
// end(); submits command to rendering thread
~DrawCommand();
// returns false if draw can be ignored (doesn't need to be respected)
operator bool();
// commands which would break if used outside of begin() and end()
void doStuff();
};
// this class has a factory that returns by value
class Canvas{
public:
DrawCommand debugDrawCommandMT(){
DrawCommand cmd;
...
return cmd;
}
};
// usage
{
DrawCommand cmd1 = DrawCommand::inMT(canvas);
cmd1.doStuff();
} // cmd1.~DrawCommand() upon exiting scope
if (DrawCommand cmd2 = canvas.debugDrawCommandMT()) {
cmd2.doStuff();
} // cmd2.~DrawCommand() upon exiting scope
This means returning this object by value.
This is troublesome as RVO is an optional optimization with side effects. When omitted, this prompts calls to the constructor and destructor. These functions are expensive as they access resources guarded by mutex, so I need to avoid that behavior.
What is the easiest way to ensure this object behaves as if RVO is always taking place when returned?
To be specific, what I'm after is the behavior of RVO, I've been able to find information on what it is and how to hint it's usage to the compiler. But I want to get the side-effect of RVO in a reliable way. Conceptually, it should be as if the object returned isn't a copy, but the original, even if that's not reality.
Upvotes: 1
Views: 96
Reputation: 5503
So this is what I had in mind. You can run this and see the console output.
It's a moveable only type (if you attempt to copy you'll see a compiler error about a deleted function).
It prevents sending the command to the thread if the object has been moved from.
class DrawCommand {
public:
explicit DrawCommand( std::string state ) noexcept
: state_{ std::move( state ) }
, moved_{ false }
{ }
DrawCommand( const DrawCommand& ) = delete;
DrawCommand& operator=( const DrawCommand& ) = delete;
DrawCommand( DrawCommand&& other ) noexcept
: state_{ std::exchange( other.state_, { } ) }
, moved_{ std::exchange( other.moved_, true ) }
{ }
DrawCommand& operator=( DrawCommand&& other ) noexcept {
state_ = std::exchange( other.state_, { } );
moved_ = std::exchange( other.moved_, true );
return *this;
}
~DrawCommand( ) {
if ( moved_ ) std::cout << "Skip sending to thread\n";
else std::cout << "Sending " << state_ << '\n';
}
private:
std::string state_;
bool moved_;
};
static auto create_command( ) -> DrawCommand {
return DrawCommand{ "Some state" };
}
auto main( ) -> int {
{
auto cmd{ create_command( ) };
}
}
Upvotes: 1
Reputation: 12891
This is what I meant, but then in code: (If needed change to shared_ptr and forget about move constructor and just use copy constructor)
#include <iostream>
#include <memory>
class ClassItf
{
public:
virtual ~ClassItf() = default;
virtual void DoWork() = 0;
protected:
ClassItf(const ClassItf&) = delete;
ClassItf(ClassItf&&) = delete;
ClassItf& operator=(ClassItf&) = delete;
ClassItf() = default;
};
// hide implementation
namespace details
{
class ImplementationClass :
public ClassItf
{
public:
ImplementationClass()
{
std::cout << "ImplementationClass::ImplementationClass" << std::endl;
}
~ImplementationClass()
{
std::cout << "ImplementationClass::~ImplementationClass" << std::endl;
}
virtual void DoWork() override
{
std::cout << "ImplementationClass::DoWork" << std::endl;
}
};
}
class YourClass :
public ClassItf
{
public:
YourClass() :
m_impl{ std::make_unique<details::ImplementationClass>() }
{
}
YourClass(YourClass&& other) :
m_impl{ std::move(other.m_impl) }
{
std::cout << "YourClass::YourClass moved" << std::endl;
}
virtual void DoWork() override
{
m_impl->DoWork();
}
private:
std::unique_ptr<ClassItf> m_impl;
};
YourClass CreateClass()
{
YourClass retval;
return retval;
}
int main()
{
{
auto obj = CreateClass();
obj.DoWork();
}
std::cout << "Done..." << std::endl;
}
Upvotes: 0