提问人:cnewbie 提问时间:10/31/2023 最后编辑:user12002570cnewbie 更新时间:10/31/2023 访问量:117
为什么我的类使用了错误的 operator()?
Why is my class using the wrong operator()?
问:
考虑以下类,它提供了 2 个运算符,一个用于读取,另一个用于写入。Foo
()
#include <iostream>
#include <vector>
template <typename T>
class Foo {
public:
Foo(const std::vector<T> &values) { vals = values; }
const T &operator()(const int i, const int j) const {
std::cout << "Read" << std::endl;
return vals[i];
}
T &operator()(const int i, const int j) {
std::cout << "Write" << std::endl;
return vals[i];
}
private:
std::vector<T> vals;
};
int main() {
std::vector<int> values(100, 1);
Foo<int> f{values};
f(1,1) = 42; //here it writes:OK
std::cout << f(1,1) << std::endl; //why is again the "read" version called?
return 0;
}
我无法理解为什么在我的代码中只调用“写入”版本。
答:
3赞
user12002570
10/31/2023
#1
问题是它是非常量,这意味着非常量成员函数版本比常量成员函数版本更匹配,因为非常量版本的参数类型是,而常量版本的参数类型是,因此后者需要限定转换,而前者不需要,因此匹配度更好。f
this
Foo<int>*
const Foo<int>*
要解决这个问题,你可以制作 const(但这样你就不能在上面写了)或者在 non-const 上使用,如下所示:f
const_cast
f
int main()
{
std::vector<int> values(100, 1);
Foo<int> f{values}; //non-const f
f(1,1) = 42; //uses write version
//use read version now as we've used const_cast
std::cout << const_cast<const Foo<int>&>(f)(1, 1) << '\n'; //use const_cast
return 0;
}
在 C++17 中,您还可以使用 std::as_const
,如此演示中所示。
评论
1赞
user12002570
10/31/2023
@WeijunZhou完成,添加到更新的答案中。std::as_const
0赞
James Kanze
10/31/2023
#2
正如其他人所解释的,运算符重载解析选择写入函数,因为它是在非常量对象上调用的。重载分辨率(通常)不考虑结果的使用方式。它只考虑参数的类型。
这里通常的解决方案是让写入重载返回代理;将运算符的实际代码放在两个不同的函数中(例如,和)。代理将简单地保存指向实际对象的指针和两个参数。它将有一个转发到函数的赋值运算符,以及一个转发到函数的转换。(解析类型转换时,运算符是运算符重载解析考虑目标类型的罕见情况之一。put
get
put
operator T() const
get
像这样的东西应该可以工作:
#include <iostream>
#include <vector>
template <typename T>
class Foo
{
class Proxy
{
public:
Proxy( Foo* owner, int i, int j )
: m_owner( owner )
, m_i( i )
, m_j( j )
{
}
Proxy& operator=( T const& t )
{
m_owner->put( m_i, m_j, t );
return *this;
}
operator T const&() const
{
return m_owner->get( m_i, m_j );
}
private:
Foo* m_owner;
int m_i;
int m_j;
};
public:
Foo(std::vector<T> const& values) : vals( values ) {}
const T &get(int i, int j) const {
std::cout << "Read" << std::endl;
return vals[i];
}
void put(int i, int j, T const& newValue ) {
std::cout << "Write" << std::endl;
vals[i] = newValue;
}
T const& operator()(int i, int j) const {
std::cout << "Read" << std::endl;
return vals[i];
}
Proxy operator()(int i, int j) {
return Proxy( this, i, j );
}
private:
std::vector<T> vals;
};
int
main()
{
std::vector<int> values(100, 1);
Foo<int> f{values};
f(1,1) = 42; //here it writes:OK
std::cout << f(1,1) << std::endl;
return 0;
}
有点啰嗦,但这是一个标准的成语,而且这个类基本上是样板的。Proxy
评论
0赞
cnewbie
11/1/2023
谢谢你的提示!我知道 Proxy 模式,但我不太喜欢我必须在每次调用运算符时构造一个 Proxy 对象的事实。这难道不是密集型计算代码的问题吗?@JamesKanze()
0赞
cnewbie
11/1/2023
或者您是否使用 RVO 实际上适用于运营商的事实?@JamesKanze()
0赞
James Kanze
11/2/2023
@cnewbie我真的指望代理类是完全内联的,编译器应该完全优化它(具有足够高的优化水平 - 在没有优化的构建中,您仍然会看到它在调试器中构造)。
0赞
James Kanze
11/2/2023
换句话说,如果你编写 ,编译器应该生成与你编写 相同的代码。这种优化是相当标准的,并且已经持续了 30 或 40 年。f( 1, 2 ) = 42
f.put( 1, 2, 42 )
评论
f
是非,因此写入版本不需要转换来制作 A。顺便说一句,这真的重要吗?const
this
const Foo<int>*
std::cout << f(1,1) << std::endl;
f(1,1)
f(1,1)