ตัวชี้ไปยังคลาสข้อมูลสมาชิก“ :: *”


243

ฉันเจอข้อมูลโค้ดแปลก ๆ ที่รวบรวมได้ดี:

class Car
{
    public:
    int speed;
};

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

เหตุใด C ++ จึงมีตัวชี้นี้ไปยังสมาชิกข้อมูลที่ไม่คงที่ของคลาส? สิ่งที่การใช้งานของตัวชี้นี้แปลกในรหัสที่แท้จริงคืออะไร?


นี่คือสิ่งที่ฉันพบมันสับสนฉันด้วย ... แต่เข้าท่าตอนนี้: stackoverflow.com/a/982941/211160
HostileFork พูดว่าอย่าเชื่อถือ SE

คำตอบ:


190

มันเป็น "ตัวชี้ไปยังสมาชิก" - รหัสต่อไปนี้แสดงให้เห็นถึงการใช้งาน:

#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ที่จำเป็นต้องมีเนื่องจากตัว->*ดำเนินการมีลำดับความสำคัญต่ำกว่าตัวดำเนินการเรียกฟังก์ชัน


3
คุณช่วยแสดงตัวอย่างของสถานการณ์ที่ยุ่งยากซึ่งสิ่งนี้มีประโยชน์ได้ไหม ขอบคุณ
Ashwin Nanjappa

ผมมีตัวอย่างของการใช้ตัวชี้ไปยังสมาชิกในชั้นเรียนลักษณะในคำตอบ SO อีก
Mike DeSimone

ตัวอย่างคือการเขียนคลาส "callback" -type สำหรับบางระบบตามเหตุการณ์ ตัวอย่างเช่นระบบการสมัครสมาชิกของเหตุการณ์ CEGUI ของ UI ใช้การโทรกลับ templated ที่เก็บตัวชี้ไปยังฟังก์ชันสมาชิกที่คุณเลือกเพื่อให้คุณสามารถระบุวิธีการจัดการเหตุการณ์
Benji XVI

2
มีตัวอย่างที่น่าสนใจเกี่ยวกับการใช้ตัวชี้ไปยังข้อมูลในฟังก์ชั่นเทมเพลตในรหัสนี้
alveko

3
ฉันเพิ่งใช้พอยน์เตอร์กับสมาชิกข้อมูลในกรอบงานการทำให้เป็นอนุกรม วัตถุ marshaller แบบคงที่ถูกเตรียมใช้งานด้วยรายการของ wrappers ที่ประกอบด้วยตัวชี้ไปยังสมาชิกข้อมูลที่สามารถ ต้นแบบต้นของรหัสนี้
Alexey Biryukov

79

นี่คือตัวอย่างที่ง่ายที่สุดที่ฉันสามารถนึกได้ว่าสื่อถึงกรณีที่หายากซึ่งคุณลักษณะนี้เกี่ยวข้องกัน:

#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
ไม่ควรจะเป็น&bowls.applesและ&bowls.oranges? &bowl::applesและ&bowl::orangesไม่ได้ชี้ไปที่อะไรเลย
Dan Nissenbaum

19
&bowl::applesและ&bowl::orangesไม่ได้ชี้ไปสมาชิกของวัตถุ ; พวกเขาชี้ไปที่สมาชิกของชั้นเรียน พวกเขาจะต้องรวมกับตัวชี้ไปยังวัตถุจริงก่อนที่จะชี้ไปที่บางสิ่งบางอย่าง การรวมกันนั้นประสบความสำเร็จกับ->*ผู้ควบคุมเครื่อง
John McFarlane

58

แอปพลิเคชั่นอื่นคือรายการที่รบกวน ประเภทองค์ประกอบสามารถบอกรายการสิ่งที่ตัวชี้ถัดไป / ก่อนหน้าของมัน ดังนั้นรายการไม่ใช้ชื่อตายตัว แต่ยังสามารถใช้ตัวชี้ที่มีอยู่:

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

หากนี่คือรายการที่เชื่อมโยงอย่างแท้จริงคุณจะไม่ต้องการสิ่งนี้: void add (E * e) {e -> * next_ptr = head; หัว = e; } ??
eeeeaaii

