简体   繁体   English

模板和依赖注入

[英]Templates and dependency injection

I have a class template ResourceManager and it is intended to be used something like this: 我有一个类模板ResourceManager ,旨在用于如下所示的内容:

ResourceManager<Image>* rm =
    ResourceManager<Image>::Instance();

Image* img = rm->acquire("picture.jpg");
rm->release(img);

I'd like to use dependency injection (pass the ResourceManager as a parameter to functions that are supposed to use it instead of having it used globally), however given that it's a template I don't know how to do this. 我想使用依赖注入(将ResourceManager作为参数传递给应该使用它的函数,而不是使其全局使用),但是鉴于它是模板,所以我不知道该怎么做。 Do you have any suggestions? 你有什么建议吗?

My game is only at the beginning of the development and I already have four resource types ( Image , Font , Animation and Sound ) so making a single ResourceManager (ie not a template) with an acquire function for each type of resource is not an option. 我的游戏才刚刚开始开发,我已经拥有四种资源类型( ImageFontAnimationSound ),因此不能为每种类型的资源制作具有获取功能的单个ResourceManager (即不是模板) 。


Edit: Some clarifications. 编辑:一些澄清。

What I'm looking for is not how to do dependency injection for one type of ResourceManager, but for all of them at once. 我要寻找的不是对一种类型的ResourceManager进行依赖注入,而是一次对所有它们进行依赖注入。

My GameState objects need to load resources when they are initialized/opened; 我的GameState对象在初始化/打开时需要加载资源; they do so through the ResourceManagers . 他们通过ResourceManagers这样做。 However, GameStates may need to load any number of types of resources: animations, fonts, images, sounds, etc — that's a lot of function parameters for each kind of ResourceManager ! 但是, GameStates可能需要加载任意数量的资源类型:动画,字体,图像,声音等-每种ResourceManager都有很多功能参数! What do you suggest I do? 你建议我做什么?

Well, if the function needs a particular kind of resource (most will, probably), just define the specific template instance as parameter: 好吧,如果函数需要一种特定类型的资源(可能会用得最多),只需将特定的模板实例定义为参数:

function(ResourceManager<Image> *rm, ...);

If the function needs any kind of resource than it can either 如果该功能需要任何种类的资源,则可以

  1. Be a template itself, like: 成为模板本身,例如:

     template <typename T> function(ResourceManager<T> *rm, ...); 

    It will probably need to refer to the resource obtained from resource manager, so it will need the template argument in more places anyway. 可能需要引用从资源管理器获得的资源,因此无论如何它都需要在更多位置使用template参数。

  2. Use a polymorphic base class. 使用多态基类。 That would mean you'd have to define something like 那意味着您必须定义类似

     class ResourceManagerBase { /* methods you need to call via the base class */ }; template <typename T> class ResourceManager : ResourceManagerBase { ... }; function(ResourceManagerBase *rm, ...) 

    The function can call any methods defined in the base class. 该函数可以调用基类中定义的任何方法。 If the methods depend on resource-type internally, they will be declared abstract virtual ( virtual returnType method(...) = 0 ) in the base and defined in the template class itself. 如果这些方法在内部依赖于资源类型,则将在基类中将它们声明为抽象虚拟( virtual returnType method(...) = 0 ),并在模板类本身中进行定义。 You can also use dynamic_cast to check which particular instantiation of ResourceManager you've got. 您还可以使用dynamic_cast来检查您具有哪个特定的ResourceManager实例。

    Note, that if the function needs to refer to the resource, you will similarly need a ResourceBase abstract base class of all resources, so you can refer to any kind of resource. 请注意,如果函数需要引用资源,则类似地,您将需要所有资源的ResourceBase抽象基类,因此您可以引用任何类型的资源。

The choice is matter of trade-off between faster but very large code with template function (the function will be compiled for each specialization separately) or slower but smaller code with virtual methods (call to virtual method is slower, but there is no code duplication). 选择是在具有模板功能的更快但非常大的代码(该函数将针对每个专业分别编译)之间的权衡问题,或者是使用虚拟方法的较慢但较小的代码(对虚拟方法的调用较慢,但是没有代码重复) )。 Also the template variant will compile slower, because most compilers will generate the code for each object file that uses it and than merge identical copies at link time. 模板变体的编译速度也较慢,因为大多数编译器将为使用它的每个目标文件生成代码,而不是在链接时合并相同的副本。

An example using constructor injection: 使用构造函数注入的示例:

template<typename T>
struct ResourceManager
{
  virtual T* acquire(std::string const& resourceName)
  { return ...; }
  ... etc ...
};

class ImageUser
{
  ResourceManager<Image>* rm_;

  public:
  explicit ImageUser(ResourceManager<Image>* rm)
    : rm_(rm)
  {}

  ImageUser()
    : rm_(ResourceManager<Image>::Instance())
  {}

  void UseImage()
  {
    Image* img = rm_->acquire("picture.jpg");
    rm_->release(img);
  }
};


struct ImageResourceManagerFake : ResourceManager<Image>
{
  virtual Image* acquire(std::string const& resourceName) // override
  { return <whatever-you-want>; }
  ... etc ...
};

void test_imageuser()
{
  ImageResourceManagerFake* rm = new ImageResourceManagerFake;
  ImageUser iu(rm);
  ... test away ...
}

Note that I've left out all resource management; 请注意,我省略了所有资源管理。 use smart pointers etc wherever applicable. 在适用的地方使用智能指针等。

First. 第一。 Remember when you use templates, you must resolve everything at compile time. 请记住,使用模板时,必须在编译时解决所有问题。

Then ResourceManager in your code seems a singleton. 然后,代码中的ResourceManager似乎是单例的。 So there's no difference, rougly speaking, with a global variable. 因此,粗略地说,与全局变量没有区别。

And I think that is useless pass rm as parameters of your functions when you can call the singleton directly. 而且我认为当您可以直接调用单例时,将rm作为函数的参数传递是无用的。

And this resolves, I hope, your question. 希望可以解决您的问题。

Even after your edit I find it a little hard to understand what you're after without seeing the whole picture, but I'll make a second attempt at a basic example: 即使进行了编辑,我仍然很难在不了解整个图片的情况下理解您的想法,但我将再次尝试一个基本示例:

template<typename T>
struct ResourceManager
{
  virtual T* acquire(std::string const& resourceName)
  { return ...; }
  // ... etc ...
};

class ImageUser
{
  ResourceManager<Image>* rm_;

  public:
  explicit ImageUser(ResourceManager<Image>* rm)
    : rm_(rm)
  {}

  void UseImage()
  {
    Image* img = rm_->acquire("picture.jpg");
    rm_->release(img);
  }
};

template<typename T>
struct ResourceManagerFake : ResourceManager<T>
{
  T* acquireRetVal;
  virtual T* acquire(std::string const& resourceName)
  { return acquireRetVal; }
  // ... etc ...
};

void test_imageuser()
{
  ResourceManagerFake<Image>* rm = new ResourceManagerFake<Image>;
  rm->acquireRetVal = new Image;
  ImageUser iu(rm);

  iu.UseImage();
}

Note to commenters: I'm well aware of the resource leaks, but that's not the point here. 注释者注意:我很清楚资源泄漏,但这不是重点。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM