我遇到这种奇怪的运行良好的代码片段:
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;
}
,< em > < / em >你为什么想这么做,它会给你另一个间接层,可以解决一些棘手的问题。但说实话,我从来没有在自己的代码中使用它们。
<强>编辑:< /强>我可以认为当即令人信服的使用指针成员数据。指针指向成员函数可以用于可插拔的体系结构,但再次失败我生产一个例子在一个小空间。以下是我最好的(未测试)尝试-应用函数之前,会做一些pre发布处理应用用户选择一个对象的成员函数:
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
";
std::cout << "I have " << count_fruit(bowls, bowls + 2, & bowl::oranges) << " oranges
";
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 ...
现在假设你想计算一个函数(比如平均值)的变量范围的样本,这意味着你想因素计算成一个函数。pointer-to-member使它容易:
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更简洁的模板函数方法< /强>
当然,你可以计算一个模板意味着任何forward-iterator和任何支持与自身之外的值类型和分裂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;
在许多架构,一个实例的<代码>样本> < /代码将填补高速缓存线路。所以在每一次迭代的循环,一个样品将从内存缓存。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值,接下来的三个也将加载到缓存(假设适当对齐),意味着你不需要任何值加载在接下来的三个迭代。
上述算法可以提高一些进一步通过使用SIMD指令如SSE2架构。然而,这些作品<强>更好< /强>如果内存中的值都是连续的,您可以使用一个单指令加载四个样本(稍后在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;
}
Note that you do need an instance to call it on, so it does not work like a delegate.
It is used rarely, I ve needed it maybe once or twice in all my years.
通常使用一个接口(即纯c++中的基类)是更好的设计的选择。
< a href = " http://publib.boulder.ibm.com/infocenter/lnxpcomp/v8v101/index.jsp?topic=/com.ibm.xlcpp8l.doc/language/ref/cplr034.htm " rel = " noreferrer " > < / > IBM有一些更多的文档如何使用这个。简单地说,你使用的指针作为抵消到类。您不能使用这些指针除了他们引用的类,所以:
int Car::*pSpeed = &Car::speed;
Car mycar;
mycar.*pSpeed = 65;
这似乎有点晦涩难懂,但一个可能的应用程序如果你试图编写代码反序列化通用数据转换成许多不同的对象类型,和您的代码需要处理对象类型,它绝对不了解(例如,您的代码是在图书馆,和你反序列化的对象是由一个用户的库)。成员指针给你一个通用的,semi-legible指个人数据成员补偿的方法,而不必诉诸的无类型void *技巧可以为C结构的方式。
它能以统一的方式绑定成员变量和函数。以下是与你的车类的例子。更常见的用法会绑定<代码> std::对::第一个< /代码>和<代码>::第二次> < /代码在使用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;
}
您可以使用一个数组的指针(均匀)成员数据启用双,named-member(即x.data)和array-subscript(即x [idx])接口。< br / >
#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语句甚至在没有实际用途的情况下,但只是因为。
Pointers to classes are not real pointers; a class is a logical construct and has no physical existence in memory, however, when you construct a pointer to a member of a class it gives an offset into an object of the member s class where the member can be found; This gives an important conclusion: Since static members are not associated with any object so a pointer to a member CANNOT point to a static member(data or functions) whatsoever Consider the following:
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 + + -赫伯特Schildt第四版< /强>
这里是一个例子,指针指向的数据成员是很有用的:
#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 <<
; // Tom
}
Suppose you have a structure. Inside of that structure are * some sort of name * two variables of the same type but with different meaning
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-b”;所有的键都是礼物,foo要么a或b或两者兼而有之。
只是添加一些用例@anon & @Oktalist年代回答,这年代伟大的阅读材料pointer-to-member-function和pointer-to-member-data。
< a href = " https://www.dre.vanderbilt.edu/施密特/ PDF / c++ -ptmf4.pdf”rel = " nofollow noreferrer " > https://www.dre.vanderbilt.edu/ ~施密特/ PDF / c++ -ptmf4.pdf < / >
有指针成员,我们可以编写通用代码
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;
}
我爱<代码> < /代码>和<代码>和< /代码>操作符:
struct X
{
int a {0};
int *ptr {NULL};
int &fa() { return a; }
int *&fptr() { return ptr; }
};
int main(void)
{
X x;
int X::*p1 = &X::a; // pointer-to-member int X::a . Type of p1 = int X::*
x.*p1 = 10;
int *X::*p2 = &X::ptr; // pointer-to-member-pointer int *X::ptr . Type of p2 = int *X::*
x.*p2 = nullptr;
X *xx;
xx->*p2 = nullptr;
int& (X::*p3)() = X::fa; // pointer-to-member-function X::fa . Type of p3 = int &(X::*)()
(x.*p3)() = 20;
(xx->*p3)() = 30;
int *&(X::*p4)() = X::fptr; // pointer-to-member-function X::fptr . Type of p4 = int *&(X::*)()
(x.*p4)() = nullptr;
(xx->*p4)() = nullptr;
}
的确是真的,只要所有成员是公开的,或者是静态的
我认为你d只想这样做如果会员数据非常大(例如,另一个相当高的类的一个对象),和你有一些外部例程只适用于该类的对象的引用。你别想复制成员对象,这允许您通过它。
realworld pointer-to-member的例子可能是一个更窄的混叠构造函数为std::要查看:
template <typename T>
template <typename U>
shared_ptr<T>::shared_ptr(const shared_ptr<U>, T U::*member);
<强>是什么构造函数将有利于< /强>
假设您有一个struct foo:
struct foo {
int ival;
float fval;
};
如果你给一个要foo,然后您可以检索要查看到其成员ival或fval使用构造函数:
auto foo_shared = std::make_shared<foo>();
auto ival_shared = std::shared_ptr<int>(foo_shared, &foo::ival);
这将是有用的,如果想通过指针foo_shared - > ival一些预计要查看的功能
< a href = " https://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr " rel = " nofollow noreferrer " > https://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr < / >
指针成员c++类型安全等效为C年代<代码> offsetof() < /代码>,<代码> stddef中定义。h > < /代码:返回的信息,某些字段所在在<代码>类< /代码>或<代码>结构> < /代码。而<代码> offsetof() > < /代码可以使用某些简单类型也在c++中,这对一般情况下失败得很惨,尤其是虚拟基类。所以指针成员被添加到标准。他们还提供了简单的语法来引用一个实际的字段:
struct C { int a; int b; } c;
int C::* intptr = &C::a; // or &C::b, depending on the field wanted
c.*intptr += 1;
容易得多比:
struct C { int a; int b; } c;
int intoffset = offsetof(struct C, a);
* (int *) (((char *) (void *) &c) + intoffset) += 1;
为什么人想使用<代码> offsetof() < /代码>(或指针成员),在stackoverflow有很好的答案。一个例子是:< a href = " https://stackoverflow.com/questions/7897877/how-does-the-c-offsetof-macro-work " > C offsetof宏是如何工作的呢? < / >