4
@ee ฉันขอแนะนำให้คุณอ่านเกี่ยวกับพารามิเตอร์อ้างอิง สิ่งที่ฉันทำนั้นโดยพื้นฐานแล้วเท่ากับสิ่งที่คุณทำ
Johannes Schaub - litb

+1 สำหรับตัวอย่างรหัสของคุณ แต่ฉันไม่เห็นความจำเป็นใด ๆ สำหรับการใช้ตัวชี้ไปยังสมาชิกตัวอย่างอื่น ๆ ?
Alcott

3
@Alcott: คุณสามารถนำไปใช้กับคนอื่น ๆ nextที่เชื่อมโยงรายการเหมือนโครงสร้างที่ชี้ต่อไปจะไม่ได้ตั้งชื่อ
icktoofay

41

นี่คือตัวอย่างในโลกแห่งความจริงที่ฉันกำลังทำงานอยู่ตอนนี้จากระบบประมวลผลสัญญาณ / ควบคุม:

สมมติว่าคุณมีโครงสร้างบางอย่างที่แสดงถึงข้อมูลที่คุณกำลังรวบรวม:

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 สำหรับแนวทางฟังก์ชั่นเทมเพลตที่กระชับยิ่งขึ้น

และแน่นอนคุณสามารถเทมเพลตเพื่อคำนวณค่าเฉลี่ยสำหรับตัวส่งต่อตัวใด ๆ และประเภทค่าใด ๆ ที่สนับสนุนการเพิ่มด้วยตัวเองและการหารด้วย 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);

แก้ไข - โค้ดด้านบนมีความเกี่ยวข้องกับประสิทธิภาพ

คุณควรทราบตามที่ฉันค้นพบในไม่ช้าว่ารหัสข้างต้นมีความเกี่ยวข้องกับประสิทธิภาพบางอย่าง สรุปคือถ้าคุณกำลังคำนวณสถิติสรุปในอนุกรมเวลาหรือคำนวณ 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 แรกจากหน่วยความจำแล้วสามค่าถัดไปจะถูกโหลดลงในแคชด้วย (หากมีการจัดตำแหน่งที่เหมาะสม) หมายความว่าคุณไม่จำเป็นต้องโหลดค่าใด ๆ สำหรับการทำซ้ำสามครั้งถัดไป

อัลกอริทึมข้างต้นสามารถปรับปรุงให้ดียิ่งขึ้นผ่านการใช้คำสั่ง SIMD ในสถาปัตยกรรม SSE2 อย่างไรก็ตามสิ่งเหล่านี้ทำงานได้มากดีกว่าหากค่าทั้งหมดอยู่ติดกันในหน่วยความจำและคุณสามารถใช้คำสั่งเดียวเพื่อโหลดตัวอย่างสี่ตัวอย่างเข้าด้วยกัน (เพิ่มเติมในรุ่น SSE รุ่นใหม่กว่า)

YMMV - ออกแบบโครงสร้างข้อมูลของคุณเพื่อให้เหมาะกับอัลกอริทึมของคุณ


มันยอดเยี่ยมมาก ฉันกำลังจะใช้บางสิ่งที่คล้ายกันมากและตอนนี้ฉันไม่ต้องเข้าใจไวยากรณ์แปลก ๆ ! ขอบคุณ!
Nicu Stiurca

นี่คือคำตอบที่ดีที่สุด double Sample::*ส่วนหนึ่งเป็นกุญแจสำคัญ!
Eyal

37

คุณสามารถเข้าถึงสมาชิกนี้ได้ในภายหลังในทุกกรณี:

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 ++) เป็นตัวเลือกการออกแบบที่ดีกว่า


แต่แน่นอนว่านี่เป็นเพียงการปฏิบัติที่ไม่ดี? ควรทำอะไรเช่น youcar.setspeed (mycar.getpspeed)
thecoshman

9
@thecoshman: ทั้งหมดขึ้นอยู่กับ - การซ่อนข้อมูลสมาชิกไว้หลังเมธอด set / get ไม่ใช่การห่อหุ้ม ในหลาย ๆ สถานการณ์ "denormalization" สำหรับสมาชิกสาธารณะเป็นตัวเลือกที่สมเหตุสมผล แต่การสนทนานั้นอาจเกินขอบเขตของฟังก์ชั่นความคิดเห็น
peterchen

