2

这是另一个问题,“我的代码不起作用,我不知道为什么”,我很害怕。我只是没有足够的 stl 知识来知道为什么 std::map::insert 会抛出异常。如果您知道它在哪些情况下会引发异常,您可能可以跳过这堵文字墙并直接回答。如果您只是迫切需要有关该问题的一些背景知识,那就去做吧。我将发布我的代码并解释做了什么,如果所有对 stl 有更好了解的人都可以解释我的插入调用可能有什么问题,我将不胜感激。

不久前我写了一个对象,我偶尔将它用作我的去工厂对象。它的主要目的基本上是获取一个字符串并存储该字符串和一个“创建新对象函数”指针,这样最后你就可以调用一个函数,传递一个字符串,如果它有一个有效的注册,它返回派生对象的新实例。少说话,多代码,这就是我得到的:

工厂.h

#ifndef FACTORY_H
#define FACTORY_H

// library tools
#include <map>
#include <string>

// Simplified registration macros
#define DECLARE_DERIVED(T, base)    static Factory<base>::DerivedRegister<T> reg;
#define DEFINE_DERIVED(T, base, s)  Factory<base>::DerivedRegister<T> T::reg(s); 

template<class base>
class Factory
{
protected:
    template<class T>
    static base * createT() { return new T;}

public:
    typedef std::map<std::string, base*(*)()> map_type;

    virtual ~Factory(){ }

    static base * createInstance(const std::string & s)
    {
        if(!m_Map.count(s))
            return nullptr;
        std::map<std::string, base*(*)()>::iterator it = m_Map.find(s);
        return it->second();
    }

    template <class T>
    struct DerivedRegister;

protected:
    static map_type m_Map; 
};

template<class base>
template<class T>
struct Factory<base>::DerivedRegister : public Factory<base>
{
    DerivedRegister(std::string const & s)
    {
        m_Map.insert(std::pair<std::string, base*(*)()>(s, &createT<T>));
    }
};


#endif

这里有一个更好的解释它真正快速地做什么。假设您有一个基类 A 类。然后你有任意数量的派生类。我在某处模板化为 A 的工厂对象,然后手动创建派生寄存器对象,或使用派生类声明顶部的宏来创建静态注册表对象。然后在实现中定义它并调用它的构造函数,传入一个用于标识对象的字符串。使用工厂成员 createInstance,您可以传入一个字符串标识符并返回一个由 A * 指向的派生对象。

例子:

class A
{

};

A.cpp

// the map for this factory template has to be defined somewhere, as it is static
Factory<A>::map_type Factory<A>::m_Map;

bh

#include <A.h>
class B : public A
{
  // anywhere in declaration of derived B
  DECLARE_DERIVED(A, B)
};

b.cpp

 // just somewhere in cpp file
 DEFINE_DERIVED(A, B, "B")

主文件

int main()
{
  A * ptr;
  Factory<A> factory;

  ptr = factory.createInstance("B");
}

这个对象过去曾为我工作过,大部分都没有问题。现在我正在做一个更复杂的项目。我喜欢游戏引擎所涉及的数据组织/api设计,我只是想实现一个编目解决方案,(但不是实例化的)着色器,这样你就有了一个完整的着色器列表。已经编程,但除非需要,否则它们不会在运行时实例化。除此之外,这个问题实际上与 d3d11 无关,或者至少我希望不是。

这就是正在发生的事情。我有一个代表图形着色器抽象类的对象。您希望编写的所有着色器都必须从该对象派生。您从所有不同的着色器中派生并实现它的功能不同。

让我们在命名空间同步中调用基础对象“SYNC::D3D11Shader”以及派生着色器“ColorShader”、“LightShader”和“TextureShader”。由于我不只是想在渲染对象中创建这些着色器实例的 std::map,因此我在渲染对象中创建了一个工厂,就像这样。

D3D11Renderer.h

