IT story

클래스 데이터 멤버“:: *”의 포인터

hot-time 2020. 4. 24. 08:07
반응형

클래스 데이터 멤버“:: *”의 포인터


컴파일이 이상한 코드 스 니펫을 발견했습니다.

class Car
{
    public:
    int speed;
};

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

C ++이 클래스의 비 정적 데이터 멤버에 대한이 포인터를 가지고 있습니까? 실제 코드에서이 이상한 포인터를 사용하는 것은 무엇입니까 ?


"멤버를 가리키는 포인터"입니다. 다음 코드는 그 사용법을 보여줍니다.

#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으므로 괄호 가 필요합니다 ->*.


이것은 내가 생각할 수있는 가장 간단한 예입니다.이 기능이 관련된 드문 경우를 전달합니다.

#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 함수를 작성하지 않아도됩니다.


다른 응용 프로그램은 침입 목록입니다. 요소 유형은 다음 / 이전 포인터가 무엇인지 목록에 알려줄 수 있습니다. 따라서 목록은 하드 코딩 된 이름을 사용하지 않지만 기존 포인터를 계속 사용할 수 있습니다.

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

신호 처리 / 제어 시스템에서 현재 작업중인 실제 예는 다음과 같습니다.

수집하는 데이터를 나타내는 구조가 있다고 가정하십시오.

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 값이 메모리에서로드되면 다음 3 개도 캐시에로드됩니다 (적절한 정렬을 가정 함). 이는 다음 세 번의 반복을 위해로드 된 값이 필요하지 않음을 의미합니다.

위의 알고리즘은 예를 들어 SSE2 아키텍처에서 SIMD 명령어를 사용하여 약간 더 개선 될 수 있습니다. 그러나 값이 모두 메모리에서 연속적이며 단일 명령을 사용하여 4 개의 샘플을 함께로드 할 수있는 경우 (이후 SSE 버전에서 더 많이 ) 이러한 방법이 훨씬 더 효과적입니다.

YMMV-알고리즘에 맞게 데이터 구조를 설계하십시오.


나중에에,이 멤버를 액세스 할 수 있는 경우 :

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 ++의 순수한 기본 클래스)를 사용하는 것이 더 나은 디자인 선택입니다.


IBM 은이를 사용하는 방법에 대한 추가 문서를 가지고 있습니다. 간단히 말해서 포인터를 클래스의 오프셋으로 사용하고 있습니다. 참조하는 클래스와 별도로 이러한 포인터를 사용할 수 없습니다.

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

다소 모호한 것처럼 보이지만 하나의 가능한 응용 프로그램은 일반 데이터를 여러 다른 객체 유형으로 역 직렬화하기위한 코드를 작성하려고 시도하고 코드가 전혀 알지 못하는 객체 유형을 처리해야하는 경우입니다 (예 : 코드는 라이브러리에서 역 직렬화하는 개체는 라이브러리의 사용자가 만든 것입니다). 멤버 포인터는 C 구조체에 대해 할 수있는 방식이없는 void * 트릭에 의존하지 않고 개별 데이터 멤버 오프셋을 참조하는 일반적이고 반 가독성있는 방법을 제공합니다.


멤버 변수와 함수를 균일 한 방식으로 바인딩 할 수 있습니다. 다음은 Car 클래스의 예입니다. 더 일반적으로 사용되는 바인딩 될 std::pair::first::second지도에 STL 알고리즘과 부스트에 사용하는 경우.

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

(동종) 멤버 데이터에 대한 포인터 배열을 사용하여 이름이 지정된 이중 멤버 (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;
}

내가 사용한 한 가지 방법은 클래스에서 무언가를 수행하는 방법에 대한 두 가지 구현이 있고 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 문보다 우아하다고 생각합니다.


클래스에 대한 포인터 실제 포인터 가 아닙니다 . 클래스는 논리적 구조이며 메모리에 물리적으로 존재하지 않지만 클래스의 멤버에 대한 포인터를 생성하면 멤버를 찾을 수있는 멤버 클래스의 객체에 오프셋을 제공합니다. 이것은 중요한 결론을 제공합니다. 정적 멤버는 어떤 오브젝트와도 연관되어 있지 않으므로 멤버에 대한 포인터는 정적 멤버 (데이터 또는 함수)를 가리킬 수 없습니다 . 다음을 고려하십시오.

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 4th Edition


멤버 데이터가 꽤 큰 경우 (예 : 다른 꽤 무거운 클래스의 객체)이 클래스의 객체에 대한 참조에서만 작동하는 외부 루틴이있는 경우에만이 작업을 수행하려고한다고 생각합니다. 멤버 객체를 복사하고 싶지 않으므로 전달할 수 있습니다.


다음은 데이터 멤버에 대한 포인터가 유용한 예입니다.

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

구조가 있다고 가정하십시오. 그 구조 안에는 * 일종의 이름 * 같은 유형이지만 두 가지 변수가 다른 의미가 있습니다

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

자, 이제 foo컨테이너 s 가 있다고 가정 해 봅시다 .

// 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 또는 둘 다 있습니다.


@anon & @Oktalist의 대답에 대한 사용 사례를 추가하기 위해 포인터 대 멤버 함수 및 포인터 대 멤버 데이터에 대한 훌륭한 독서 자료가 있습니다. http://www.cs.wustl.edu/~schmidt/PDF/C++-ptmf4.pdf

참고 URL : https://stackoverflow.com/questions/670734/pointer-to-class-data-member

반응형