태그 보관물: c++-faq

c++-faq

클래스 데이터 멤버“:: *”의 포인터 int Car::*pSpeed = &Car::speed;

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

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