如何传递指向构造函数的函数指针?

44

我正在实现C++中的反射机制。 我的代码中的所有对象都是Object的子类(我自己定义的通用类型),它们包含一个名为Class的静态成员变量。

class Class{
public:
   Class(const std::string &n, Object *(*c)());
protected:
   std::string name;     // Name for subclass
   Object *(*create)();  // Pointer to creation function for subclass
};

对于任何具有静态Class成员数据的Object子类,我希望能够使用指向该子类构造函数的指针来初始化“create”。


虽然这已经是6年后的事情了,但你应该认真考虑是否真的想要实现自己的反射机制。首先考虑使用模板、类型特征和SFINAE原则来解决编译时的“反射”问题;然后尝试使用现有的C++反射库;只有在这些都不可行的情况下,才考虑自己动手实现。 - einpoklum
8个回答

71

你不能获取构造函数的地址(C++98标准12.1/12 Constructors - "12.1-12 Constructors - "The address of a constructor shall not be taken.")

最好的方法是有一个工厂函数或方法来创建Object并传递工厂函数的地址:

class Object;

class Class{
public:
   Class(const std::string &n, Object *(*c)()) : name(n), create(c) {};
protected:
   std::string name;     // Name for subclass
   Object *(*create)();  // Pointer to creation function for subclass
};

class Object {};

Object* ObjectFactory()
{
    return new Object;
}



int main(int argc, char**argv)
{
    Class foo( "myFoo", ObjectFactory);

    return 0;
}

7
将其变为模板将使其实际返回“Class”:template<typename T> Object* ObjectFactory() { return new T; } .... Class foo("myFoo", &ObjectFactory<Class>); - Johannes Schaub - litb
原因:https://dev59.com/gGsz5IYBdhLWcg3wbHPG - Rufus

8
我遇到了同样的问题。我的解决方案是一个调用构造函数的模板函数。
template<class T> MyClass* create()
{
    return new T;
}

要将其用作函数指针很简单:
MyClass* (*createMyClass)(void) = create<MyClass>;

要获取MyClass的实例:

MyClass* myClass = createMyClass();

6
Lambda风格:
[](){return new YourClass();}

2
请考虑为此添加更多的解释/背景信息,这将使得答案对未来的读者更有用。 - EJoshuaS - Stand with Ukraine
1
请务必说明这是C++11及以后版本的特性。 - Kareem
你可以省略括号 () - Jimmy R.T.
1
这绝对是最简洁的解决方案。您甚至可以使用更复杂的模板来定义一个适用于所有类的所有构造函数的 lambda。 - Smiley1000
@Smiley1000,您能否通过编辑详细说明一下? - That Realty Programmer Guy
1
@ThatRealtyProgrammerGuy 我在考虑类似于 auto create = []<typename Class, typename... Args>(Args&&... args){ return Class(std::forward<Args>(args)...); }; (https://godbolt.org/z/7P97q5e9h) 这样的东西,类似于 https://dev59.com/2XNA5IYBdhLWcg3wdthd#62412513。 - Smiley1000

5

使用可变参数模板,您可以创建一个包装器,将构造函数转换为仿函数。

#include <utility>

template <typename T>
struct BindConstructor{
    template<typename... Args>
    T operator()(Args&&...args)const{
        return T(std::forward<Args>(args)...);
    }
};


struct Foo {
    Foo(int a, int b):a(a),b(b){}
    int a;
    int b;
};

template <typename Fn>
auto Bar(Fn f){
    return f(10,20);
}

int main(){
    Foo foo = Bar(BindConstructor<Foo>());
}

https://godbolt.org/z/5W383McTc


1
你在 operator() 的参数上忘记了 && - user362515
谢谢。已修复。 - bradgonesurfing

3

有点奇怪。 create 是一个成员变量,即只能在类实例中使用,但其意图似乎是首先创建一个实例。

您不能获取构造函数的地址,但是您可以创建自己的静态工厂方法并获取其地址。


1
当我构造Class对象时,我传入一个名称和一个创建函数。我的目标是拥有一个类列表,可以在项目的任何地方引用它们。 指向静态成员函数的指针将起作用。 - Kareem

1
如果你想要一个代表构造函数的实际函数指针,而不是一个函数对象或闭包,你可以创建一个通用工厂模板:
#include <iostream>

namespace {
    template <typename Type, typename... Args>
    Type create(Args... args)
    {
        return Type{std::forward<Args>(args)...};
    }
}

template <typename Type, typename... Args>
static constexpr auto constructor = &create<Type, Args...>;

struct Foo {
    Foo(double v){ std::cout << "Foo ctor " << v << std::endl; }
};

int main()
{
    auto z = constructor<Foo, double>(0.33);

    return 0;
}

Foo ctor 0.33

https://godbolt.org/z/qKGGGzjTs

  • 这与Michael Burr's answer类似,但您没有针对每个对象的静态工厂方法,并且允许任何类型的构造函数(而不仅仅是默认)
  • 这与bradgonesurfing's answer类似,但您为每个构造函数创建一个函数指针,而不是为表示该类所有构造函数的每个函数子创建一个函数子。

尽管这个问题已经很旧并且已经有了几个非常有帮助的答案,但我还是要添加这个额外的建议,因为为任何类的每个构造函数创建一个函数指针在实现运行时类型反射系统时非常有用。


你可能想在create函数中使用std::forward。这会破坏一些东西,特别是移动操作。另外,那个double const&应该只是一个double - MSalters
实际上,我刚刚删除了完美转发,因为它与函数指针不兼容:https://godbolt.org/z/Toq9Goah7,函数指针的类型将解析为`Foo (* const&)(double&&)`,这不再是通用引用,而是一个普通的右值引用。绝对不是我想要的。另请参见https://dev59.com/AHQOtIcB2Jgan1znh1m6。你能举个没有完美转发就会出错的例子吗? - joergbrech
你关于将 double const& 改为 double 是正确的,我会相应地编辑答案。 - joergbrech
好的,如果构造函数实际上需要一个右值引用,它就会出错。我注意到,如果我们使用std::forward参数,但不为参数使用通用引用,则代码按预期工作:https://godbolt.org/z/6a96brGfW。我将相应地编辑答案。 - joergbrech

1

你不能在方法上使用普通的函数指针,必须使用方法指针,其语法非常奇怪:

void (MyClass::*method_ptr)(int x, int y);
method_ptr = &MyClass::MyMethod;

这会给你一个指向 MyClass 的方法 - MyMethod 的指针。然而,这不是一个真正的指针,因为它不是一个绝对的内存地址,它基本上是一个偏移量(由于虚继承更加复杂,但那些东西是实现特定的)进入一个类。所以要使用方法指针,你必须提供一个类,就像这样:
MyClass myclass;
myclass.*method_ptr(x, y);

或者

MyClass *myclass = new MyClass;
myclass->*method_ptr(x, y);

当然,现在显而易见的是,您不能使用方法指针来指向对象的构造函数。为了使用方法指针,您需要拥有该类的实例,因此其构造函数已经被调用!所以在您的情况下,Michael的对象工厂建议可能是最好的方法。

9
这不正确。&MyClass::MyMethod 会给你实际可执行代码的内存地址。方法有一个隐含的第一个参数,即this指针。你不是用类来提供一个方法,而是用对象(类的实例,例如一个this指针)。去查一下thiscall调用约定。 - Rob K
2
我必须补充说明的是,如果方法是虚拟的,获取代码位置以调用该方法涉及更多的魔法,但方法的存在与类的任何实例的存在无关。 - Rob K
啊,你说得对。从概念上讲,这对于 OP 来说没有太大的区别,无论哪种方式,除非类的实例存在,否则方法指针都不起作用。我已经修复了我的帖子以反映这一点。 - Niki Yoshiuchi

0
使用Qt,如果您将构造函数声明为Q_INVOKABLE,则可以使用Qt反射机制(QMetaObject)调用构造函数(除此之外无需进行其他操作)。
class MyClass : public QObject {
   Q_OBJECT
public:
   Q_INVOKABLE MyClass(int foo);
   MyClass *cloningMySelf() {
     return metaObject()->newInstance(Q_ARG(int, 42));
   }
};

我不确定你是否想要仅为此功能嵌入Qt;-)但也许你想看看它是如何实现的。

http://doc.qt.io/qt-5/metaobjects.html#meta-object-system


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接