简体   繁体   English

指向 class 数据成员“::*”的指针

[英]Pointer to class data member "::*"

I came across this strange code snippet which compiles fine:我遇到了这个编译得很好的奇怪代码片段:

class Car
{
    public:
    int speed;
};

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

Why does C++ have this pointer to a non-static data member of a class?为什么C++ 有这个指向 class 的非静态数据成员的指针? What is the use of this strange pointer in real code?这个奇怪的指针在实际代码中有什么用?

It's a "pointer to member" - the following code illustrates its use:它是一个“指向成员的指针”——下面的代码说明了它的用法:

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

As to why you would want to do that, well it gives you another level of indirection that can solve some tricky problems.至于您为什么要这样做,它为您提供了另一个层次的间接性,可以解决一些棘手的问题。 But to be honest, I've never had to use them in my own code.但老实说,我从来没有在自己的代码中使用过它们。

Edit: I can't think off-hand of a convincing use for pointers to member data.编辑:我想不出一个令人信服的使用指向成员数据的指针。 Pointer to member functions can be used in pluggable architectures, but once again producing an example in a small space defeats me.指向成员函数的指针可用于可插拔架构中,但再次在狭小的空间中生成示例让我失望。 The following is my best (untested) try - an Apply function that would do some pre &post processing before applying a user-selected member function to an object:以下是我最好的(未经测试)尝试-应用 function 在将用户选择的成员 function 应用于 ZA8CFDE6331BD59EB2AC96F8911C4B6666 之前会进行一些前后处理:

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

The parentheses around c->*func are necessary because the ->* operator has lower precedence than the function call operator. c->*func周围的括号是必要的,因为->*运算符的优先级低于 function 调用运算符。

This is the simplest example I can think of that conveys the rare cases where this feature is pertinent:这是我能想到的最简单的示例,它传达了与此功能相关的罕见情况:

#include <iostream>

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

The thing to note here is the pointer passed in to count_fruit.这里要注意的是传入count_fruit的指针。 This saves you having to write separate count_apples and count_oranges functions.这使您不必编写单独的 count_apples 和 count_oranges 函数。

Another application are intrusive lists.另一个应用是侵入式列表。 The element type can tell the list what its next/prev pointers are.元素类型可以告诉列表它的下一个/上一个指针是什么。 So the list does not use hard-coded names but can still use existing pointers:所以列表不使用硬编码的名称,但仍然可以使用现有的指针:

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

Here's a real-world example I am working on right now, from signal processing / control systems:这是我现在正在处理的一个真实示例,来自信号处理/控制系统:

Suppose you have some structure that represents the data you are collecting:假设您有一些表示您正在收集的数据的结构:

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

Now suppose that you stuff them into a vector:现在假设您将它们填充到向量中:

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

Now suppose that you want to calculate some function (say the mean) of one of the variables over a range of samples, and you want to factor this mean calculation into a function.现在假设您想要计算一个变量在一系列样本中的一些 function(比如平均值),并且您希望将此平均值计算纳入 function。 The pointer-to-member makes it easy:指向成员的指针使它变得容易:

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

Note Edited 2016/08/05 for a more concise template-function approach注意 2016/08/05 编辑,以获得更简洁的模板函数方法

And, of course, you can template it to compute a mean for any forward-iterator and any value type that supports addition with itself and division by size_t:而且,当然,您可以对其进行模板化,以计算任何前向迭代器和任何支持与自身相加和除以 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);

EDIT - The above code has performance implications编辑 - 上面的代码具有性能影响

You should note, as I soon discovered, that the code above has some serious performance implications.你应该注意到,正如我很快发现的那样,上面的代码有一些严重的性能影响。 The summary is that if you're calculating a summary statistic on a time series, or calculating an FFT etc, then you should store the values for each variable contiguously in memory.总结是,如果您正在计算时间序列的汇总统计量,或计算 FFT 等,那么您应该将每个变量的值连续存储在 memory 中。 Otherwise, iterating over the series will cause a cache miss for every value retrieved.否则,遍历该系列将导致每个检索到的值的缓存未命中。

Consider the performance of this code:考虑这段代码的性能:

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;

On many architectures, one instance of Sample will fill a cache line.在许多架构上,一个Sample实例将填充一个缓存行。 So on each iteration of the loop, one sample will be pulled from memory into the cache.因此,在循环的每次迭代中,将从 memory 中提取一个样本到缓存中。 4 bytes from the cache line will be used and the rest thrown away, and the next iteration will result in another cache miss, memory access and so on.将使用缓存行中的 4 个字节,并丢弃 rest,下一次迭代将导致另一个缓存未命中,memory 访问等等。

Much better to do this:这样做要好得多:

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;

Now when the first x value is loaded from memory, the next three will also be loaded into the cache (supposing suitable alignment), meaning you don't need any values loaded for the next three iterations.现在,当从 memory 加载第一个 x 值时,接下来的三个也将加载到缓存中(假设合适的对齐方式),这意味着您不需要为接下来的三个迭代加载任何值。