4
+1 สำหรับการชี้ให้เห็นถ้าฉันเข้าใจถูกต้องว่านี่เป็นตัวชี้ไปยังสมาชิกของอินสแตนซ์ใด ๆ และไม่ใช่ตัวชี้ไปยังค่าเฉพาะของอินสแตนซ์หนึ่งซึ่งเป็นส่วนที่ฉันหายไปโดยสิ้นเชิง
johnbakers

@Fellowshee คุณเข้าใจถูกต้อง :) (เน้นที่คำตอบ)
peterchen

26

IBMมีเอกสารเพิ่มเติมเกี่ยวกับวิธีใช้สิ่งนี้ สั้น ๆ คุณกำลังใช้ตัวชี้เป็นออฟเซ็ตในคลาส คุณไม่สามารถใช้พอยน์เตอร์เหล่านี้นอกเหนือจากคลาสที่อ้างถึงดังนั้น:

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

ดูเหมือนว่าจะคลุมเครือเล็กน้อย แต่มีแอปพลิเคชั่นหนึ่งที่เป็นไปได้คือถ้าคุณพยายามเขียนรหัสสำหรับการแยกแยะข้อมูลทั่วไปออกเป็นประเภทออบเจกต์ต่าง ๆ และรหัสของคุณต้องจัดการกับประเภทออบเจกต์ที่ไม่รู้อะไรเลย ในห้องสมุดและวัตถุที่คุณไม่ต้องทำการซีเรียลสร้างขึ้นโดยผู้ใช้ห้องสมุดของคุณ) พอยน์เตอร์ของสมาชิกจะให้วิธีการทั่วไปในการอ้างถึงข้อมูลออฟเซ็ตของสมาชิกแต่ละรายโดยไม่ต้องมีขาประจำตัวโดยไม่ต้องใช้โมฆะแบบไม่มีกลอุบายวิธีที่คุณใช้กับ C struct


คุณสามารถแบ่งปันตัวอย่างข้อมูลโค้ดที่โครงสร้างนี้มีประโยชน์หรือไม่ ขอบคุณ
Ashwin Nanjappa

2
ฉันกำลังทำสิ่งนี้มากเนื่องจากการทำงาน DCOM บางส่วนและใช้คลาสทรัพยากรที่มีการจัดการซึ่งเกี่ยวข้องกับการทำงานเล็กน้อยก่อนการโทรแต่ละครั้งและการใช้ข้อมูลสมาชิกสำหรับการเป็นตัวแทนภายในเพื่อส่งไปยังคอมบวกด้วยการสร้างเทมเพลต รหัสจานหม้อไอน้ำมากขนาดเล็ก
แดน

19

มันทำให้เป็นไปได้ที่จะผูกตัวแปรสมาชิกและฟังก์ชั่นในลักษณะที่เหมือนกัน ต่อไปนี้เป็นตัวอย่างสำหรับคลาสรถยนต์ของคุณ การใช้งานทั่วไปจะมีผลผูกพัน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;
}

11

คุณสามารถใช้อาร์เรย์ของตัวชี้ไปยังข้อมูลสมาชิก (เป็นเนื้อเดียวกัน) เพื่อเปิดใช้งานอินเทอร์เฟซแบบ dual, named-member (iexdata) และ array-subscript (เช่น 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;
}

ฉันมักจะเห็นสิ่งนี้ดำเนินการโดยใช้สหภาพที่ไม่ระบุชื่อรวมทั้งอาร์เรย์เขตข้อมูล v [3] ตั้งแต่ที่หลีกเลี่ยงการอ้อม แต่ยังฉลาดและอาจเป็นประโยชน์สำหรับเขตข้อมูลที่ไม่ต่อเนื่อง
Dwayne Robinson

2
@DwayneRobinson แต่การใช้ a union-type-Pun ในแบบนั้นไม่ได้รับอนุญาตตามมาตรฐานเนื่องจากจะเรียกใช้พฤติกรรมที่ไม่ได้กำหนดหลายรูปแบบ ... ในขณะที่คำตอบนี้ใช้ได้
underscore_d

