我正在对操作员超载进行一些探索,同时重新阅读我的一些旧的大学教科书,我认为我误解了一些东西,所以希望这对某些人来说是个不错的声誉回答者。如果这是重复,请指出我正确的方向。
我创建了一个简单的计数器类,在(在这个阶段)有一个成员,val(一个int)。
我初始化了其中三个计数器,varOne为varThree,并希望第三个计数器为前两个计数器的总和(例如,varThree.val在下面的代码中设置为5)
counter::counter(int initialVal)
{
val = initialVal;
//pVal = new int;
//*pVal = 10; // an arbitrary number for now
}
int main (int argc, char const* argv[])
{
counter varOne(3), varTwo(2), varThree;
varThree = varOne + varTwo;
return 0;
}
我重载了operator +,就像这样:
counter operator+(counter& lhs, counter& rhs)
{
counter temp(lhs.val + rhs.val);
return temp;
}
我已将此作为非成员函数,并且是计数器类的朋友,以便它可以访问私有值。
我的问题在添加另一个私有成员 pVal (指向int的指针)时开始。添加这意味着我不能再执行简单的varThree = varOne
复制,因为当varOne被销毁时,varThree.pVal仍将指向相同的内存位。
我按如下方式重载了operator=
。
int counter::getN()
{
return *newVal;
}
counter& counter::operator=(counter &rhs)
{
if (this == &rhs) return *this;
val = rhs.val;
delete pVal;
pVal = new int;
*pVal = rhs.getN();
return *this;
}
现在,如果我执行varThree = varOne
之类的操作,则所有内容都会正确复制,但尝试执行varThree = varOne + varTwo
时会出现以下错误:
counter.cpp: In function ‘int main(int, const char**)’:
counter.cpp:96: error: no match for ‘operator=’ in ‘varThree = operator+(counter&, counter&)(((counter&)(& varTwo)))’
counter.cpp:55: note: candidates are: counter& counter::operator=(counter&)
make: *** [counter] Error 1
好像counter::operator=
无法处理来自operator+
的返回输出,并且我需要进一步重载operator=
以接受operator+
所属的类型回来了,但我没有运气,我开始认为也许我做了一些根本错误的事情。
答案 0 :(得分:11)
您需要将参数作为const引用传递。例如:
counter& counter::operator=( const counter &rhs )
类似于operator +()。这是必要的,以便能够将临时值绑定到函数参数。按值返回时会创建临时值,因此当您说:
时varOne + varTwo
创建无名临时。这是正确的做法,但你必须确保诸如赋值op之类的函数可以通过使它们的参数为const来接受这些值。
您还需要为您的类实现复制构造函数和析构函数,但缺少这些不会导致编译错误(不幸的是)。
答案 1 :(得分:1)
解决此问题的另一种方法是使用PImpl模式并交换赋值运算符。假设你还有一个counter(int)构造函数,你可以编写operator =,如下所示
counter& counter::operator=(const counter& rhs) {
counter temp(rhs.getN());
std::swap(&pVal,rhs.pVal);
return *this;
}
这有利于将凌乱的内存管理功能留在构造函数和析构函数中。
答案 2 :(得分:1)
这里的关键(如前一张海报所述)但值得强调的是,C ++中的表达式可以分为rvalues或lvalues。
他们在这些类别背后有很多细节,但是引导你直觉的有用启发式是:如果你可以获取表达式的地址(例如变量)它是一个左值(这里的故事还有很多,但这是一个很好的起点)。
如果它确实不是左值,它是一个右值 - 对于右值的一个有用的启发式是它们代表编译器实例化的“隐藏”临时对象以使代码工作。这些对象是由幕后编译器创建和销毁的。
为什么这里有关系?
好吧,在C ++ 98/03中(我猜你正在使用它),请记住以下两条规则:
1)只有左值表达式可以绑定到非const引用(忽略强制转换) 2)rvalue表达式只能绑定到const引用(忽略强制转换)
一个例子在这里会有所帮助:
// Consider the function foo below - it returns an int -
// whenever this function is called, the compiler has
// to behave as if a temporary int object with the value 5 is returned.
// The use of 'foo()' is an expression that is an rvalue - try typing &foo() -
// [Note: if foo was declared as int& foo(), the story gets complicated, so
// i'll leave that for another post if someone asks]
int foo() { return 5; }
void bind_r(int& r) { return; }
void bind_cr(const int& cr) { return; }
int main()
{
int i = 10; // ok
int& ri = i; // ok binding lvalue to non-const reference, see rule #1
int& ri2 = foo(); // Not ok, binding a temporary (rvalue) to a non-const reference
// The temporary int is created & destroyed by compiler here
const int& cri = foo(); // ok - see rule #2, temporary int is NOT destroyed here
//Similarly
bind_r(i); // ok - rule #1
bind_r(foo()); // NOT ok - rule #2
bind_cr(foo()); // ok - rule #2
// Since the rules above keep you out of trouble, but do not exhaust all possibilities
// know that the following is well-formed too:
const int& cri2 = i;
bind_cr(i);
bind_cr(cri);
bind_cr(cri2);
}
当你将一个rvalue绑定到一个const引用时,你基本上将临时对象的生命周期延长到引用的生命周期(在这种情况下是范围)(并且编译器不能只在它的末尾销毁它)表达式) - 所以你最终得到了对有效对象的引用。
我希望这有助于理解为什么必须将赋值运算符声明为接受const引用而不仅仅是非const引用,这是正确推荐的其他海报之一。
P.S。您的代码还有一些其他问题(例如,为什么要销毁和创建对象在每个赋值时专门指向的内存,以及缺少复制构造函数和析构函数),如果没有人解决它们的问题这篇文章出现了,我会:)
P.S。也许值得一提的是,C ++ 0x增加了一些称为rvalue引用(非const)的东西,它优先绑定到rvalues并为程序员提供极其强大的优化机会(无需依赖编译器的优化功能) - 它们也有助于解决在C ++中创建完美转发函数的问题 - 但现在我们正在离题;)