我写了一些示例代码(下面)来理解C++的指向成员的指针功能。但是,我遇到了一个奇怪的问题,其中语法
(*it).*attribute
被编译器接受,但语法
it->*attribute
不接受,但出现错误
->*
的左操作数必须是指向与右操作数兼容的类的指针,但std::__1::__wrap_iter<Bowl *>
但是,如果我取消注释Bowl bowls[3] =
并注释掉std::vector<Bowl> bowls =
,即从使用std::vector
切换到使用原始数组,那么两种语法都可以正常工作。
从C++参考中,我发现
表达式
E1->*E2
完全等效于内置类型的表达式(*E1).*E2
因此,该错误似乎与数组是内置类型std::vector
但不是这一事实有关。在该部分的后面,我发现
在针对用户定义运算符的重载解析中,对于类型 D、B、R 的每个组合,其中类类型 B 要么与 D 相同,要么是 D 的明确且可访问的基类,R 是对象或函数类型,以下函数签名参与重载解析:
R& operator->*(D*, R B::*);
但是由于std::vector
没有定义operator->*
所以我非常困惑。为什么我在一种语法上收到此错误,而在另一种语法上却没有,并且仅在使用std::vector
而不是基元数组时出现此错误?
#include <iostream>
#include <iterator>
#include <vector>
class Bowl
{
public:
unsigned int apples;
unsigned int oranges;
unsigned int bananas;
Bowl(unsigned int apples, unsigned int oranges, unsigned int bananas)
: apples(apples), oranges(oranges), bananas(bananas)
{
// nothing to do here
}
};
template <typename TClass, typename TIterator, typename TResult>
TResult sum_attribute(TIterator begin, TIterator end, TResult TClass::*attribute)
{
TResult sum = 0;
for (TIterator it = begin; it != end; ++it)
{
sum += (*it).*attribute;
sum += it->*attribute;
}
return sum;
}
int main()
{
std::vector<Bowl> bowls =
// Bowl bowls[3] =
{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
};
int num_apples = sum_attribute(std::begin(bowls), std::end(bowls), &Bowl::apples);
int num_oranges = sum_attribute(std::begin(bowls), std::end(bowls), &Bowl::oranges);
int num_bananas = sum_attribute(std::begin(bowls), std::end(bowls), &Bowl::bananas);
std::cout << "We have " << num_apples << " apples, " << num_oranges << " oranges, and " <<
num_bananas << " bananas. Now wasn't that fun?" << std::endl;
}
为了更好地理解这一点,我认为讨论与*
运算符和->
运算符的相似之处是有帮助的。
如果你有一个类类型的迭代器,你写
(*itr).field
C++将其解释为意味着
itr.operator*().field
同样,如果你写
itr->field
C++将其解释为意味着
itr.operator->().field
请注意,它们调用不同的重载运算符。第一个调用operator*
,第二个调用operator->
。这与内置类型不同;如您所指出的,对于内置类型,语法
base->field
只是一个简写
(*base).field
当你谈论时,这里也会发生类似的事情
(*itr).*memberPtr
和
itr->*memberPtr
在第一种情况下,C++将(*itr).*memberPtr
视为意味着
itr.operator*().*memberPtr
请注意,这意味着.*
运算符直接应用于要迭代的项,并且迭代器本身不会重载.*
运算符。另一方面,如果你写
itr->*memberPtr
C++将其视为对
itr.operator->*(memberPtr)
并报告错误,因为迭代器类型不需要(也很少)实现operator ->*
。(事实上,我在 C++ 编程过程中看到operator ->*
的重载完全为零)。
基元类型在这里将base->*memberPtr
视为(*base).*memberPtr
这一事实是无关紧要的,这与基元类型将base->field
视为(*base).field
的事实相同;编译器不会从operator->
自动生成operator*
,反之亦然。
还有一个单独的问题,关于为什么不需要迭代器来做到这一点,不幸的是,我没有一个很好的答案。我的猜测是,这是一个非常罕见的情况,没有人想到把它放在标准中,但我不确定。
至于为什么这适用于基元数组而不是std::vector
,你正在使用支持->*
运算符的原始指针的基元数组。std::vector
的迭代器不需要是原始指针,如果它们是类类型的对象,上面的推理解释了为什么你不应该期望它们支持operator ->*
。