代码之家  ›  专栏  ›  技术社区  ›  Ashwin Nanjappa

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

  •  200
  • Ashwin Nanjappa  · 技术社区  · 17 年前

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

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

    C++是否具有指向类的非静态数据成员的指针? 在真实代码中使用这个奇怪的指针吗?

    14 回复  |  直到 7 年前
        1
  •  213
  •   Oktalist    11 年前

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

    #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 ->* 运算符的优先级低于函数调用运算符。

        2
  •  97
  •   John McFarlane    15 年前

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

    这里要注意的是传递给count_fruit的指针。这样就不用编写单独的count_apples和count_oranges函数。

        3
  •  66
  •   Johannes Schaub - litb    17 年前

    // 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);
    }
    
        4
  •  47
  •   Tom    10 年前

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

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

    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,用于更简洁的模板功能方法

    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值也将加载到缓存中(假设适当对齐),这意味着您不需要为接下来的三个迭代加载任何值。

    通过在eg SSE2体系结构上使用SIMD指令,可以进一步改进上述算法。然而,这些都是有效的 如果这些值在内存中都是连续的,并且您可以使用一条指令将四个样本加载到一起(在以后的SSE版本中会有更多)。

        5
  •  39
  •   peterchen    13 年前

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

    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++中的纯基类)是更好的设计选择。

        6
  •  26
  •   AHelps    13 年前

    IBM 有更多关于如何使用这个的文档。简单地说,您使用指针作为类的偏移量。除了它们引用的类之外,不能使用这些指针,因此:

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

        7
  •  20
  •   Alex B    17 年前

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

    #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;
    }
    
        8
  •  11
  •   Functastic    17 年前

    您可以使用指向(同构)成员数据的指针数组来启用双命名成员(即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;
    }
    
        9
  •  2
  •   Troubadour    17 年前

    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语句更优雅,即使在没有实际用途的情况下,但这只是我的观点。

        10
  •  2
  •   shycha    7 年前

    指向类的指针不可用 真实的 由于静态成员不与任何对象关联,因此指向成员的指针不能指向任何静态成员(数据或函数) 考虑以下事项:

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

    资料来源:完整的参考C++ Herbert Schildt第四版

        11
  •  0
  •   prestokeys    10 年前

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

    #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
    }
    
        12
  •  0
  •   inetknght    10 年前

    假设你有一个结构。该结构的内部是 *两个类型相同但含义不同的变量

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

        13
  •  0
  •   benb    6 年前

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

    那个构造函数有什么好处

    struct foo {
        int ival;
        float fval;
    };
    

    如果您已向foo提供了共享的\u ptr,则可以使用该构造函数检索其成员ival或fval的共享\u ptr:

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

    如果希望传递指针foo_shared->ival到某个需要共享\u ptr的函数

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

        14
  •  0
  •   Kai Petzke    5 年前

    指向成员的指针是C++的类型安全等价物 offsetof() ,在中定义 stddef.h class 或 struct . 虽然 也可以用C++中的某些简单的类来使用,它对于一般情况来说是非常失败的,尤其是对于虚拟基类。因此,向标准中添加了指向成员的指针。它们还提供了更简单的语法来引用实际字段:

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

    这比:

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

    至于为什么要使用 抵销额() How does the C offsetof macro work?

        15
  •  0
  •   Spyros Tsimboulis    5 年前

    使用指向成员的指针,我们可以编写这样的通用代码

    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;
    }
    
        16
  •  0
  •   mada    4 年前

    我爱这个世界 * & 操作员:

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

        17
  •  -1
  •   Andrew Jaffe    17 年前

    我认为只有当成员数据相当大(例如,另一个相当大的类的对象)并且您有一些只对该类的对象进行引用的外部例程时,您才会希望这样做。您不想复制成员对象,因此这允许您传递它。

        18
  •  -1
  •   Kuba hasn't forgotten Monica    6 年前

    只是为@anon's&添加一些用例@Oktalist的回答是,这里有一个关于成员函数指针和成员数据指针的阅读材料。

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

    推荐文章