The above algorithm can be improved somewhat further through the use of SIMD instructions on eg SSE2 architectures.通过在例如 SSE2 架构上使用 SIMD 指令,可以进一步改进上述算法。 However, these work much better if the values are all contiguous in memory and you can use a single instruction to load four samples together (more in later SSE versions).但是,如果这些值在 memory 中都是连续的,并且您可以使用一条指令将四个样本一起加载(在以后的 SSE 版本中更多),则这些工作更好。

YMMV - design your data structures to suit your algorithm. YMMV - 设计您的数据结构以适合您的算法。

You can later access this member, on any instance:您可以稍后在任何实例上访问此成员:

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

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

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

  return 0;
}

Note that you do need an instance to call it on, so it does not work like a delegate.请注意,您确实需要一个实例来调用它,因此它不像委托那样工作。
It is used rarely, I've needed it maybe once or twice in all my years.它很少使用,我多年来可能需要它一两次。

Normally using an interface (ie a pure base class in C++) is the better design choice.通常使用接口(即 C++ 中的纯基础 class)是更好的设计选择。

IBM has some more documentation on how to use this. IBM有更多关于如何使用它的文档。 Briefly, you're using the pointer as an offset into the class.简而言之,您将指针用作 class 的偏移量。 You can't use these pointers apart from the class they refer to, so:除了它们所指的 class 之外,您不能使用这些指针,因此:

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

It makes it possible to bind member variables and functions in the uniform manner.它使得以统一的方式绑定成员变量和函数成为可能。 The following is example with your Car class.以下是您的汽车 class 的示例。 More common usage would be binding std::pair::first and ::second when using in STL algorithms and Boost on a map.更常见的用法是在 STL 算法和 map 上使用时绑定std::pair::first::second

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

You can use an array of pointer to (homogeneous) member data to enable a dual, named-member (iexdata) and array-subscript (ie x[idx]) interface.您可以使用指向(同质)成员数据的指针数组来启用双重命名成员(iexdata)和数组下标(即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;
}

Pointers to classes are not real pointers;指向类的指针不是真正的指针; a class is a logical construct and has no physical existence in memory, however, when you construct a pointer to a member of a class it gives an offset into an object of the member's class where the member can be found; a class is a logical construct and has no physical existence in memory, however, when you construct a pointer to a member of a class it gives an offset into an object of the member's class where the member can be found; This gives an important conclusion: Since static members are not associated with any object so a pointer to a member CANNOT point to a static member(data or functions) whatsoever Consider the following:这给出了一个重要的结论:由于 static 成员不与任何 object 相关联,因此指向成员的指针不能指向 static 成员(数据或函数)无论如何考虑以下

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

Source: The Complete Reference C++ - Herbert Schildt 4th Edition资料来源:完整参考 C++ - Herbert Schildt 第 4 版

One way I've used it is if I have two implementations of how to do something in a class and I want to choose one at run-time without having to continually go through an if statement ie我使用它的一种方法是,如果我有两种如何在 class 中执行某些操作的实现,并且我想在运行时选择一个,而不必通过 if 语句不断地 go 即

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

Obviously this is only practically useful if you feel the code is being hammered enough that the if statement is slowing things done eg.显然,如果您觉得代码被敲打到足以使 if 语句减慢某些事情的速度,例如,这实际上是有用的。 deep in the guts of some intensive algorithm somewhere.在某处某个密集算法的内部深处。 I still think it's more elegant than the if statement even in situations where it has no practical use but that's just my opnion.即使在没有实际用途的情况下,我仍然认为它比 if 语句更优雅,但这只是我的意见。

Here is an example where pointer to data members could be useful:这是一个指向数据成员的指针可能有用的示例:

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

Suppose you have a structure.假设你有一个结构。 Inside of that structure are * some sort of name * two variables of the same type but with different meaning该结构内部是 * 某种名称 * 两个相同类型但含义不同的变量

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

Okay, now let's say you have a bunch of foo s in a container:好的,现在假设您在容器中有一堆foo

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

Okay, now suppose you load the data from separate sources, but the data is presented in the same fashion (eg, you need the same parsing method).好的,现在假设您从不同的源加载数据,但数据以相同的方式呈现(例如,您需要相同的解析方法)。

You could do something like this:你可以这样做:

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

At this point, calling readValues() will return a container with a unison of "input-a" and "input-b";此时,调用readValues()将返回一个“input-a”和“input-b”一致的容器; all keys will be present, and foos with have either a or b or both.所有键都将存在,并且 foos 具有 a 或 b 或两者兼有。

Here's one example.这是一个例子。 I found some unit test codes, where a huge data structure class was instantiated multiple times, in different locations.我发现了一些单元测试代码,其中在不同的位置多次实例化了一个巨大的数据结构 class。 It took up several hundred lines all together.它总共占用了数百行。 Every data member of that class was const, and they could only be set in the constructor.该 class 的每个数据成员都是 const 的,它们只能在构造函数中设置。 Every instantiation changed a couple of parameters at a time, but most of them had the default values.每个实例化一次更改几个参数,但其中大多数具有默认值。 So I had to find a way to allow the data members to be set in the unit tests, as elegantly as possible, without changing the original class.所以我必须想办法让数据成员在单元测试中尽可能优雅地设置,而不改变原来的 class。 My solution was, you guessed it, data member pointers.我的解决方案是,你猜对了,数据成员指针。

