我遇到了这个编译好的奇怪的代码片段:
class Car { public: int speed; }; int main() { int Car::*pSpeed = &Car::speed; return 0; }
为什么 C++有一个指向类的非静态数据成员的指针?在实际代码中这个奇怪的指针有什么用?
它是"指向成员的指针" - 以下代码说明了它的用法:
#includeusing 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
是必要的,因为->*
运算符的优先级低于函数调用运算符.
这是我能想到的最简单的例子,它传达了这个特征相关的罕见情况:
#includeclass 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函数的麻烦.
另一个应用程序是侵入性列表.元素类型可以告诉列表它的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: // templatetemplate 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 lst(&apple::next); apple a; lst.add(a); }
这是我正在研究的一个真实世界的例子,来自信号处理/控制系统:
假设您有一些表示您正在收集的数据的结构:
struct Sample { time_t time; double value1; double value2; double value3; };
现在假设你将它们填充到一个向量中:
std::vectorsamples; ... fill the vector ...
现在假设您想要计算一系列样本中某个变量的某个函数(比如平均值),并且您希望将此平均值计算因子计算为函数.指向成员的指针使其变得简单:
double Mean(std::vector::const_iterator begin, std::vector ::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的任何值类型:
templateS mean(Titer begin, const Titer& end, S std::iterator_traits ::value_type::* var) { using T = typename std::iterator_traits ::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 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::vectorseries = ...; 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::vectorw, 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值时,接下来的三个值也将加载到缓存中(假设适当的对齐),这意味着您不需要为接下来的三次迭代加载任何值.
通过在例如SSE2架构上使用SIMD指令,可以进一步改进上述算法.但是,如果值在内存中都是连续的,那么这些工作要好得多,您可以使用单个指令将四个样本一起加载(在以后的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算法和Boost时.
#include#include
#include #include #include #include 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 l; l.push_back(Car(10)); l.push_back(Car(140)); l.push_back(Car(130)); l.push_back(Car(60)); // Speeding cars list 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#include 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; }