【问题标题】:Give another class access to specific methods让另一个类访问特定方法
【发布时间】:2017-08-02 08:44:35
【问题描述】:

我在暑假期间将游戏引擎作为一个项目进行工作。每个可编写脚本的组件都应该可以访问它们所在场景中的某些方法。为了实现这一点,我将调用相应方法的场景中的 lambdas 传递给可编写脚本的组件,在这些组件中它们被隐式转换为 std::function 类型。

场景.h:

class Scene
{
private:
    unsigned int _currentId;
    std::vector<System*> _systems;

    //SCRIPTABLE NEEDS THE BELOW METHODS THESE EXCLUSIVELY:

    bool exists(unsigned id);
    void destroy(unsigned int);
    void addComponent(Component*, unsigned int);
    template<typename T> T& getComponent(unsigned int);
    template<typename T> bool hasComponent(unsigned int);
    template<typename T> void removeComponent(unsigned int);

protected:
    unsigned int instantiate(std::vector<Component*>);

public:
    Scene(ChangeSceneCallback);
    ~Scene();
    void initiate();
    void update(long dt);
};

template<typename T>
inline T & Scene::getComponent(unsigned int id)
{
    for (System* system : _systems) {
        if (system->corresponds(T)) {
            return static_cast<T*>(system->getComponent(entityId));
        }
    }
}

template<typename T>
inline bool Scene::hasComponent(unsigned int id)
{
    for (System* system : _systems) {
        if (system->corresponds(T)) {
            return system->contains(id);
        }
    }
}

template<typename T>
inline void Scene::removeComponent(unsigned int id)
{
    for (System* system : _systems) {
        if (system->corresponds(T)) {
            return system->destroy(id);
        }
    }
}

回调方法适用于我需要访问的非模板函数,但不适用于模板函数,所以它是不可能的。

可编写脚本:

typedef std::function<void(int)>                                    ChangeSceneCallback;
typedef std::function<int(std::vector<Component*>)>                 InstantiateCallback;
typedef std::function<void(int)>                                    DestroyCallback;
typedef std::function<bool(int)>                                    ExistCallback;
typedef std::function<void(Component*, unsigned int)>               AddComponentCallback;


class Scriptable: public Component
{
protected:
    ChangeSceneCallback changeScene;
    InstantiateCallback instantiate;
    DestroyCallback destroy;
    ExistCallback exists;
public:
    ~Scriptable();
    Scriptable();
    void assignCallbacks(ChangeSceneCallback, InstantiateCallback etc ...);

    virtual void init() = 0;
    virtual void update() = 0;
}; 

Scriptable 无法访问场景中的公共方法,因为这将使用户/开发人员能够访问它们(Scriptable 是游戏行为的基类)。这就是为什么我需要想出一些东西来让脚本有限地访问场景。

有什么想法吗?

【问题讨论】:

  • 没有指向模板函数的指针。它需要运行时代码编译。
  • 这个问题毫无意义。只要模板参数不固定(特化),模板就是模板。它不是类型,也不是函数。您可以定义模板 typedef(使用 C++11 功能),但这仍然不允许您在不指定模板参数的情况下将其用作类型。
  • 您介意解释一下system-&gt;corresponds(T) 的含义吗?
  • using 通常比typedef 更具可读性,using 是模板所必需的:template&lt;typename T&gt; using GetComponentCallback = std::function&lt;T&amp;(unsigned int)&gt;;。但这仍然需要为std::function&lt;MyComponent&amp;(unsigned int)&gt; 使用GetComponentCallback&lt;MyComponent&gt; 之类的东西
  • @ojoj kolol:“回调”是当今 C++ 中一个相当广泛的概念。这取决于您所说的“回调”是什么意思。如果您希望将回调目标存储在std::function 中,那么您必须首先专门化目标模板,即为所有模板参数指定具体参数(之后它将不再是模板)。

标签: c++ templates callback game-engine


【解决方案1】:

你不能有一个类型被删除的“模板回调”。您必须在模板或类型擦除之间进行选择。让我解释一下。

这就是“模板回调”的样子。这实际上是一个通用的 lambda:

auto print_callback = [](auto var) {
    std::cout << var << std::endl;
}

print_callback(4) ;      // prints "4"
print_callback(4.5);     // prints "4.5"
print_callback("hello"); // prints "hello"

看起来不错,但请注意,您不能使用 std::function 执行此操作,因为您必须预定义签名。

std::function<void(int)> func_print_callback = print_callback;

func_print_callback(5); // Yay! Prints "5"
func_print_callback("hello"); // error