struct HugeDataStructure {
     const int    i = 0;
     const double d = 0.0;
};

struct MutableHugeDataStructure : public HugeDataStructure {
    template<class T>
    auto& ForceSet (const T HugeDataStructure::* target, const T& value) {
        const_cast<T&> (this->*target) = value;
        return *this;
    }
};

#define SET(memberName, value) ForceSet(&HugeDataStructure::memberName, value)

And it could be used like this:它可以像这样使用:

MutableHugeDataStructure data;
data.SET(i, 5) .SET(d, 3.5);

A realworld example of a pointer-to-member could be a more narrow aliasing constructor for std::shared_ptr:指向成员的真实世界示例可能是 std::shared_ptr 的更窄的别名构造函数:

template <typename T>
template <typename U>
shared_ptr<T>::shared_ptr(const shared_ptr<U>, T U::*member);

What that constructor would be good for那个构造函数有什么好处

assume you have a struct foo:假设你有一个 struct foo:

struct foo {
    int ival;
    float fval;
};

If you have given a shared_ptr to a foo, you could then retrieve shared_ptr's to its members ival or fval using that constructor:如果您已将 shared_ptr 赋予 foo,则可以使用该构造函数将 shared_ptr 检索到其成员 ival 或 fval:

auto foo_shared = std::make_shared<foo>();
auto ival_shared = std::shared_ptr<int>(foo_shared, &foo::ival);

This would be useful if want to pass the pointer foo_shared->ival to some function which expects a shared_ptr如果想将指针 foo_shared->ival 传递给一些需要 shared_ptr 的 function 这将很有用

https://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr https://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr

Pointer to members are C++'s type safe equivalent for C's offsetof() , which is defined in stddef.h : Both return the information, where a certain field is located within a class or struct .指向成员的指针是 C 的offsetof()的 C++ 类型安全等效项,它在stddef.h中定义:两者都返回信息,其中某个字段位于classstruct中。 While offsetof() may be used with certain simple enough classes also in C++, it fails miserably for the general case, especially with virtual base classes.虽然offsetof()也可以在 C++ 中与某些足够简单的类一起使用,但它在一般情况下会失败,尤其是对于虚拟基类。 So pointer to members were added to the standard.所以指向成员的指针被添加到标准中。 They also provide easier syntax to reference an actual field:它们还提供了更简单的语法来引用实际字段:

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

is much easier than:比:

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

As to why one wants to use offsetof() (or pointer to members), there are good answers elsewhere on stackoverflow.至于为什么要使用offsetof() (或指向成员的指针),stackoverflow 的其他地方有很好的答案。 One example is here: How does the C offsetof macro work?一个例子在这里: C offsetof 宏是如何工作的?

with pointer to member, we can write generic code like this使用指向成员的指针,我们可以编写这样的通用代码

template<typename T, typename U>
struct alpha{
   T U::*p_some_member;
};

struct beta{
   int foo;
};

int main()
{

   beta b{};

   alpha<int, beta> a{&beta::foo};

   b.*(a.p_some_member) = 4;

   return 0;
}

I love the * and & operators:我喜欢*&运算符:

struct X 
{ 
    int a {0}; 
    int *ptr {NULL};

    int &fa() { return a; }
    int *&fptr() { return ptr; }
};

int main(void) 
{
    X x;
    int X::*p1 = &X::a;     // pointer-to-member 'int X::a'. Type of p1 = 'int X::*'
    x.*p1 = 10;

    int *X::*p2 = &X::ptr;  // pointer-to-member-pointer 'int *X::ptr'. Type of p2 = 'int *X::*' 
    x.*p2 = nullptr;
    X *xx;
    xx->*p2 = nullptr;

    int& (X::*p3)() = X::fa; // pointer-to-member-function 'X::fa'. Type of p3 = 'int &(X::*)()'
    (x.*p3)() = 20; 
    (xx->*p3)() = 30;

    int *&(X::*p4)() = X::fptr;  // pointer-to-member-function 'X::fptr'. Type of p4 = 'int *&(X::*)()'
    (x.*p4)() = nullptr; 
    (xx->*p4)() = nullptr;
}

Indeed all is true as long as the members are public, or static确实,只要成员是公开的,或者 static

Just to add some use cases for @anon's & @Oktalist's answer, here's a great reading material about pointer-to-member-function and pointer-to-member-data.只是为@anon 和@Oktalist 的答案添加一些用例,这里有一个关于指针到成员函数和指针到成员数据的很好的阅读材料。

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

I think you'd only want to do this if the member data was pretty large (eg, an object of another pretty hefty class), and you have some external routine which only works on references to objects of that class.我认为您只想在成员数据非常大的情况下执行此操作(例如,另一个相当大的类的 object),并且您有一些外部例程仅适用于对该 class 对象的引用。 You don't want to copy the member object, so this lets you pass it around.您不想复制成员 object,所以这可以让您传递它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM