当前位置:  开发笔记 > 编程语言 > 正文

指向类数据成员"::*"的指针

如何解决《指向类数据成员"::*"的指针》经验,为你挑选了8个好方法。

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

class Car
{
    public:
    int speed;
};

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

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



1> 小智..:

它是"指向成员的指针" - 以下代码说明了它的用法:

#include 
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是必要的,因为->*运算符的优先级低于函数调用运算符.


你能举例说明一个有用的棘手情况吗?谢谢.
我最近在序列化框架中使用了指向数据成员的指针.静态marshaller对象使用包含指向可序列化数据成员的指针的包装列表进行初始化.[此代码的早期原型.](http://ideone.com/MIHQAM)
模板函数中有一个非常酷的指针指向**数据**成员用法的示例[在此代码中](http://www.keithschwarz.com/interesting/code/?dir=inplace-tree-删除)

2> John McFarla..:

这是我能想到的最简单的例子,它传达了这个特征相关的罕见情况:

#include 

class bowl {
public:
    int apples;
    int oranges;
};

int count_fruit(bowl * begin, bowl * end, int bowl::*fruit)
{
    int count = 0;
    for (bowl * iterator = begin; iterator != end; ++ iterator)
        count += iterator->*fruit;
    return count;
}

int main()
{
    bowl bowls[2] = {
        { 1, 2 },
        { 3, 5 }
    };
    std::cout << "I have " << count_fruit(bowls, bowls + 2, & bowl::apples) << " apples\n";
    std::cout << "I have " << count_fruit(bowls, bowls + 2, & bowl::oranges) << " oranges\n";
    return 0;
}

这里要注意的是传递给count_fruit的指针.这样可以节省编写单独的count_apples和count_oranges函数的麻烦.


`&bowl :: apples`和`&bowl :: oranges`不指向_object_的成员; 他们指向_class_的成员.它们需要在指向某个东西之前与指向实际对象的指针结合使用.使用` - >*`运算符实现了这种组合.
现在很清楚了; 我没有直视.谢谢.
不应该是`&bowls.apples`和`&bowls.oranges`?`&bowl :: apples`和`&bowl :: oranges`没有指向任何东西.

3> Johannes Sch..:

另一个应用程序是侵入性列表.元素类型可以告诉列表它的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
template
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 lst(&apple::next);

    apple a;
    lst.add(a);
}


@eee我建议你阅读有关参考参数的信息.我所做的基本上与你所做的相同.
@Alcott:您可以将它应用于其他下一个指针未命名为"next"的类似链表的结构.

4> Tom..:

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

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

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

现在假设你将它们填充到一个向量中:

std::vector samples;
... fill the vector ...

现在假设您想要计算一系列样本中某个变量的某个函数(比如平均值),并且您希望将此平均值计算因子计算为函数.指向成员的指针使其变得简单:

double Mean(std::vector::const_iterator begin, 
    std::vector::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
S mean(Titer begin, const Titer& end, S std::iterator_traits::value_type::* var) {
    using T = typename std::iterator_traits::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 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 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 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值时,接下来的三个值也将加载到缓存中(假设适当的对齐),这意味着您不需要为接下来的三次迭代加载任何值.

通过在例如SSE2架构上使用SIMD指令,可以进一步改进上述算法.但是,如果值在内存中都是连续的,那么这些工作好得多,您可以使用单个指令将四个样本一起加载(在以后的SSE版本中更多).

YMMV - 设计适合您算法的数据结构.



5> peterchen..:

您可以稍后在任何实例上访问此成员:

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

  int mySpeed = myCar.*pSpeed;
  int yourSpeed = yourCar.*pSpeed;

  assert(mySpeed > yourSpeed); // ;-)

  return 0;
}

请注意,您确实需要一个实例来调用它,因此它不像委托那样工作.
它很少使用,我需要它可能在我的所有年份一次或两次.

通常使用接口(即C++中的纯基类)是更好的设计选择.


@thecoshman:完全依赖 - 隐藏set/get方法背后的数据成员不是封装,只是挤奶女工尝试接口抽象.在许多情况下,对公众成员进行"非规范化"是一个合理的选择.但该讨论可能超出了评论功能的范围.
指出+1,如果我理解正确的话,这是指向任何实例的成员的指针,而不是指向一个实例的特定值的指针,这是我完全遗漏的部分.

6> AHelps..:

IBM有一些关于如何使用它的文档.简而言之,您将指针用作类的偏移量.除了它们引用的类之外​​,您不能使用这些指针,因此:

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

这看起来有点模糊,但是一个可能的应用是,如果您正在尝试编写用于将通用数据反序列化为许多不同对象类型的代码,并且您的代码需要处理它完全不知道的对象类型(例如,您的代码是在库中,您反序列化的对象是由库的用户创建的).成员指针为您提供了一种通用的,易读的方式来引用各个数据成员的偏移量,而不必像C结构那样采用无类型的void*技巧.


由于做了一些DCOM工作并使用托管资源类,这涉及在每次调用之前做一些工作,并使用数据成员进行内部表示发送到com,加上模板,我现在做了很多这样的事情.锅炉板代码*多*小

7> Alex B..:

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

#include 
#include 
#include 
#include 
#include 
#include 


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 l;
    l.push_back(Car(10));
    l.push_back(Car(140));
    l.push_back(Car(130));
    l.push_back(Car(60));

    // Speeding cars
    list 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;
}



8> Functastic..:

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

#include 
#include 

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;
}


@DwayneRobinson但使用“ union”以这种方式键入双关语是标准所不允许的,因为它会调用多种形式的未定义行为……而这个答案是可以的。
推荐阅读
路人甲
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有