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

class Car
{
    public:
    int speed;
};

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

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


当前回答

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

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

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

其他回答

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

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

我使用它的一种方式是,如果我有两个如何在一个类中做某事的实现,我想在运行时选择一个,而不必连续地通过if语句,即。

class Algorithm
{
public:
    Algorithm() : m_impFn( &Algorithm::implementationA ) {}
    void frequentlyCalled()
    {
        // Avoid if ( using A ) else if ( using B ) type of thing
        (this->*m_impFn)();
    }
private:
    void implementationA() { /*...*/ }
    void implementationB() { /*...*/ }

    typedef void ( Algorithm::*IMP_FN ) ();
    IMP_FN m_impFn;
};

显然,这只有在你觉得代码被敲打到足够的if语句减慢事情完成时才有用。在某个密集算法的深处。我仍然认为它比if语句更优雅,即使在它没有实际用途的情况下,但这只是我的观点。

您可以使用指向(同构)成员数据的指针数组来启用双重命名成员(即x.data)和数组下标(即x[idx])接口。

#include <cassert>
#include <cstddef>

struct vector3 {
    float x;
    float y;
    float z;

    float& operator[](std::size_t idx) {
        static float vector3::*component[3] = {
            &vector3::x, &vector3::y, &vector3::z
        };
        return this->*component[idx];
    }
};

int main()
{
    vector3 v = { 0.0f, 1.0f, 2.0f };

    assert(&v[0] == &v.x);
    assert(&v[1] == &v.y);
    assert(&v[2] == &v.z);

    for (std::size_t i = 0; i < 3; ++i) {
        v[i] += 1.0f;
    }

    assert(v.x == 1.0f);
    assert(v.y == 2.0f);
    assert(v.z == 3.0f);

    return 0;
}

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

#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周围的括号是必要的,因为->*操作符的优先级低于函数调用操作符。

下面是一个例子,其中指向数据成员的指针可能很有用:

#include <iostream>
#include <list>
#include <string>

template <typename Container, typename T, typename DataPtr>
typename Container::value_type searchByDataMember (const Container& container, const T& t, DataPtr ptr) {
    for (const typename Container::value_type& x : container) {
        if (x->*ptr == t)
            return x;
    }
    return typename Container::value_type{};
}

struct Object {
    int ID, value;
    std::string name;
    Object (int i, int v, const std::string& n) : ID(i), value(v), name(n) {}
};

std::list<Object*> objects { new Object(5,6,"Sam"), new Object(11,7,"Mark"), new Object(9,12,"Rob"),
    new Object(2,11,"Tom"), new Object(15,16,"John") };

int main() {
    const Object* object = searchByDataMember (objects, 11, &Object::value);
    std::cout << object->name << '\n';  // Tom
}