class D3D11Renderer
{
    // many other members...
    Factory<D3D11Shader> m_ShaderFactory;
    // many other member...
};

D3D11Renderer.cpp

// define this templated classes map or you'll get undefined errors
Factory<SYNC::D3D11Shader>::map_type Factory<SYNC::D3D11Shader>::m_Map;

然后在 ColorShader 中我使用这样的宏

D3D11ColorShader.h

class D3D11ColorShader : public SYNC::D3D11Shader
{
  // ...lotsa members
  DECLARE_DERIVED(D3D11ColorShader, SYNC::D3D11Shader)
  // lotsa member...
};

D3D11ColorShader.cpp

// define the registery object with it's key here
DEFINE_DERIVED(D3D11ColorShader, SYNC::D3D11Shader, "ColorShader")

这一切都编译得很好,它抛出异常的地方是我第一次在 D3D11ColorShader.cpp 中调用 registryObjects 构造函数,特别是在插入调用时。异常错误是这样的:

Syncopate.exe 中 0x772315de 处的未处理异常:0xC0000005:访问冲突读取位置 0x00000004。

所以实际上,问题归结为 std::map::insert 何时抛出异常以及为什么。我只知道每个人都会询问我正在做的事情的背景。低头,一堵巨大的文字墙出现了!我真正需要的只是一种预感。

我应该还是不应该标记 d3d11,因为这个问题与它无关?

4

3 回答 3

2

这里有一个问题:

   std::map<std::string, base*(*)()>::iterator it = m_Map.find(s);
   return it->second();

如果调用find失败(即在地图中找不到's'),那么它将返回m_Map.end(). 取消引用这是一个禁忌。

于 2012-09-27T02:37:09.407 回答
1

我的猜测是,这是由于静态变量的初始化顺序。没有办法控制这个顺序。所以你不能保证你的初始化:

Factory<A>::map_type Factory<A>::m_Map;

在此初始化之前被调用:

DEFINE_DERIVED(A, B, "B")

在这种情况下,后一个语句必须首先被初始化,所以你的 map 没有被分配。

另一种设计模式将控制单例工厂的初始化。如果你有一个显式的 Initialize 函数来创建工厂对象,那么你可以在 main 的开头调用它。例如

工厂.h

class Factory {
private:
    static Factory* instance_;
public:
    static Initialize(){instance_=new Factory;}
    Factory* instance(){return instance_;}
}

工厂.cpp

static Factory* Factory::instance_ = NULL;

如果您有很多工厂,您可能需要一个初始化函数来初始化它们,并且您必须记住在创建它们时添加新工厂。

于 2012-09-27T02:36:07.503 回答
1

好的,我实际上已经为这个错误努力了大约一天,直到现在我才意识到出了什么问题。

问题1:

派生的着色器标头实际上从未包含在整个项目中的任何地方,尽管它从不需要直接实例化,但它仍然必须包含在某个地方,以便可以链接并包含在构建中。

问题2:

很有趣,就像组合说的那样,初始化顺序不是一个接一个地完成,但是再看我的旧代码,它似乎之前初始化正确。这里的区别是,我将派生对象的工厂放在与基类不同的对象中。我以前做的是在基类中声明一个静态函数和静态工厂,以便您可以从基类本身实例化它的任何注册派生类。当工厂被包含在基类中,并且通过静态函数完成实例化时,所有静态的初始化顺序似乎始终如一(不确定这是否总是正确的)。更改后它现在运行良好。

所以现在,我的回答是,你可以得到这样的操作系统异常,因为你试图使用对从未真正包含在项目中的任何地方的对象的引用。尽管从未包含此对象,但我对编译器或链接器的了解并不多,无法告诉您为什么它似乎编译得很好。如果有人想扩展我的答案,请。

如果与这种困境有关,我会使用 MSVC++ 2010 express。

于 2012-09-27T03:24:36.963 回答