【问题标题】:What's the function signature of a member function?成员函数的函数签名是什么?
【发布时间】:2016-12-26 06:35:07
【问题描述】:

我无法理解函数签名和指针。

struct myStruct
{
    static void staticFunc(){};
    void nonstaticFunc(){};
};

int main()
{
    void (*p)();     // Pointer to function with signature void();
    p = &myStruct::staticFunc;    // Works fine
    p = &myStruct::nonstaticFunc; // Type mismatch   
}

我的编译器说myStruct::nonstaticFunc()的类型是void (myStruct::*)(),但这不是指向它的指针的类型吗?

我问是因为当您创建 std::function 对象时,您传递了您希望它指向的函数的函数签名,例如:

std::function<void()> funcPtr;      // Pointer to function with signature void()
not 
std::function<void(*)()> funcPtr;

如果我必须根据void() 的模式进行猜测,我会说:

void myStruct::();
or
void (myStruct::)();

但这是不对的。我不明白为什么我应该添加星号只是因为它是非静态的而不是静态的。也就是说,指针void(* )()指向带有签名void()的函数,而指针void(myStruct::*)()指向带有签名什么的函数?

【问题讨论】:

  • 你不能在std::function 中持有一个成员函数而不将它绑定到一个类对象。指向成员的指针不像常规的函数指针。
  • 不能将成员函数直接赋值给std::function,需要绑定
  • 我知道如何指向非静态成员函数,我只需声明 void(myStruct::*)();但我想知道它的签名是什么,因为 void(*)() 指向的函数的签名是 void()
  • @TitoneMaurice - 如果您只想存储成员函数以供以后使用,请参阅我的编辑。
  • C++ 没有任何特定的签名格式。成员函数的签名在 1.3.20 中定义为 name、参数类型列表 (8.3.5)、函数所属的类、cvqualifiers (如果有)和 ref-qualifier(如果有)。请注意签名中包含名称。

标签: c++ function pointers


【解决方案1】:

对我来说,似乎对成员指针是什么存在基本的误解。例如,如果您有:

struct P2d {
    double x, y;
};

成员指针double P2d::*mp = &amp;P2d::x; 不能指向特定 P2d 实例的x 坐标,而是指向名称x 的“指针”:获取双精度您需要提供您正在寻找的 P2d 实例...例如:

P2d p{10, 20};

printf("%.18g\n", p.*mp); // prints 10

这同样适用于成员函数...例如:

struct P2d {
    double x, y;
    double len() const {
        return sqrt(x*x + y*y);
    }
};

double (P2d::*f)() const = &P2d::len;

其中f 不是指向特定实例的成员函数的指针,它需要使用this 来调用

printf("%.18g\n", (p.*f)());

f 换句话说就是P2d 的哪些const 成员函数不接受任何参数并返回您感兴趣的double 的“选择器”。在这种特定情况下(因为只有一个成员函数兼容),这样的选择器可以使用零位存储(您可以将该指针设置为的唯一可能值是 &amp;P2d::len)。

请不要因为一开始不理解成员指针而感到羞耻。它们确实有点“奇怪”,而且没有多少 C++ 程序员理解它们。

说实话,它们也不是那么有用:最常用的是指向特定实例的方法的指针。

C++11 提供了 std::function 包装器和 lambdas:

std::function<double()> g = [&](){ return p.len(); };

printf("%.18g\n", g()); // calls .len() on instance p

【讨论】:

  • 据我了解,非静态成员函数不同于成员变量。 obj1.intMemb 将具有与 obj2.intMemb 不同的地址,但 obj1.membFunc 将具有所有对象的相同地址,甚至我认为是虚拟的。它就像一个带有 this 指针的普通静态函数,语言强制你通过一个对象来调用它,所以它可以保证获取到 'this' 对象的地址。它可能不允许您将其分配给 void*p 因为您不能像 p(); 那样调用它;但我不明白为什么你不能这样称呼它......
  • @TitoneMaurice:非静态成员指针不是内存地址;在示例中,数据成员指针可以是单个位 (0=x, 1=y),并且在函数中成员指针不需要任何存储(因为只有一个兼容的成员函数可以指向)。静态成员指针(指向数据和代码)是常规指针。但是请注意,在 C 和 C++ 中,函数代码实际上并没有“内存地址”;哲学模型使代码和数据在逻辑上分离:例如,您不能将函数指针存储在 void * 中,反之亦然。内存用于数据。