นั่นเป็นตัวอย่างที่เรียบร้อย แต่โอเปอเรเตอร์ [] สามารถเขียนใหม่ได้โดยไม่ใช้ตัวชี้ไปยังองค์ประกอบ: float *component[] = { &x, &y, &z }; return *component[idx];นั่นคือตัวชี้ไปยังส่วนประกอบดูเหมือนว่าจะไม่มีจุดประสงค์ยกเว้นการทำให้งงงวย
tobi_s

2

วิธีหนึ่งที่ฉันใช้คือถ้าฉันมีการใช้งานสองอย่างของวิธีการทำบางสิ่งบางอย่างในชั้นเรียนและฉันต้องการเลือกหนึ่งตัวที่รันไทม์โดยไม่ต้องผ่านคำสั่ง 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 แม้ในสถานการณ์ที่ไม่มีการใช้งานจริง แต่นั่นเป็นเพียง opnion ของฉัน


โดยทั่วไปคุณจะประสบความสำเร็จเช่นเดียวกันกับที่เป็นนามธรรมAlgorithmและสองชั้นเรียนมาเช่นและAlgorithmA AlgorithmBในกรณีเช่นนี้อัลกอริธึมทั้งสองจะถูกแยกออกจากกันอย่างดีและทำให้มั่นใจได้ว่าจะทดสอบอย่างอิสระ
shycha

2

ตัวชี้ไปยังเรียนไม่ได้จริงชี้; คลาสเป็นโครงสร้างเชิงตรรกะและไม่มีการมีอยู่จริงในหน่วยความจำอย่างไรก็ตามเมื่อคุณสร้างตัวชี้ไปยังสมาชิกของคลาสนั้นจะให้ออฟเซ็ตเป็นวัตถุของคลาสของสมาชิกที่สมาชิกสามารถพบได้ สิ่งนี้ให้ข้อสรุปที่สำคัญ: เนื่องจากสมาชิกแบบสแตติกไม่ได้เชื่อมโยงกับวัตถุใด ๆ ดังนั้นตัวชี้ไปยังสมาชิกไม่สามารถชี้ไปที่สมาชิกแบบสแตติก (ข้อมูลหรือฟังก์ชัน) ใด ๆ พิจารณาต่อไปนี้:

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 รุ่นที่ 4


0

ฉันคิดว่าคุณต้องการทำสิ่งนี้หากข้อมูลสมาชิกมีขนาดใหญ่มาก (เช่นวัตถุของคลาสที่สวยมาก) และคุณมีรูทีนภายนอกบางอย่างที่ใช้งานได้กับการอ้างอิงถึงวัตถุของคลาสนั้นเท่านั้น คุณไม่ต้องการคัดลอกวัตถุสมาชิกดังนั้นจึงช่วยให้คุณสามารถส่งผ่านวัตถุได้


0

นี่คือตัวอย่างที่ตัวชี้ไปยังสมาชิกข้อมูลอาจเป็นประโยชน์:

#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

สมมติว่าคุณมีโครงสร้าง ภายในโครงสร้างนั้นคือ * ชื่อบางประเภท * ตัวแปรสองตัวที่เป็นประเภทเดียวกัน แต่มีความหมายแตกต่างกัน

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

ตกลงตอนนี้สมมติว่าคุณมีหลายfoos ในภาชนะ:

// 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"; ปุ่มทั้งหมดจะถูกนำเสนอและ foos ที่มี a หรือ b หรือทั้งสองอย่าง


0

เพียงเพิ่มกรณีการใช้งานสำหรับคำตอบของ @ anon & @ Oktalist นี่คือเนื้อหาการอ่านที่ยอดเยี่ยมเกี่ยวกับตัวชี้ไปยังสมาชิกฟังก์ชันและตัวชี้ไปยังข้อมูลสมาชิก

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


ลิงก์นี้ตายไปแล้ว นั่นเป็นเหตุผลที่ไม่คาดหวังคำตอบสำหรับลิงค์เท่านั้น อย่างน้อยก็สรุปเนื้อหาของลิงค์มิฉะนั้นคำตอบของคุณจะไม่ถูกต้องเมื่อลิงก์เสีย
phuclv
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.