首页 文章

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

提问于
浏览
195

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

class Car
{
    public:
    int speed;
};

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

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

14 回答

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

    至于为什么你想这样做,它给你另一层次的间接,可以解决一些棘手的问题 . 但说实话,我从来没有在我自己的代码中使用它们 .

    Edit: 我无法想象一下对成员数据指针的说服力 . 成员函数的指针可以在可插入的体系结构中使用,但是再次在一个小空间中生成一个例子会让我失望 . 以下是我最好的(未经测试)尝试 - 在将用户选择的成员函数应用于对象之前执行一些预处理和后处理的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 周围的括号是必要的,因为 ->* 运算符的优先级低于函数调用运算符 .

  • 0

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

    #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函数的麻烦 .

  • 156

    另一个应用程序是侵入式列表 . 元素类型可以告诉列表它的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<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);
    }
    
  • 18

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

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

  • 0

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

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

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

    Note Edited 2016/08/05 for a more concise template-function approach

    当然,您可以对其进行模板化以计算任何前向迭代器的均值以及支持自身加法和除以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

    您应该注意,正如我很快发现的那样,上面的代码会对性能产生严重影响 . 总结是,如果您正在计算时间序列的汇总统计量,或计算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值时,接下来的三个值也将被加载到缓存中(假设适当的对齐),这意味着您不需要为接下来的三次迭代加载任何值 .

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

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

  • 29

    IBM有一些关于如何使用它的文档 . 简而言之,除了它们引用的类之外,你还可以使用这些指针,因此:

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

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

  • 0

    它使得以统一的方式绑定成员变量和函数成为可能 . 以下是您的Car类的示例 . 更常见的用法是在STL算法和 Map 上使用Boost时绑定 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;
    }
    
  • 33

    您可以使用指向(同类)成员数据的指针数组来启用双重命名成员(即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;
    }
    
  • 52

    我使用它的一种方法是,如果我有两个如何在类中执行某些操作的实现,并且我想在运行时选择一个而不必不断地通过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语句更优雅,但这只是我的意见 .

  • 8

    我认为如果成员数据非常大(例如,另一个非常大的类的对象),你只想要这样做,并且你有一些外部例程只适用于对该类对象的引用 . 您不希望复制成员对象,因此这可以让您传递它 .

  • 62

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

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

    假设你有一个结构 . 在该结构内部是某种名称两个相同类型但具有不同含义的变量

    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() 将返回一个同时具有"input-a"和"input-b"的容器;将存在所有键,并且具有a或b或两者的foos .

  • 24

    只是为@anon _815234的答案添加一些用例,这里有一个关于指向成员函数的指针和指向成员数据的指针的很好的阅读材料 . http://www.cs.wustl.edu/~schmidt/PDF/C++-ptmf4.pdf

  • 0

    指向类的指针不是真正的指针;类是一个逻辑构造,并且在内存中没有物理存在,但是,当你构造一个指向类成员的指针时,它会给成员类的对象提供一个偏移量,在该对象中可以找到成员;这给出了一个重要的结论:由于静态成员不与任何对象相关联,因此指向成员的指针无法指向静态成员(数据或函数),请考虑以下事项:

    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

相关问题