c++ - सी++: कक्षा डेटा सदस्य के सूचक "::*"




class pointers (9)

@ Anon's और @ Oktalist के उत्तर के लिए कुछ उपयोग मामलों को जोड़ने के लिए, यहां पॉइंटर-टू-सदस्य-फ़ंक्शन और पॉइंटर-टू-सदस्य-डेटा के बारे में एक अच्छी पठन सामग्री है। http://www.cs.wustl.edu/~schmidt/PDF/C++-ptmf4.pdf

मैं इस अजीब कोड स्निपेट में आया जो ठीक से संकलित करता है:

class Car
{
    public:
    int speed;
};

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

C ++ में इस पॉइंटर को कक्षा के गैर-स्थैतिक डेटा सदस्य के पास क्यों है? वास्तविक कोड में इस अजीब सूचक का उपयोग क्या है?


आप दोहरी, नामांकित सदस्य (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;
}

एक तरह से मैंने इसका इस्तेमाल किया है यदि मेरे पास कक्षा में कुछ कैसे करना है, इसके दो कार्यान्वयन हैं और मैं लगातार एक कथन के माध्यम से बिना किसी रन-टाइम पर एक चुनना चाहता हूं

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

जाहिर है, यह केवल व्यावहारिक रूप से उपयोगी है अगर आपको लगता है कि कोड को पर्याप्त रूप से हथियार दिया जा रहा है कि यदि कथन कथन की चीजें धीमा कर रहा है उदाहरण के लिए। कहीं गहन एल्गोरिदम के गले में गहराई से। मुझे अभी भी लगता है कि यह कथन की तुलना में अधिक सुरुचिपूर्ण है, यहां तक ​​कि परिस्थितियों में जहां इसका कोई व्यावहारिक उपयोग नहीं है लेकिन यह सिर्फ मेरा विकल्प है।


बाद में आप किसी भी उदाहरण पर इस सदस्य तक पहुंच सकते हैं:

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

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

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

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

आप ऐसा क्यों करना चाहते हैं, ठीक है यह आपको एक और स्तर का संकेत देता है जो कुछ मुश्किल समस्याओं को हल कर सकता है। लेकिन ईमानदार होने के लिए, मुझे कभी भी अपने कोड में उनका उपयोग नहीं करना पड़ा।

संपादित करें: मैं पॉइंटर्स के सदस्य डेटा के लिए एक विश्वसनीय उपयोग के बारे में सोच नहीं सकता। सदस्य कार्यों के सूचक को प्लग करने योग्य आर्किटेक्चर में इस्तेमाल किया जा सकता है, लेकिन एक बार फिर एक छोटी सी जगह में एक उदाहरण का उत्पादन मुझे पराजित करता है। निम्नलिखित मेरा सबसे अच्छा (अवांछित) प्रयास है - एक लागू फ़ंक्शन जो किसी ऑब्जेक्ट में उपयोगकर्ता द्वारा चयनित सदस्य फ़ंक्शन को लागू करने से पहले कुछ पूर्व और पोस्ट प्रोसेसिंग करेगा:

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 आसपास कोष्ठक आवश्यक हैं क्योंकि ->* ऑपरेटर फ़ंक्शन कॉल ऑपरेटर की तुलना में कम प्राथमिकता है।


यह समान रूप से सदस्य चर और कार्यों को बांधना संभव बनाता है। निम्नलिखित आपकी कार कक्षा के साथ उदाहरण है। एसटीएल एल्गोरिदम में उपयोग करते समय और नक्शे पर बूस्ट करते समय अधिक सामान्य उपयोग बाध्यकारी 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;
}

यहां एक असली दुनिया का उदाहरण है, मैं अभी सिग्नल प्रोसेसिंग / कंट्रोल सिस्टम से काम कर रहा हूं:

मान लें कि आपके पास कुछ संरचना है जो आपके द्वारा एकत्र किए जा रहे डेटा का प्रतिनिधित्व करती है:

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

संपादित करें - उपर्युक्त कोड में प्रदर्शन प्रभाव हैं

आपको नोट करना चाहिए, जैसा कि मैंने जल्द ही खोजा था, ऊपर दिए गए कोड में कुछ गंभीर प्रदर्शन प्रभाव हैं। सारांश यह है कि यदि आप एक समय श्रृंखला पर सारांश आंकड़े की गणना कर रहे हैं, या एक एफएफटी आदि की गणना कर रहे हैं, तो आपको स्मृति में प्रत्येक चर के लिए मानों को संग्रहित करना चाहिए। अन्यथा, सीरीज़ पर पुनरावृत्ति करने से हर मूल्य को पुनर्प्राप्त करने के लिए कैश मिस का कारण बन जाएगा।

इस कोड के प्रदर्शन पर विचार करें:

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;

अब जब पहला एक्स मान स्मृति से लोड होता है, तो अगले तीन को कैश में भी लोड किया जाएगा (उपयुक्त संरेखण का अनुमान लगाया जा रहा है), जिसका अर्थ है कि आपको अगले तीन पुनरावृत्तियों के लिए लोड किए गए किसी भी मूल्य की आवश्यकता नहीं है।

एसएसई 2 आर्किटेक्चर जैसे सिम निर्देशों के उपयोग के माध्यम से उपरोक्त एल्गोरिदम को कुछ और सुधार किया जा सकता है। हालांकि, ये मान बेहतर होते हैं कि मान स्मृति में सभी संगत हैं और आप चार नमूनों को एक साथ लोड करने के लिए एक ही निर्देश का उपयोग कर सकते हैं (बाद में एसएसई संस्करणों में अधिक)।

वाईएमएमवी - अपने एल्गोरिदम के अनुकूल होने के लिए अपने डेटा संरचनाओं को डिज़ाइन करें।


IBM पास इसका उपयोग करने के तरीके पर कुछ और दस्तावेज हैं। संक्षेप में, आप कक्षा में ऑफसेट के रूप में पॉइंटर का उपयोग कर रहे हैं। आप इन पॉइंटर्स का उपयोग उस कक्षा से अलग नहीं कर सकते जो वे संदर्भित करते हैं, इसलिए:

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

यह थोड़ा अस्पष्ट लगता है, लेकिन एक संभावित अनुप्रयोग यह है कि यदि आप जेनेरिक डेटा को कई अलग-अलग ऑब्जेक्ट प्रकारों में deserializing के लिए कोड लिखने की कोशिश कर रहे हैं, और आपके कोड को ऑब्जेक्ट प्रकारों को संभालने की आवश्यकता है जो इसे बिल्कुल कुछ नहीं जानता (उदाहरण के लिए, आपका कोड है एक पुस्तकालय में, और जिन वस्तुओं में आप deserialize वे पुस्तकालय के उपयोगकर्ता द्वारा बनाया गया था)। सदस्य पॉइंटर्स आपको व्यक्तिगत डेटा सदस्य ऑफ़सेट का जिक्र करने का एक सामान्य, अर्ध-सुगम तरीका प्रदान करते हैं, बिना टाइपलेस शून्य * का उपयोग किए बिना सी स्ट्रक्चर के लिए आप जिस तरह से प्रयास कर सकते हैं।







pointers