6

有一个正在测试的类当前unique_ptr< Interface >&&在其构造函数中接受 a ,以表示它希望获得接口实现的单一所有权。当想要使用模拟来测试这个类时会出现问题Interface:模拟框架(HippoMocks)只给我Interface*我不拥有的,因此不能删除。

在测试作为参数的类之前,我遇到了同样的问题const shared_ptr< Interface >&,但通过提供自定义无操作删除器解决了这个问题:

template< class T >
void NoDelete( T* )
{
}

  //create a shared_ptr without effective deleter
template< class T >
std::shared_ptr< T > mock_shared( T* t )
{
  return std::shared_ptr< T >( t, NoDelete< T > );
}

Interface* iface = mocks.GetMeAMock< Interface >();
DoStuffWithSharedPtrOfInterface( mock_shared< Interface >( iface ) );

unique_ptr 的类似修复并没有真正奏效,因为删除器是模板参数:

template< class T >
struct NoDelete
{
  void operator ()( T* )
  {
  }
};

  //oops this is totally useless since std::unique_ptr< T, NoDelete< T > >
  //is not quite the same type as std::unique_ptr< T >
template< class T >
std::unique_ptr< T, NoDelete< T > > mock_unique( T* t )
{
  return std::unique_ptr< T, NoDelete< T > >( t, NoDelete< T >() );
}

有解决方法吗?或者我不应该首先在这里使用 unique_ptr 吗?

更新 我试了一下;应该可以工作,但 sizeof( ptr ) 现在是 8,很难说它有什么影响。

  //use CustomUniquePtr::type instead of uniqe_ptr
template< class T >
struct CustomUniquePtr
{
  typedef typename std::unique_ptr< T, void (*) ( T* ) > type;
}

  //use everywhere
template< class T >
CustomUniquePtr< T >::type make_unique( T* p )
{
  return CustomUniquePtr< T >::type( p, Delete< T > );
}

  //use when mocking, doe not delete p!
template< class T >
CustomUniquePtr< T >::type mock_unique( T* p )
{
  return CustomUniquePtr< T >::type( p, NoDelete< T > );
}
4

4 回答 4

7

shared_ptr将其删除器与其他簿记数据(引用计数等)一起存储在堆上;unique_ptr没有堆开销,因此删除器必须存储在对象中并成为类型的一部分。

您可以将构造函数模板化Deleter并将其转换unique_ptr为 ashared_ptr以擦除删除器类型。

更好的(取决于接口的大小)将提供一个代理Interface对象转发到 mocked Interface *

于 2012-06-14T17:05:25.793 回答
3

我可以想到几个选项,没有特别的顺序:

  • 在测试代​​码中(并且在测试代码中,您不希望在您的应用程序中这样做)专门default_delete<Interface>用于不实际删除任何内容。这将意味着永远不会删除它拥有的对象,如果您的对象即使在测试中也应该删除拥有的对象unique_ptr<Interface>,这可能是不可取的。unique_ptr<Interface>

  • 创建一个实现Interface,将所有内容转发给提供给其构造函数的实例。然后,您可以在转发到模拟框架提供的接口的测试中动态分配一个实例。

  • 更改模拟框架以动态分配接口,以便可以将其删除。

  • 更改您的代码以使用 astd::shared_ptr以便您可以传递自定义删除器。但是,这会失去“唯一所有权”属性。

  • 更改您的代码以使用根据构造参数删除或不删除的自定义智能指针。这可能只是 a 的包装unique_ptr,带有“是否删除”标志。在分配/销毁调用中,如果标志设置为“不删除”,那么只需调用release()包装unique_ptr而不是允许它删除对象。用户对自定义指针类型的熟悉程度低于标准指针类型,并且标志会占用空间。

  • 使用类型生成器,就像你在“更新”中一样,所以到处都说CustomUniquePtr<T>::type,然后让类型生成器添加删除器。这里的缺点是用户代码必须知道删除器才能从原始指针创建该类型的新实例。这意味着用户代码Interface在不知道删除器的情况下无法轻松地创建您的实现(即使是一个简单的只记录调用并转发它们的实现)。

当然可能还有其他选择。

于 2012-06-20T07:04:32.930 回答
2

Hippomock 已经为这个问题提供了解决方案。如果你有一个带有虚拟析构函数的接口,那么你需要做的就是为析构函数注册一个期望。模拟不会因调用其析构函数而被破坏,因为它是模拟析构函数,但必须设置对析构函数调用的期望。

MockRepository mocks;
// create the mock
std::unique_ptr<IFoo> foo( mocks.Mock<IFoo>() );

// register the expectation for the destructor
mocks.ExpectCallDestructor( foo.get() );

// call to mocks destructor ok, mock not destroyed
foo.reset( nullptr );
于 2013-02-08T09:47:22.517 回答
0

在答案中给出的所有选项中,最合适的一个肯定是修改模拟框架,因为我可以继续使用 plain unique_ptr。我不知道如何让它动态分配模拟实例,但事实证明我可以让它模拟delete自己。这很 hacky,但它似乎工作正常。

基本上 hippomocks 直接更改 vtable,使条目指向检查期望等的函数。现在,据我所知,在 MSVC 上,具有虚拟析构函数的类的 vtable 中的第一个条目是标量删除析构函数。调用delete ptr. 而且,这通常是编译器生成并调用析构函数。因此,改变 vtable 并使第一个入口点指向其他东西,即无操作,解决了这个问题。

这是添加到 MockRepository 的代码,它只是更改的函数之一的副本RegisterExpect_ro 始终使用void(void*)签名:

template< int X >
void NoOp( void* )
{
}

  //make first vtable entry point to NoOp
template< int X, typename Z2 >
void DeleteExpect( Z2 *mck )
{
  const int funcIndex = 0; //index of scalar deleting destructor
  void (MockRepository::*mfp)(void*) = &MockRepository::NoOp< X >;
  BasicRegisterExpect( reinterpret_cast<mock<Z2> *>( mck ),
   funcIndex, reinterpret_cast<void (base_mock::*)()>( mfp ), X );
}

#define MockDelete( obj ) DeleteExpect< __COUNTER__ >( obj )

以下是它的使用方法:

MockRepository mocks;
Interface* p = mocks.InterfaceMock< Interface >();
mocks.MockDelete( p );

mocks.InterfaceMock< DataStitcher >( std::unique_ptr< Interface >( p ) );
于 2012-06-21T11:36:27.403 回答