问题是,您可能认为限制只是因为std::function 需要特定的签名才能使用,但限制远不止于此。

问题是,没有模板函数。他们不存在。另一方面,函数模板确实存在。为什么我如此强调我的语言顺序是因为这个东西的名字说明了一切:它不是一个函数,它是一个模板,用来制作功能。

这是一个简单的例子:

template<typename T>
void foo(T t) {
    std::cout << t << std::endl;
}

此函数未编译。因为它不是函数。直到孔 T 被填满,函数 foo 才会存在。

你如何填补名为T 应该是一个类型的洞?

当然是用一种类型来填充它!

foo(5.4); // the hole T is `double`

当编译器看到这一点时,它就知道您需要一个名为foo 的函数,该函数将双精度数作为参数。没有名为 foo 的函数采用 double。但是我们给了编译器一个工具来创建它:模板!

所以编译器会生成这个函数:

void foo_double(double t) {
    std::cout << t std::endl;
}

这里的词是这样的:generate。编译器需要创建函数才能存在。编译器为你生成代码。

当函数生成并编译时,T 不再存在。模板参数是编译时实体,只有编译器知道。


现在,我将向您解释为什么没有模板回调之类的东西。

类型擦除的容器如std::function 是用函数指针实现的。我将使用类型别名来简化语法。它的工作原理是这样的:

// A function
void foo(int) {}

// The type of the pointer to function
using func_ptr = void(*)(int);

// A pointer to foo
func_ptr ptr = &foo;

指向函数 foo 的指针有一个值指向内存中 foo 的位置。

现在想象我们有一种方法来获得模板函数指针。我们必须指向一个尚不存在的函数。它没有内存位置,所以它没有意义。而通过指针,当作为函数调用时,你必须生成函数代码。

由于指向函数的指针可以指向任何函数,即使是编译器还不知道的函数,您必须以某种方式生成函数代码并对其进行编译。但是指针的值,我们的指针指向的函数,是在运行时定义的!因此,当编译器不再存在时,您必须在运行时从不存在的值编译您还不知道的代码。可以看到,指向模板函数的指针、模板std::function或虚拟模板函数都不存在。


既然你已经理解了问题,让我提出一个解决方案:放弃回调使用。您应该直接调用这些函数。

您似乎只使用回调来调用私有成员函数。这是错误的方法,即使它有效。你需要的是friend,C++ 允许你访问私有成员的特性。

class Scene {
    friend Component;

    // ...
};

class Component {
protected:

    // Let `scene` be a reference to your scene

    void addComponent(Component* c, unsigned int id) {
        scene.addComponent(c, id);
    }

    template<typename T>
    T& getComponent(unsigned int id) {
        return scene.getComponent<T>(id);
    }

    template<typename T>
    bool hasComponent(unsigned int id) {
        return scene.hasComponent(id);
    }

    template<typename T>
    void removeComponent(unsigned int id) {
        removeComponent(id);
    }

    // ...
};

由于Component 类是Scene 的唯一朋友,因此只有它可以调用私有成员函数。由于Component 中所有新定义的函数都受到保护,因此只有从Component 扩展的类才能调用它们。它们的调用方式如下:

class Scriptable : public Component {
    void foo() {
        hasComponent<Bar>(87); // works, call function defined in `Component`
    }
};

【讨论】:

  • 谢谢!这就是答案,我想我需要再读一遍哈哈。
  • @ojojkolol 我建议您阅读有关模板及其工作原理的更多信息。它们是 C++ 中最大的主题之一
  • 我一定会的,谢谢!我看到你也在做游戏引擎,进展如何?
  • @ojojkolol 我真的很想讨论它,但评论部分专门讨论答案/问题。不过我可以说,尽管工作了多年,而且几乎没有一些基本的游戏,但它给了我很多不可替代的经验。我鼓励你继续你的项目,学习更多关于 C++ 和编程的知识,奖励将会到来。
  • 谢谢,很高兴听到这个消息。我真的很喜欢游戏开发。我使用 Unity 已经有一段时间了,但我的想法通常围绕 2D,所以 Unity 感觉“错误”(当然,你可以在 unity 中制作完美的 2D 游戏,但在 3D 环境中工作感觉很奇怪)。我每天都在学习,感觉游戏引擎是学习软件设计尤其是 C++ 的完美项目。
猜你喜欢
  • 2020-07-12
  • 2022-12-05
  • 2013-11-24
  • 1970-01-01
  • 1970-01-01
  • 2014-10-06
  • 2017-05-12
  • 2013-12-27
  • 2016-08-30
相关资源
最近更新 更多