void(U::*)(void) 的意思是什么?

6

我正在查看Boost中is_class模板的实现,并遇到了一些语法,我无法轻松地解释。

    template <class U> static ::boost::type_traits::yes_type is_class_tester(void(U::*)(void));
    template <class U> static ::boost::type_traits::no_type is_class_tester(...);

我该如何解释上面的 void(U::*)(void)?我熟悉C语言,所以它似乎有点类似于void(*)(void),但我不明白U::是如何修改指针的。有人能帮忙吗?
谢谢。

1
U::* 表示指针所指向的函数是 U 的成员,这是必要的,因为在 C++ 中,在成员函数的底层,第一个参数是指向调用它的对象的指针。 - Austin Hyde
@Austin Hyde:对于那些附加在类上的函数/方法,至少对于自由方法或类静态方法来说不是这种情况。 - Matthieu M.
@Matthieu,非常正确。感谢你指出。 - Austin Hyde
5个回答

13

* 代表指针,因为你可以通过写 *p 来访问其内容。 U::* 表示指向类 U 成员的指针。你可以通过写 u.*ppu->*p(其中 uU 的一个实例)来访问其内容。

所以,在你的例子中,void (U::*)(void) 是指向 U 类成员的指针,它是一个不带参数且不返回值的函数。

示例:

class C { void foo() {} };

typedef void (C::*c_func_ptr)(void);

c_func_ptr myPointer = &C::foo;

3

您是正确的,它类似于函数指针。实际上,这是一个成员函数指针,其中成员是类U的成员。

类型的差异是必要的,因为成员函数具有隐式的this指针,不能在没有实例的情况下调用。去掉模板可能会使它更容易:

struct foo
{
    void bar(void);
};

void(*)(void) 不行,因为它没有办法传递类的实例。相反,我们需要:

void (foo::*)(void)

这表示函数指针需要一个 foo 的实例。


就其价值而言,您可以像这样使用它们:

typedef void (foo::*func_ptr)(void);

foo f;
foo* fp = &f;
func_ptr func = &foo::bar;

(f.*func)();
(fp->*func)();

3

从U开始,向内部工作。

声明的类型是指向类U中采用void参数并返回void的成员函数的指针。


2
但是...'U'是一个类,不是一个指针。你的意思是,'is_class_tester'的参数是一个成员函数的指针吗?那就说得通了。 - Aidan Cully
请注意,这里的类型(方法指针的类型)没有被命名(因为它在错误消息中)。因此,在这种情况下,U是调用该方法的类的名称。 - Martin York

3

它是指向类U的成员函数的指针。它与void(*)(void)非常相似,但它指向类U的成员函数。


0
我本来也想问这个问题,但已经有人问过了。不幸的是,现有的答案都没有真正回答这个问题。至少对我来说是这样。我必须自己琢磨。我想问的问题和 OP 一样,还有更多。我的问题是:这个 is_class_tester (void (U::*)(void)) 是什么鬼,它在 SFINAE(替换失败不是错误)的上下文中如何工作?
简单来说,Boost 使用这个结构体如下:
template <typename U>
char is_class_tester (void (U::*)(void));

template <typename U>
TypeBiggerThanChar is_class_tester (...);

template <typename T>
struct IsClass {
   static const bool value = sizeof (is_class_tester<T>(0)) == 1;
};

一些观察:

这些函数模板并不是真正的函数模板,它们只是一对重载函数模板的前向声明。函数模板本身从来没有被定义。了解这一点并理解如何在不需要定义模板的情况下工作是理解这个结构的关键要素之一。
那些谈论如何使用第一个函数模板的答案都没抓住关键。因为其定义不存在,所以无法使用此函数模板。
请注意,由于奇怪的参数,只有当类型 T 是类时,两个函数模板中的第一个才有意义。基本类型和指针没有成员函数。第一个声明对于非类类型的语法是无效的。
相比之下,第二个重载函数模板适用于所有模板参数的语法,并且(如果存在)将接受任何参数,得益于其可变长参数...。(顺便说一句:这让我想起了我最喜欢的一行 C 程序,它可以解决世界上任何问题,只要用户输入格式正确。)
虽然函数模板声明不能用作函数,但这些声明可用于简单的编译时查询,例如有关返回类型的查询。这种查询不需要实际的定义,只需要原型。
这正是类模板 IsClass 用来定义编译时常量 IsClass<SomeType>::value 的方法。
那么,IsClass<SomeType>::value是如何获得其值的,以及它如何在编译时执行?编译器必须要么理解sizeof (is_class_tester<T>(0)),要么放弃尝试。我们需要根据SomeType是否为类来看两种情况。
情况1: SomeType是一个类。
在这里,两个模板声明都是有效的语法,因此编译器有两个可行的候选项可供选择。编译器可以并且必须选择第一个函数模板,因为选择规则规定可变参数函数在选择中具有最低优先级。所选函数返回一个char。由于sizeof(char)保证为1,因此在SomeType是类的情况下,IsClass<SomeType>::value将为true。

情况2:SomeType不是一个类。
这就是SFINAE发挥作用的地方。第一个函数模板声明是无效的语法。编译器不能因为SFINAE而放弃在这里。它必须继续寻找替代方案,而第二个函数模板声明正好符合要求。唯一可行的函数返回一个TypeBiggerThanChar,定义被省略了,但希望很明显。我们只想知道这个东西的sizeof()。它比一个char大,所以如果SomeType不是一个类,IsClass<SomeType>::value将会是false。


他们回答了问题,但是原始问题并没有问及其余部分。如果你希望更容易在SO上找到这个问题的答案,你可以提一个新问题并自己提供其中一个答案。 - Georg Fritzsche
@Georg:他们回答了这个问题,但并没有考虑到它的使用背景。(例如,没有人注意到函数模板被声明但未定义。)关于您评论的更大背景:我在这里还是个新手(只有一个月),但我的看法是,在SO上复制的问题受到高度反感。因此,我将其作为复活的答案添加,而不是开始一个全新的问题。如果这是错误的,请告诉我。 - David Hammen
问题所在的上下文并不重要,他只是想知道成员函数指针语法。虽然你不应该提出重复的问题,但这个问题不会重复,因为它涉及到is_class_tester()函数的工作原理,而不是那个语法本身 :) - Georg Fritzsche

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