Tuesday, May 8, 2012

Using Gmock with move-only return types

With the introduction of move semantics in C++11, noncopyable types are far more common. While the STL has largely been updated to support move-only types, this is not yet the case for the multiverse of available C++ libraries. One such library is googlemock. Consider the following:
class Foo  
{  
public:  
    MOCK_METHOD0(Clone, std::unique_ptr<Foo>());  
};  
This will not compile, because the return type of mocked functions must be copyable. A workaround I have been using for simple cases is to use an adapter function:
class Foo  
{  
public:  
    MOCK_METHOD0(Clone_, Foo*());  
    std::unique_ptr<Foo> Clone()  
    {  
      return std::unique_ptr<Foo>(Clone_());  
    }  
};  

This isn't pretty but it works for simple cases where the return object can be easily constructed from some other type. That isn't always possible, as I discovered today when trying to mock a function that returns a unique_future. There's no way of getting a value into a unique_future without getting it from a promise, packaged task, or another future. (The constructor needed to do so is private, and only accessible by friend classes.)

The workaround I came up with is to define a container type which implements move-on-copy for the contained object:
 template <typename T>  
 class Mover  
 {  
 public:  
   Mover(T&& obj)  
   : m_obj(std::move(obj)), m_valid(true)  
   {  
   }  
   
   Mover(const Mover<T>& rhs)  
   : m_obj(const_cast<T&&>(rhs.m_obj)), m_valid(true)  
   {  
     assert(rhs.m_valid);  
     rhs.m_valid = false;  
   }  
   
   Mover& operator=(const Mover& rhs)  
   {  
     assert(rhs.m_valid);  
     m_obj = const_cast<T&&>(rhs.m_obj);  
     rhs.m_valid = false;  
     m_valid = true;  
   }  
   
   bool Valid() const  
   {  
     return m_valid;  
   }  
   
   T& Get()  
   {  
     assert(m_valid);  
     return m_obj;  
   }  
   
   const T& Get() const  
   {  
     assert(m_valid);  
     return *m_obj;  
   }  
   
 private:  
   T m_obj;  
   mutable bool m_valid;  
 };  
   
 template <typename T>  
 inline Mover<T> Movable(T&& obj)  
 {  
   return Mover<T>(std::move(obj));  
 }  

It goes without saying that this is dangerous territory (obviously, any time const_cast is involved, red flags should be going up.) The m_valid field is used to verify that once a Mover has been copied from, any attempt to access the contained object (which is now invalidated as it is in a moved-from state) will result in an assertion.
And now we can use this in our mock:
class Foo  
 {  
 public:  
   MOCK_METHOD0(Bar_, Mover<boost::unique_future<int>>());  
   boost::unique_future<int> Bar()  
   {  
     return std::move(Bar_()).Get());  
   }  
 };  
   
 TEST(FooTest, BarReturnsTheRightFuture)  
 {  
   Foo foo;  
   boost::promise<int> p;  
   EXPECT_CALL(f, Bar_()).Times(1).WillOnce(Return(Movable(p.get_future())));  
 }  
Conceivably this technique could be used in other situations where you need to pass a move-only type through a framework. The validity check offers some semblance of safety, but really, this is likely not suitable for production code. Good enough for unit testing, though.

No comments:

Post a Comment