C++ 赋予另一个类访问特定方法的权限

C++ 赋予另一个类访问特定方法的权限,c++,templates,callback,game-engine,C++,Templates,Callback,Game Engine,我在夏天做游戏引擎项目。每个可编写脚本的组件都应该能够访问它们所在场景中的某些方法。为了实现这一点,我将lambda从调用相应方法的场景传递到脚本,在脚本中它们被隐式转换为std::function类型 场景h: class Scene { private: unsigned int _currentId; std::vector<System*> _systems; //SCRIPTABLE NEEDS THE BELOW METHODS THESE EX

我在夏天做游戏引擎项目。每个可编写脚本的组件都应该能够访问它们所在场景中的某些方法。为了实现这一点,我将lambda从调用相应方法的场景传递到脚本,在脚本中它们被隐式转换为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是游戏行为的基类。这就是为什么我需要想出一些东西,让脚本有限地访问场景


有什么想法吗?

您不能让类型擦除模板回调。您必须在模板或类型擦除之间进行选择。让我解释一下

这就是模板回调的样子。这实际上是一个通用的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需要一个特定的签名来处理,但限制远不止于此

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

下面是一个简单的例子:

template<typename T>
void foo(T t) {
    std::cout << t << std::endl;
}
当编译器看到这一点时,它知道您需要一个名为foo的函数,该函数以double作为参数。没有名为foo的函数接受双精度。但是我们给了编译器一个工具来创建一个:模板

因此编译器将生成此函数:

void foo_double(double t) {
    std::cout << t std::endl;
}
指向函数foo的指针有一个值,该值指向foo在内存中的位置

现在想象一下,我们有一种方法可以使用模板函数指针。我们必须指出一个还不存在的函数。它没有内存位置,因此没有意义。通过指针,当作为函数调用时,必须生成函数代码

由于指向函数的指针可以指向任何函数,甚至是编译器还不知道的函数,因此您必须以某种方式生成函数代码并对其进行编译。但指针的值(指针指向的函数)是在运行时定义的!因此,当编译器不再存在时,您必须在运行时从一个不存在的值编译您还不知道的代码。如您所见,指向模板函数、模板std::函数或虚拟模板函数的指针不可能存在

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

您使用回调似乎只是为了能够调用私有成员函数。这是一种错误的方法,即使它有效。您需要的是朋友,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);
    }

    // ...
};

没有指向模板函数的指针。这需要编译运行时代码。这个问题毫无意义。只要模板参数不是固定的,模板就是一个模板。它不是类型,也不是函数。您可以使用C++11功能定义模板typedef,但如果不指定模板参数,您仍然无法将其作为类型使用。您介意解释system->correspondsT的含义吗?而使用通常比typedef更具可读性,模板需要使用:使用GetComponentCallback=std::function;的模板;。但这仍然需要使用类似GetComponentCallback for std::function@ojojKOLOL:回调在C++中是一个相当广泛的概念。这取决于你所说的回调是什么意思。如果希望回调目标存储在std::函数中,则必须首先专门化目标模板,即为所有模板参数指定具体参数,之后它将不再是模板。谢谢!这就是答案,我想我需要再读一遍哈哈。@Ojojkolo我建议你多读一些关于模板及其工作原理的内容。它们是C++中最大的主题之一,我肯定会的,谢谢!我看到你也在开发一个游戏引擎,进展如何?@ojojkolol我很想讨论一下,但评论部分专门讨论答案/问题。然而,我可以说,尽管多年的工作,几乎没有一些基本的游戏,我有很多不可替代的经验。我鼓励您继续您的项目,了解更多关于C++和编程和奖励将到来。谢谢,很高兴听到这一点。我非常喜欢游戏开发。我使用unity已经有一段时间了,但是我的想法通常都是围绕2D的,所以unity感觉不对。当然,你可以在unity中制作完美的2D游戏,但是在3D环境中工作感觉很奇妙 . 我每天学习越来越多,感觉游戏引擎是一个学习软件设计的完美项目,尤其是C++。
void foo_double(double t) {
    std::cout << t std::endl;
}
// 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;
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);
    }

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