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; }
至于你为什么要这样做,那么它会给你一个间接的解决scheme,可以解决一些棘手的问题。 但说实话,我从来没有用我自己的代码中使用它们。
编辑:我不能想象一个令人信服的使用成员数据指针的手。 指向成员函数的指针可以在可插入的体系结构中使用,但是再次在小空间中生成一个例子会使我失望。 以下是我最好的(未经testing的)尝试 – 一个Apply函数,在将用户select的成员函数应用到对象之前,将执行一些预处理和后处理:
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
周围的括号是必要的,因为->*
运算符比函数调用运算符的优先级低。
这是我能想到的最简单的例子,它expression了这个特征相关的罕见情况:
#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函数。
另一个应用程序是侵入性列表。 元素types可以告诉列表它的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); }
您可以稍后访问此成员,在任何情况下:
int main() { int Car::*pSpeed = &Car::speed; Car myCar; Car yourCar; int mySpeed = myCar.*pSpeed; int yourSpeed = yourCar.*pSpeed; assert(mySpeed > yourSpeed); // ;-) return 0; }
请注意,您确实需要一个实例来调用它,所以它不能像委托一样工作。
它很less使用,我需要它可能在我所有的年份一次或两次。
通常使用接口(即C ++中的纯基类)是更好的deviseselect。
从信号处理/控制系统中,我现在正在处理一个真实世界的例子:
假设你有一些代表你正在收集的数据的结构:
struct Sample { time_t time; double value1; double value2; double value3; };
现在假设你把它们填入vector中:
std::vector<Sample> samples; ... fill the vector ...
现在假设你想计算一个样本范围内某个variables的某个函数(比如平均值),并且你想把这个平均值计算分解成一个函数。 指针的成员可以很容易地:
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自身除法的任何值types:
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等,那么你应该把每个variables的值连续存储在内存中。 否则,遍历整个系列将导致检索到的每个值都未命中。
考虑这个代码的性能:
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
一个实例将填充caching行。 所以在循环的每一次迭代中,一个样本将从内存中被抽取到caching中。 将使用来自caching行的4个字节,其余的将被丢弃,下一次迭代将导致另一个caching未命中,内存访问等。
更好的做到这一点:
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值从内存加载时,接下来的三个值也将被加载到caching中(假设合适的alignment),这意味着在接下来的三次迭代中不需要加载任何值。
上述algorithm可以通过在例如SSE2体系结构上使用SIMD指令进一步改进。 但是,如果这些值在内存中都是连续的,并且可以使用单个指令一起加载四个采样(在稍后的SSE版本中),则这些工作会更好。
YMMV – devise你的数据结构来适应你的algorithm。
IBM还有一些关于如何使用它的文档。 简而言之,您使用指针作为类中的偏移量。 除了引用的类外,不能使用这些指针,所以:
int Car::*pSpeed = &Car::speed; Car mycar; mycar.*pSpeed = 65;
这看起来有些模糊,但是一个可能的应用是,如果你正在尝试编写用于将通用数据反序列化成许多不同的对象types的代码,并且你的代码需要处理它完全不知道的对象types(例如,你的代码是在一个库中,你反序列化的对象是由你的库的用户创build的)。 成员指针为您提供了一种通用的,半可读的方式来引用各个数据成员的偏移量,而不必像C结构那样求助于无types的void *技巧。
它可以以统一的方式绑定成员variables和函数。 以下是您的Car类的例子。 更常见的用法是在STLalgorithm中使用std::pair::first
和::second
,并在地图上使用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; }
您可以使用指向(同类)成员数据的指针数组来启用双重命名成员(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(vx == 1.0f); assert(vy == 2.0f); assert(vz == 3.0f); return 0; }
我使用过的一种方法是,如果我有两个如何在类中执行某些操作的实现,并且我想在运行时select一个,而不必不断地通过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语句会减慢所做的事情, 在某个密集algorithm的深处。 即使在没有实际用途的情况下,我仍然认为它比if语句更优雅,但这只是我的观点。
我认为如果成员数据相当大(例如,另一个相当庞大的类的对象),那么只需要执行此操作,并且只有对该类的对象的引用有效的外部例程。 你不想复制成员对象,所以这可以让你传递它。
这里是一个指向数据成员的指针可能有用的例子:
#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 }
假设你有一个结构。 该结构的内部是*某种名称*两个相同types的variables,但具有不同的含义
struct foo { std::string a; std::string b; };
好吧,现在让我们假设你在容器中有一堆foo
:
// key: some sort of name, value: a foo instance std::map<std::string, foo> container;
好的,现在假设你从不同的来源加载数据,但数据是以同样的方式呈现的(例如,你需要相同的parsing方法)。
你可以做这样的事情:
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's&@ Oktalist的答案添加一些用例,这是关于指向成员函数的指针和指向成员数据的很棒的阅读材料。 http://www.cs.wustl.edu/~schmidt/PDF/C++-ptmf4.pdf