私はうまくコンパイルするこの奇妙なコードスニペットに出くわしました:
class Car
{
public:
int speed;
};
int main()
{
int Car::*pSpeed = &Car::speed;
return 0;
}
なぜ C ++には、クラスの非静的データメンバーへのこのポインターがあるのですか?実際のコードでこの奇妙なポインターの使用は何ですか?
私はうまくコンパイルするこの奇妙なコードスニペットに出くわしました:
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;
}
なぜあなたがそれをしたいと思う、まあそれはあなたにいくつかのトリッキーな問題を解決することができる間接の別のレベルを提供します。しかし、正直なところ、自分のコードでこれらを使用する必要はありませんでした。
編集:メンバーデータへのポインターの説得力のある使用から離れて考えることはできません。メンバー関数へのポインターは、プラガブルアーキテクチャで使用できますが、もう一度小さなスペースで例を生成すると、私は失敗します。以下は、私の最善の(テストされていない)試行です-ユーザーが選択したメンバー関数をオブジェクトに適用する前に、事前処理を行う適用関数です。
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関数を別々に記述する必要がなくなります。
&bowls.apples
あり&bowls.oranges
ませんか? &bowl::apples
そして&bowl::oranges
何を指していません。
&bowl::apples
オブジェクトの&bowl::oranges
メンバーを指さないでください。彼らはクラスのメンバーを指しています。それらは、何かを指す前に、実際のオブジェクトへのポインターと組み合わせる必要があります。その組み合わせは、オペレーターによって実現されます。->*
別のアプリケーションは侵入型リストです。要素タイプは、そのnext / prevポインターが何であるかをリストに伝えることができます。したがって、リストはハードコードされた名前を使用しませんが、既存のポインターを引き続き使用できます。
// 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);
}
next
。
これは、信号処理/制御システムから、私が現在取り組んでいる実際の例です。
収集するデータを表す構造があるとします。
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命令を使用することにより、さらに改善できます。しかし、これらは多くの働きをします値がすべてメモリ内で連続しており、単一の命令を使用して4つのサンプルを一緒にロードできる場合(これらはより新しいSSEバージョンでより多く)うまく機能します。
YMMV-アルゴリズムに合うようにデータ構造を設計します。
double Sample::*
重要なのはこの部分です。
後で、任意のインスタンスでこのメンバーにアクセスできます。
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;
少しあいまいなように見えますが、1つの可能なアプリケーションは、汎用データをさまざまなオブジェクトタイプに逆シリアル化するコードを記述しようとしていて、コードが完全に何も知らないオブジェクトタイプを処理する必要がある場合です(たとえば、コードはライブラリ内、および逆シリアル化するオブジェクトは、ライブラリのユーザーによって作成されました)。メンバーポインターを使用すると、型のないvoid *を使わなくても、個々のデータメンバーのオフセットを参照できます。これは、C構造体の場合と同じです。
これにより、メンバー変数と関数を統一した方法でバインドできます。以下は、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;
}
union
to type-pun を使用することは、未定義の動作の多数の形式を呼び出すため、標準では許可されていません...この答えは問題ありません。
float *component[] = { &x, &y, &z }; return *component[idx];
、コンポーネントへのポインターなしで書き換えることができます。つまり、コンポーネントへのポインターは、難読化以外の目的には使用できないようです。
私がそれを使用した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ステートメントよりもエレガントだと思いますが、それは私の意見にすぎません。
Algorithm
クラスと2つの派生クラス(たとえば、AlgorithmA
と)でも同じことができますAlgorithmB
。このような場合、両方のアルゴリズムは十分に分離されており、個別にテストすることが保証されています。
クラスへのポインタは実際のポインタではありません。クラスは論理的な構造であり、メモリ内に物理的な存在はありませんが、クラスのメンバーへのポインターを作成すると、メンバーが見つかるメンバーのクラスのオブジェクトへのオフセットが与えられます。これにより重要な結論が得られます。静的メンバーはどのオブジェクトにも関連付けられていないため、メンバーへのポインターは静的メンバー(データまたは関数)を指すことはできません 。
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;
}
出典:The Complete Reference 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
}
構造があるとします。その構造の内部には*ある種の名前があります*同じタイプであるが異なる意味を持つ2つの変数
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の回答にいくつかの使用例を追加するために、ここで、ポインターへのメンバー関数とポインターからメンバーへのデータに関する優れた資料を紹介します。