Pointer to class data member “::*”

后端 未结 15 1659
清歌不尽
清歌不尽 2020-11-21 11:47

I came across this strange code snippet which compiles fine:

class Car
{
    public:
    int speed;
};

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


        
相关标签:
15条回答
  • 2020-11-21 12:01

    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. 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

    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:

    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. 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. So on each iteration of the loop, one sample will be pulled from memory into the cache. 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.

    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.

    The above algorithm can be improved somewhat further through the use of SIMD instructions on eg SSE2 architectures. 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).

    YMMV - design your data structures to suit your algorithm.

    0 讨论(0)
  • 2020-11-21 12:01

    You can use an array of pointer to (homogeneous) member data to enable a dual, named-member (i.e. x.data) and array-subscript (i.e. x[idx]) interface.

    #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;
    }
    
    0 讨论(0)
  • 2020-11-21 12:05

    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
    }
    
    0 讨论(0)
  • 2020-11-21 12:06

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

    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

    0 讨论(0)
  • 2020-11-21 12:07

    A realworld example of a pointer-to-member could be a more narrow aliasing constructor for 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 {
        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:

    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

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

    0 讨论(0)
  • 2020-11-21 12:09

    I think you'd only want to do this if the member data was pretty large (e.g., an object of another pretty hefty class), and you have some external routine which only works on references to objects of that class. You don't want to copy the member object, so this lets you pass it around.

    0 讨论(0)
提交回复
热议问题