C++ 中的指针成员 ->* 和 .* 运算符是啥?

Posted

技术标签:

【中文标题】C++ 中的指针成员 ->* 和 .* 运算符是啥?【英文标题】:What are the Pointer-to-Member ->* and .* Operators in C++?C++ 中的指针成员 ->* 和 .* 运算符是什么? 【发布时间】:2011-09-28 23:54:13 【问题描述】:

是的,我见过 this question 和 this FAQ,但我仍然不明白 ->*.* 在 C++ 中的含义。 这些页面提供了关于运算符的信息(例如重载),但似乎没有很好地解释它们是什么

什么是 C++ 中的 ->*.*,与 ->. 相比,您什么时候需要使用它们?

【问题讨论】:

【参考方案1】:

指向成员的访问运算符:.*->*

指向成员的访问运算符.*->* 用于解除对指向成员的指针对象 的组合>分别指向对象的指针。此描述适用于指向数据成员的指针指向成员函数的指针

例如,考虑类Foo

struct Foo 
   int i;
   void f();
;

如果您将成员指针iPtr 声明为指向Fooint 数据成员:

int Foo::* iPtr;

你可以初始化这个成员指针iPtr,让它指向Foo::i成员:

iPtr = &Foo::i;

要取消引用此指针,您需要将它与Foo 对象结合使用。

现在考虑对象foo 和指向对象fooPtr 的指针:

Foo foo;
Foo* fooPtr = &foo;

然后,您可以取消引用 iPtrfoofooPtr 的组合:

foo.*iPtr = 0;
fooPtr->*iPtr = 0;

类似地,您可以将.*->*指向函数成员的指针一起使用。但是请注意,您需要将它们括在括号之间,因为 函数调用运算符,即(),比.*->* 具有更高的优先级:

void (Foo::*memFuncPtr)() = &Foo::f;

(foo.*memFuncPtr)();
(fooPtr->*memFuncPtr)();

总结:您需要一个对象来取消引用指向成员的指针,而您使用哪个对象,.*->* 来取消引用指向成员的指针,取决于该所需对象是直接提供还是通过一个对象指针。

C++17 — 改用std::invoke()

从 C++17 开始,std::invoke 函数模板可以替换这两个运算符的使用。 std::invoke 提供了一种统一的方式来取消引用成员指针,无论您是否将它们与 objectobject pointer 结合使用,也无论 指向成员的指针对应于一个指向数据成员的指针指向成员函数的指针

// dereference a pointer to a data member
std::invoke(iPtr, foo) = 0;      // with an object
std::invoke(iPtr, fooPtr) = 0;   // with an object pointer

// dereference a pointer to a member function
std::invoke(memFuncPtr, foo);      // with an object
std::invoke(memFuncPtr, fooPtr);   // with an object pointer

这种统一的语法对应于普通的函数调用语法,可能更容易编写泛型代码。

【讨论】:

【参考方案2】:

编辑:顺便说一句,virtual member functions pointers 变得很奇怪。

对于成员变量:

struct Foo 
   int a;
   int b;
;


int main ()

    Foo foo;
    int (Foo :: * ptr);

    ptr = & Foo :: a;
    foo .*ptr = 123; // foo.a = 123;

    ptr = & Foo :: b;
    foo .*ptr = 234; // foo.b = 234;

成员功能几乎相同。

struct Foo 
   int a ();
   int b ();
;


int main ()

    Foo foo;
    int (Foo :: * ptr) ();

    ptr = & Foo :: a;
    (foo .*ptr) (); // foo.a ();

    ptr = & Foo :: b;
    (foo .*ptr) (); // foo.b ();

【讨论】:

+1 表示语法适用于所有成员,而不仅仅是成员函数。我发现指向成员变量的指针很少使用,尽管它们有许多相当有趣的潜在应用。【参考方案3】:

我希望这个例子能让你明白

//we have a class
struct X

   void f() 
   void g() 
;

typedef void (X::*pointer)();
//ok, let's take a pointer and assign f to it.
pointer somePointer = &X::f;
//now I want to call somePointer. But for that, I need an object
X x;
//now I call the member function on x like this
(x.*somePointer)(); //will call x.f()
//now, suppose x is not an object but a pointer to object
X* px = new X;
//I want to call the memfun pointer on px. I use ->*
(px ->* somePointer)(); //will call px->f();

现在,您不能使用x.somePointer()px->somePointer(),因为在X 类中没有这样的成员。为此使用了特殊的成员函数指针调用语法...您自己尝试几个示例,您会习惯的

【讨论】:

