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

class Car
{
    public:
    int speed;
};

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

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


当前回答

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

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

其他回答

我认为,只有当成员数据相当大(例如,另一个相当庞大的类的对象),并且您有一些外部例程,只对该类的对象引用起作用时,才会想要这样做。你不想复制成员对象,所以这让你可以传递它。

另一个应用是侵入式列表。元素类型可以告诉列表它的next/prev指针是什么。所以列表不使用硬编码的名称,但仍然可以使用现有的指针:

// say this is some existing structure. And we want to use
// a list. We can tell it that the next pointer
// is apple::next.
struct apple {
    int data;
    apple * next;
};

// simple example of a minimal intrusive list. Could specify the
// member pointer as template argument too, if we wanted:
// template<typename E, E *E::*next_ptr>
template<typename E>
struct List {
    List(E *E::*next_ptr):head(0), next_ptr(next_ptr) { }

    void add(E &e) {
        // access its next pointer by the member pointer
        e.*next_ptr = head;
        head = &e;
    }

    E * head;
    E *E::*next_ptr;
};

int main() {
    List<apple> lst(&apple::next);

    apple a;
    lst.add(a);
}

它是一个“指向成员的指针”——下面的代码说明了它的用法:

#include <iostream>
using namespace std;

class Car
{
    public:
    int speed;
};

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

    Car c1;
    c1.speed = 1;       // direct access
    cout << "speed is " << c1.speed << endl;
    c1.*pSpeed = 2;     // access via pointer to member
    cout << "speed is " << c1.speed << endl;
    return 0;
}

至于你为什么要这样做,它给了你另一种间接的层次,可以解决一些棘手的问题。但说实话,我从未在自己的代码中使用过它们。

编辑:我想不出一个令人信服的使用指针成员数据。指向成员函数的指针可以在可插拔的体系结构中使用,但是在这么小的空间里生成一个例子再次让我感到挫败。以下是我最好的(未经测试)尝试-一个Apply函数,在应用用户选择的成员函数到对象之前,会做一些前后处理:

void Apply( SomeClass * c, void (SomeClass::*func)() ) {
    // do hefty pre-call processing
    (c->*func)();  // call user specified function
    // do hefty post-call processing
}

c->*func周围的括号是必要的,因为->*操作符的优先级低于函数调用操作符。

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

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

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 -设计适合你的算法的数据结构。

假设你有一个结构。在那个结构里面 *某种名字 *两个相同类型但含义不同的变量

struct foo {
    std::string a;
    std::string b;
};

好的,现在假设你在一个容器里有一堆foo:

// key: some sort of name, value: a foo instance
std::map<std::string, foo> container;

好吧,现在假设您从不同的源加载数据,但是数据以相同的方式呈现(例如,您需要相同的解析方法)。

你可以这样做:

void readDataFromText(std::istream & input, std::map<std::string, foo> & container, std::string foo::*storage) {
    std::string line, name, value;

    // while lines are successfully retrieved
    while (std::getline(input, line)) {
        std::stringstream linestr(line);
        if ( line.empty() ) {
            continue;
        }

        // retrieve name and value
        linestr >> name >> value;

        // store value into correct storage, whichever one is correct
        container[name].*storage = value;
    }
}

std::map<std::string, foo> readValues() {
    std::map<std::string, foo> foos;

    std::ifstream a("input-a");
    readDataFromText(a, foos, &foo::a);
    std::ifstream b("input-b");
    readDataFromText(b, foos, &foo::b);
    return foos;
}

此时,调用readValues()将返回一个“input-a”和“input-b”一致的容器;所有的键都将出现,带有a或b或两者都有的foo。