287

正常にコンパイルされるこの奇妙なコードスニペットに出くわしました:

class Car
{
    public:
    int speed;
};

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

C++ がクラスの非静的データ メンバへのポインタを持っているのはなぜですか? 実際のコードでこの奇妙なポインターを使用するのは何ですか?

4

18 に答える 18

223

これは「メンバーへのポインター」です。次のコードはその使用法を示しています。

#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必要があります。->*

于 2009-03-22T09:13:24.017 に答える
103

これは、この機能が関連するまれなケースを伝える、私が考えることができる最も単純な例です。

#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 関数を別々に記述する必要がなくなります。

于 2011-04-29T16:05:59.073 に答える
66

別のアプリケーションは、侵入リストです。要素の型は、次/前のポインターが何であるかをリストに伝えることができます。したがって、リストはハードコーディングされた名前を使用しませんが、既存のポインターを引き続き使用できます。

// 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);
}
于 2009-03-23T00:19:15.787 に答える
47

これは、信号処理/制御システムから、私が現在取り組んでいる実際の例です。

収集しているデータを表す構造があるとします。

struct Sample {
    time_t time;
    double value1;
    double value2;
    double value3;
};

これらをベクトルに詰め込むとします。

std::vector<Sample> samples;
... fill the vector ...

ここで、ある範囲のサンプルで変数の 1 つの関数 (平均など) を計算し、この平均計算を関数に因数分解したいとします。メンバーへのポインターを使用すると、次のことが簡単になります。

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;

多くのアーキテクチャでは、 の 1 つのインスタンスがSampleキャッシュ ラインを埋めます。したがって、ループの反復ごとに、1 つのサンプルがメモリからキャッシュに取り込まれます。キャッシュ ラインから 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 つの値もキャッシュに読み込まれます (適切な配置が想定されます)。つまり、次の 3 回の反復で値を読み込む必要はありません。

上記のアルゴリズムは、SSE2 アーキテクチャなどで SIMD 命令を使用することにより、さらに改善することができます。ただし、これらの値がすべてメモリ内で連続していて、1 つの命令を使用して 4 つのサンプルを一緒にロードできる場合 (SSE の新しいバージョンではさらに多くの場合)、これらはより適切に機能します。

YMMV - アルゴリズムに合わせてデータ構造を設計します。

于 2010-11-02T13:17:26.407 に答える
39

後で任意のインスタンスでこのメンバーにアクセスできます。

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

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

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

  return 0;
}

呼び出すにはインスタンスが必要なので、デリゲートのようには機能しないことに注意してください。
めったに使用されません。私は、私のすべての年でおそらく1回か2回それを必要としました.

通常、インターフェイス (つまり、C++ の純粋な基本クラス) を使用することは、より適切な設計上の選択です。

于 2009-03-22T09:10:20.263 に答える
26

IBMには、これを使用する方法に関するドキュメントがいくつかあります。簡単に言うと、ポインターをクラスへのオフセットとして使用しています。これらのポインターは、それらが参照するクラスとは別に使用できないため、次のようになります。

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

少しあいまいに思えますが、汎用データを多くの異なるオブジェクト型に逆シリアル化するためのコードを記述しようとしていて、そのコードがまったく何も知らないオブジェクト型を処理する必要がある場合 (たとえば、コードがライブラリ内にあり、デシリアライズ先のオブジェクトはライブラリのユーザーによって作成されたものです)。メンバー ポインターを使用すると、C 構造体の場合のように型のない void * トリックに頼る必要なく、個々のデータ メンバー オフセットを参照するための一般的で半判読可能な方法が得られます。

于 2009-03-22T09:13:18.133 に答える
20

これにより、メンバー変数と関数を統一された方法でバインドできます。以下は Car クラスの例です。より一般的な使用法はバインディング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;
}
于 2009-03-22T13:02:42.123 に答える
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;
}
于 2009-03-23T04:24:54.230 に答える
2

私がそれを使用した1つの方法は、クラスで何かを行う方法の2つの実装があり、ifステートメントを継続的に実行することなく実行時に1つを選択したい場合です。

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 ステートメントよりもエレガントだと思いますが、それは私の意見です。

于 2009-03-22T11:57:30.563 に答える
0

構造があるとします。その構造体の中には * ある種の名前 * 同じ型で異なる意味を持つ 2 つの変数があります

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

fooさて、コンテナにたくさんの があるとしましょう:

// 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 のいずれか、または両方が含まれます。

于 2016-04-12T15:08:47.250 に答える
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
}
于 2015-11-18T18:48:27.413 に答える
0

メンバーへのポインターの実際の例は、std::shared_ptr のより狭いエイリアシング コンストラクターである可能性があります。

template <typename T>
template <typename U>
shared_ptr<T>::shared_ptr(const shared_ptr<U>, T U::*member);

そのコンストラクタは何に適していますか

構造体 foo があるとします。

struct foo {
    int ival;
    float fval;
};

foo に shared_ptr を指定した場合、そのコンストラクターを使用して、shared_ptr をそのメンバー ival または fval に取得できます。

auto foo_shared = std::make_shared<foo>();
auto ival_shared = std::shared_ptr<int>(foo_shared, &foo::ival);

これは、ポインター foo_shared->ival を、shared_ptr を期待する関数に渡したい場合に便利です。

https://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr

于 2020-09-20T15:56:05.627 に答える
0

メンバーへのポインターを使用すると、次のような汎用コードを記述できます

template<typename T, typename U>
struct alpha{
   T U::*p_some_member;
};

struct beta{
   int foo;
};

int main()
{

   beta b{};

   alpha<int, beta> a{&beta::foo};

   b.*(a.p_some_member) = 4;

   return 0;
}
于 2021-07-22T10:25:37.933 に答える