添加 必需的 额外大括号以补偿运算符优先级。 一个指向成员函数的指针可能比一个普通的指向函数的指针大:专门处理继承iirc 把它想象成一个“相对”指针:相对于一个对象——然后你必须提供它才能真正到达最终目的地。 这真是很好的解释,它们是什么。但我真的不知道为什么以及何时我们需要这样的东西。【参考方案4】:

当你有一个普通指针(指向一个对象或基本类型)时,你会使用* 来取消引用它:

int a;
int* b = a;
*b = 5;     // we use *b to dereference b, to access the thing it points to

从概念上讲,我们用成员函数指针做同样的事情:

class SomeClass

   public:  void func() 
;

// typedefs make function pointers much easier.
// this is a pointer to a member function of SomeClass, which takes no parameters and returns void
typedef void (SomeClass::*memfunc)();

memfunc myPointer = &SomeClass::func;

SomeClass foo;

// to call func(), we could do:
foo.func();

// to call func() using our pointer, we need to dereference the pointer:
foo.*myPointer();
// this is conceptually just:    foo  .  *myPointer  ();


// likewise with a pointer to the object itself:
SomeClass* p = new SomeClass;

// normal call func()
p->func();

// calling func() by dereferencing our pointer:
p->*myPointer();
// this is conceptually just:    p  ->  *myPointer  ();

我希望这有助于解释这个概念。我们有效地取消了指向成员函数的指针。它比这更复杂一些——它不是一个指向内存中函数的绝对指针,而只是一个偏移量,它应用于上面的foop。但从概念上讲,我们取消引用它,就像我们取消引用普通对象指针一样。

【讨论】:

尝试编译会出现must use '.*' or '->*' to call pointer-to-member function... 错误。这是由于函数调用() 优先于成员指针运算符。我认为这可以通过分别添加括号(foo.*myPointer)();(p->*myPointer)(); 来解决。【参考方案5】:

你不能像普通指针那样取消引用指向成员的指针——因为成员函数需要this指针,你必须以某种方式传递它。因此,您需要使用这两个运算符,一侧是对象,另一侧是指针,例如(object.*ptr)().

不过,请考虑使用 functionbindstd::boost::,取决于您编写 C++03 还是 0x)而不是它们。

【讨论】:

我认为这可能是最好的解释。【参考方案6】:

C++ 中所谓的“指针”在内部更像是偏移量。您需要这样的成员“指针”和对象来引用对象中的成员。但成员“指针”与指针语法一起使用,因此得名。

您可以通过两种方式获得手头的对象:您拥有对该对象的引用,或者您拥有一个指向该对象的指针。

对于引用,使用.*将其与成员指针组合,对于指针,使用->*将其与成员指针组合。

但是,如果可以避免的话,通常不要使用成员指针。

它们遵循相当违反直觉的规则,并且可以绕过protected 访问而无需任何显式强制转换,也就是说,不经意间......

干杯,

【讨论】:

+1 无需代码就能很好地解释它。 :) 问题:为什么我们不能像普通函数一样获取函数的地址?指向成员函数的指针与指向其他函数的指针不同吗? (例如,它更大吗?) @Mehrdad:如果您可以有一个指向仅限于非虚拟成员函数的成员函数的指针,那么它确实可能只是地址。但是,是否虚拟不是成员函数指针类型的一部分。因此,它的表示需要包括一些关于当前值是否指代虚函数的信息,如果是虚函数,则对于基于虚表的实现信息,该信息确定与指针类型关联的类的虚表中的偏移量。 【参考方案7】:

简而言之:如果您知道要访问的成员,请使用 ->.。如果您知道要访问哪个成员,请使用->*.*

简单的侵入式列表示例

template<typename ItemType>
struct List 
  List(ItemType *head, ItemType * ItemType::*nextMemPointer)
  :m_head(head), m_nextMemPointer(nextMemPointer)  

  void addHead(ItemType *item) 
    (item ->* m_nextMemPointer) = m_head;
    m_head = item;
  

private:
  ItemType *m_head;

  // this stores the member pointer denoting the 
  // "next" pointer of an item
  ItemType * ItemType::*m_nextMemPointer;
;

【讨论】:

+1 表示第一句话,虽然我一生中从未知道我想访问哪个成员,哈哈。 :)

以上是关于C++ 中的指针成员 ->* 和 .* 运算符是啥?的主要内容,如果未能解决你的问题,请参考以下文章

C++拷贝控制含有指针成员的类

C++对象成员的使用

C++ 类和对象

C++ 类和对象

C++大纲及疑惑点三

C++大纲及疑惑点三