【解决方案2】:
std::function<void()> funcPtr = std::bind(&myStruct::nonstaticFunc, obj);

是如何在std::function 中存储成员函数的。成员函数必须在有效对象上调用。


如果你想延迟传递一个对象,你可以这样完成:

#include <functional>
#include <iostream>

struct A {

    void foo() { std::cout << "A::foo\n"; }
};

int main() {
    using namespace std::placeholders;

    std::function<void(A&)> f = std::bind(&A::foo, _1);

    A a;
    f(a);

    return 0;
}

std::bind 将为您处理详细信息。 std::function 仍然必须具有常规函数的签名,因为它是类型参数。但它可以屏蔽一个成员,如果该对象作为参数出现在函数中。


附录
对于分配到std::function,您甚至不需要std::bind 来进行对象的后期绑定,只要原型正确即可:

std::function<void(A&)> f = &A::foo;

【讨论】:

  • std::function&lt;void(A&amp;)&gt; f = &amp;A::foo; 应该也能正常工作,没有bind。它已经将&amp;A::foo 视为“如果通过A 可以调用的东西”。
  • @hvd - 图我应该把它加进去。谢谢。我一直忘记bind 的使用次数与每个新标准版本相差甚远。
【解决方案3】:
p = &myStruct::staticFunc;    // Works fine
p = &myStruct::nonstaticFunc; // Type mismatch

原因:函数到指针的转换从不适用非静态成员函数,因为左值引用了非静态成员函数 无法获取。


指针void(*)()指向带有签名void()的函数,指针void(myStruct::*)()指向带有签名什么的函数?

myStruct::是确保struct myStruct非静态成员函数被调用(不是其他struct的,如下图):

struct myStruct
{
    static void staticFunc(){};
    void nonstaticFunc(){};
};
struct myStruct2
{
    static void staticFunc(){};
    void nonstaticFunc(){};
};

int main()
{
    void (*p)();     // Pointer to function with signature void();
    void (myStruct::*f)();
    p = &myStruct::staticFunc;    // Works fine
    p = &myStruct2::staticFunc;   // Works fine
    f = &myStruct::nonstaticFunc; // Works fine
    //f =  &myStruct2::nonstaticFunc;  // Error. Cannot convert 'void (myStruct2::*)()' to 'void (myStruct::*)()' in assignment

    return 0;
}

【讨论】:

    【解决方案4】:

    当您使用指针、std::function 或 std::bind 来引用非静态成员函数(即 Foo 类的“方法”)时,第一个参数必须是 Foo 类的具体对象,因为非静态方法必须由具体对象调用,而不是由类调用。

    更多详情:std::functionstd::bind.

    【讨论】:

      【解决方案5】:

      答案在doc

      指向成员声明符的指针:声明 S C::* D;D 声明为 指向C 的非静态成员的指针,其类型由下式确定 decl-specifier-seq S.

      struct C
      {
          void f(int n) { std::cout << n << '\n'; }
      };
      
      int main()
      {
          void (C::* p)(int) = &C::f; // pointer to member function f of class C
          C c;
          (c.*p)(1);                  // prints 1
          C* cp = &c;
          (cp->*p)(2);                // prints 2
      }
      

      没有签名void ()的函数。有void (*)() 用于函数或void (foo::*)() 用于foo 方法。星号是强制性的,因为它是指向 x 的指针。 std::function 与此无关。

      注意:您的困惑是void()void (*)() 的签名相同。甚至int() int (*)()。也许你认为你可以写int (foo::*) 来获得一个方法 指针。但这是一个数据成员指针,因为括号是可选的,int (foo::*) int foo::*

      为了避免这种晦涩的语法,您需要编写指向具有返回类型、星号及其参数的函数/成员的指针。

      【讨论】:

      • 对,那么签名是什么?指针void()()指向函数签名void(),指针void(myStruct::)()指向函数签名what?
      • 我认为这样一个函数的签名是void(),这就是为什么你可以声明:void aFuncTakingAFunc(void());
      猜你喜欢
      • 2022-06-28
      • 1970-01-01
      • 1970-01-01
      • 2010-09-11
      • 2017-06-28
      • 2011-06-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多