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


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

에 관해서는 당신이하고 싶은 것, 잘 당신에게 몇 가지 까다로운 문제를 해결할 수있는 간접 다른 수준을 제공합니다. 그러나 솔직히 말해서, 나는 내 코드에서 그것들을 사용할 필요가 없었습니다.

편집 : 회원 데이터에 대한 포인터를 설득력있게 사용한다고 생각할 수 없습니다. 멤버 함수에 대한 포인터는 플러그 가능한 아키텍처에서 사용할 수 있지만 작은 공간에서 예제를 다시 생성하면 패배합니다. 다음은 최선의 (시험되지 않은) 시도입니다-사용자가 선택한 멤버 함수를 객체에 적용하기 전에 사전 및 사후 처리를 수행하는 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으므로 괄호 가 필요합니다 ->*.


3
이것이 유용한 까다로운 상황의 예를 보여 주시겠습니까? 감사.
Ashwin Nanjappa

다른 SO answer 의 Traits 클래스에서 포인터 대 멤버를 사용하는 예가 있습니다.
Mike DeSimone

예를 들어 일부 이벤트 기반 시스템에 대한 "콜백"유형 클래스를 작성하는 것입니다. 예를 들어 CEGUI의 UI 이벤트 구독 시스템은 선택한 멤버 함수에 대한 포인터를 저장하는 템플릿 콜백을 사용하여 이벤트 처리 방법을 지정할 수 있습니다.
Benji XVI

2
이 코드 의 템플릿 함수 에서 포인터 대 데이터 멤버 사용법에 대한 멋진 예가 있습니다.
alveko

3
최근에는 직렬화 프레임 워크에서 데이터 멤버에 대한 포인터를 사용했습니다. 정적 마샬 러 객체는 직렬화 가능한 데이터 멤버에 대한 포인터를 포함하는 래퍼 목록으로 초기화되었습니다. 이 코드의 초기 프로토 타입입니다.
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
@eee 참조 매개 변수에 대해 읽어 보는 것이 좋습니다. 내가 한 것은 기본적으로 당신이 한 것과 같습니다.
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 값이 메모리에서로드되면 다음 3 개도 캐시에로드됩니다 (적절한 정렬을 가정 함). 이는 다음 세 번의 반복에로드 된 값이 필요하지 않음을 의미합니다.

위의 알고리즘은 예를 들어 SSE2 아키텍처에서 SIMD 명령을 사용하여 약간 더 개선 될 수 있습니다. 그러나 값이 모두 메모리에서 연속적이며 단일 명령을 사용하여 4 개의 샘플을 함께로드 할 수있는 경우 (이 이후의 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 메소드 뒤에 데이터 멤버를 숨기는 것은 캡슐화가 아니며 인터페이스 추상화를위한 착유 자 시도 일 뿐이다. 많은 시나리오에서 공개 구성원에 대한 "비정규 화"가 합리적인 선택입니다. 그러나 그 논의는 아마도 주석 기능의 한계를 넘어 설 것입니다.
peterchen

4
내가 올바르게 이해하면 이것이 인스턴스의 멤버에 대한 포인터이며 한 인스턴스의 특정 값에 대한 포인터가 아니라는 것을 지적하기 위해 +1을 지적합니다.
johnbakers

@Fellowshee 당신은 올바르게 이해합니다 :) (답변에서 강조했습니다).
peterchen

26

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

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

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


이 구성이 유용한 코드 스 니펫 예제를 공유 할 수 있습니까? 감사.
Ashwin Nanjappa

2
나는 현재 일부 DCOM 작업을 수행하고 각 호출 전에 약간의 작업을 수행하고 내부 표현을 위해 데이터 멤버를 사용하여 com으로 전송하고 템플릿을 사용하여 많은 DCOM 작업을 수행하기 때문에이 작업을 많이하고 있습니다. 보일러 플레이트 코드가 훨씬 작음
Dan

19

멤버 변수와 함수를 균일 한 방식으로 바인딩 할 수 있습니다. 다음은 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;
}

11

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

나는 배열 필드 v [3]을 포함하는 익명 공용체를 사용하여 구현되는 것을 더 자주 보았습니다. 왜냐하면 간접적 인 것을 피하지만 영리하고 비 연속적 인 필드에 유용 할 수 있기 때문입니다.
드웨인 로빈슨

2
@DwayneRobinson을 사용하지만 union그 방식으로 타입 펀칭을 사용하는 것은 표준에 의해 허용되지 않습니다.
underscore_d

깔끔한 예이지만 operator-]는 포인터 대 컴포넌트없이 다시 작성할 수 있습니다. 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 문보다 우아하다고 생각합니다.


기본적으로, 당신은 추상과 같은 얻을 수 있습니다 Algorithm예를 들어, 두 개의 파생 클래스, AlgorithmAAlgorithmB. 이 경우 두 알고리즘이 잘 분리되어 독립적으로 테스트됩니다.
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 4th Edition


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

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


0

@anon & @Oktalist의 대답에 대한 사용 사례를 추가하기 위해 포인터 대 멤버 함수 및 포인터 대 멤버 데이터에 대한 훌륭한 독서 자료가 있습니다.

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


연결이 끊어졌습니다. 이것이 바로 링크 전용 답변이 예상되지 않는 이유입니다. 최소한 링크의 내용을 요약하십시오. 그렇지 않으면 링크가 썩을 때 답이 유효하지 않습니다
phuclv
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.