我遇到了这个奇怪的代码片段,它编译得很好:

class Car
{
    public:
    int speed;
};

int main()
{
    int Car::*pSpeed = &Car::speed;
    return 0;
}

为什么c++有这个指针指向类的非静态数据成员?在实际代码中,这个奇怪的指针有什么用呢?


当前回答

指向类的指针不是真正的指针;类是一个逻辑构造,在内存中没有物理存在,然而,当你构造一个指向类成员的指针时,它会给出一个指向该成员所在类的对象的偏移量;这给出了一个重要的结论:由于静态成员不与任何对象相关联,因此指向成员的指针不能指向静态成员(数据或函数) 考虑以下几点:

class x {
public:
    int val;
    x(int i) { val = i;}

    int get_val() { return val; }
    int d_val(int i) {return i+i; }
};

int main() {
    int (x::* data) = &x::val;               //pointer to data member
    int (x::* func)(int) = &x::d_val;        //pointer to function member

    x ob1(1), ob2(2);

    cout <<ob1.*data;
    cout <<ob2.*data;

    cout <<(ob1.*func)(ob1.*data);
    cout <<(ob2.*func)(ob2.*data);


    return 0;
}

来源:完整参考c++ - Herbert Schildt第四版

其他回答

它使得以统一的方式绑定成员变量和函数成为可能。下面是Car类的示例。更常见的用法是绑定std::pair::first和::second,当在STL算法和Boost上使用时。

#include <list>
#include <algorithm>
#include <iostream>
#include <iterator>
#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>


class Car {
public:
    Car(int s): speed(s) {}
    void drive() {
        std::cout << "Driving at " << speed << " km/h" << std::endl;
    }
    int speed;
};

int main() {

    using namespace std;
    using namespace boost::lambda;

    list<Car> l;
    l.push_back(Car(10));
    l.push_back(Car(140));
    l.push_back(Car(130));
    l.push_back(Car(60));

    // Speeding cars
    list<Car> s;

    // Binding a value to a member variable.
    // Find all cars with speed over 60 km/h.
    remove_copy_if(l.begin(), l.end(),
                   back_inserter(s),
                   bind(&Car::speed, _1) <= 60);

    // Binding a value to a member function.
    // Call a function on each car.
    for_each(s.begin(), s.end(), bind(&Car::drive, _1));

    return 0;
}

指向成员的指针是c++的类型安全等价于C的offsetof(),它在stddef.h中定义:两者都返回某个字段位于类或结构中的信息。虽然在c++中也可以将offset()用于某些足够简单的类,但在一般情况下,它会失败,尤其是虚拟基类。因此指针成员被添加到标准中。它们还提供了更简单的语法来引用实际字段:

struct C { int a; int b; } c;
int C::* intptr = &C::a;       // or &C::b, depending on the field wanted
c.*intptr += 1;

要比:

struct C { int a; int b; } c;
int intoffset = offsetof(struct C, a);
* (int *) (((char *) (void *) &c) + intoffset) += 1;

至于为什么要使用offsetof()(或指向成员的指针),在stackoverflow的其他地方有很好的答案。这里有一个例子:宏的C偏移是如何工作的?

为了给@anon和@Oktalist的回答添加一些用例,这里有一份关于指向成员函数的指针和指向成员数据的阅读材料。

https://www.dre.vanderbilt.edu/~schmidt/PDF/C++-ptmf4.pdf

下面是我现在正在研究的一个现实世界的例子,来自信号处理/控制系统:

假设你有一些表示你正在收集的数据的结构:

struct Sample {
    time_t time;
    double value1;
    double value2;
    double value3;
};

现在假设你把它们放到一个向量中:

std::vector<Sample> samples;
... fill the vector ...

现在假设你想计算一个变量在一定范围内的某个函数(比如均值),你想把这个均值计算分解成一个函数。指向成员的指针使它变得简单:

double Mean(std::vector<Sample>::const_iterator begin, 
    std::vector<Sample>::const_iterator end,
    double Sample::* var)
{
    float mean = 0;
    int samples = 0;
    for(; begin != end; begin++) {
        const Sample& s = *begin;
        mean += s.*var;
        samples++;
    }
    mean /= samples;
    return mean;
}

...
double mean = Mean(samples.begin(), samples.end(), &Sample::value2);

注释编辑2016/08/05以获得更简洁的模板函数方法

当然,你可以用模板来计算任何前向迭代器和任何值类型的均值,这些值类型支持与自身相加和除以size_t:

template<typename Titer, typename S>
S mean(Titer begin, const Titer& end, S std::iterator_traits<Titer>::value_type::* var) {
    using T = typename std::iterator_traits<Titer>::value_type;
    S sum = 0;
    size_t samples = 0;
    for( ; begin != end ; ++begin ) {
        const T& s = *begin;
        sum += s.*var;
        samples++;
    }
    return sum / samples;
}

struct Sample {
    double x;
}

std::vector<Sample> samples { {1.0}, {2.0}, {3.0} };
double m = mean(samples.begin(), samples.end(), &Sample::x);

编辑-上面的代码具有性能影响

您应该注意,正如我很快发现的那样,上面的代码有一些严重的性能影响。总的来说,如果你在计算一个时间序列的摘要统计量,或者计算FFT等,那么你应该在内存中连续存储每个变量的值。否则,遍历该系列将导致检索到的每个值缓存失败。

考虑这段代码的性能:

struct Sample {
  float w, x, y, z;
};

std::vector<Sample> series = ...;

float sum = 0;
int samples = 0;
for(auto it = series.begin(); it != series.end(); it++) {
  sum += *it.x;
  samples++;
}
float mean = sum / samples;

在许多体系结构上,一个Sample实例将填满一条缓存线。因此,在循环的每次迭代中,将从内存中取出一个样本到缓存中。缓存线中的4个字节将被使用,其余的将被丢弃,下一次迭代将导致另一个缓存丢失、内存访问等等。

这样做会更好:

struct Samples {
  std::vector<float> w, x, y, z;
};

Samples series = ...;

float sum = 0;
float samples = 0;
for(auto it = series.x.begin(); it != series.x.end(); it++) {
  sum += *it;
  samples++;
}
float mean = sum / samples;

现在,当第一个x值从内存中加载时,接下来的三个x值也将加载到缓存中(假设适当的对齐),这意味着您不需要为接下来的三个迭代加载任何值。

通过在SSE2体系结构上使用SIMD指令,可以进一步改进上述算法。但是,如果这些值在内存中都是连续的,并且您可以使用一条指令一起加载四个样本(后续的SSE版本中会有更多),那么这些方法就会工作得更好。

YMMV -设计适合你的算法的数据结构。

IBM有更多关于如何使用它的文档。简单地说,您使用指针作为类的偏移量。你不能在它们所指向的类之外使用这些指针,所以:

  int Car::*pSpeed = &Car::speed;
  Car mycar;
  mycar.*pSpeed = 65;

It seems a little obscure, but one possible application is if you're trying to write code for deserializing generic data into many different object types, and your code needs to handle object types that it knows absolutely nothing about (for example, your code is in a library, and the objects into which you deserialize were created by a user of your library). The member pointers give you a generic, semi-legible way of referring to the individual data member offsets, without having to resort to typeless void * tricks the way you might for C structs.