4

我正在重新阅读我的一些旧大学教科书时正在对运算符重载进行一些探索,我认为我误解了一些东西,所以希望这对一些回答者来说是一个不错的轻松声誉。如果这是重复的,请指出我正确的方向。

我创建了一个简单的计数器类,它(在这个阶段)有一个成员,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;
}

我已经把它变成了一个非成员函数,并且是 counter 类的一个朋友,这样它就可以访问私有值。

当添加另一个私有成员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+返回的类型,但是我没有运气,我开始认为也许我已经做了一些事情根本错误。

4

3 回答 3

11

您需要将参数作为 const 引用传递。例如:

counter& counter::operator=( const counter &rhs )

运算符+() 也是如此。为了能够将临时值绑定到函数参数,这是必要的。当你按值返回时会创建临时值,所以当你说:

varOne + varTwo

一个无名的临时创建。这是正确的做法,但您必须确保诸如赋值操作之类的函数可以通过将其参数设为 const 来接受此类值。

您还需要为您的类实现复制构造函数和析构函数,尽管缺少这些不会导致编译错误(很遗憾)。

于 2009-06-07T14:13:07.253 回答
1

解决此问题的另一种方法是使用 PImpl 模式并交换赋值运算符。假设您仍然有一个 counter(int) 构造函数,您可以编写 operator= 如下

counter& counter::operator=(const counter& rhs) {
  counter temp(rhs.getN());
  std::swap(&pVal,rhs.pVal);
  return *this;
}

这样做的好处是将混乱的内存管理函数留在构造函数和析构函数中它们应该在的地方。

于 2009-06-07T15:03:24.967 回答
1

这里的关键(正如之前的海报所触及)但值得强调的是,C++ 中的表达式可以分为右值或左值。

它们在这些类别背后有很多细节,但是指导您直觉的有用启发式方法是:如果您可以获取表达式的地址(例如变量),则它是左值(这里的故事还有很多,但这是一个很好的起点)。

如果它真的不是左值,它就是右值——右值的一个有用的启发式方法是它们代表编译器实例化以使您的代码工作的“隐藏”临时对象。这些对象由编译器在后台创建和销毁。

为什么这在这里相关?

好吧,在 C++98/03 中(我假设你正在使用),请记住以下两条规则:

1) 只有左值表达式可以绑定到非 const 引用(忽略强制转换) 2) 右值表达式只能绑定到 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); 

}

当您将右值绑定到 const 引用时,您基本上将临时对象的生命周期延长到引用的生命周期(在本例中为范围)(并且编译器不能只是在该表达式的末尾销毁它) -所以你最终会引用一个有效的对象。

我希望这有助于理解为什么您必须将赋值运算符声明为接受 const 引用而不仅仅是非常量引用,正如其他海报之一正确推荐的那样。

ps 您的代码还有其他一些问题(例如,为什么要销毁和创建对象在每次赋值时专门指向的内存,以及缺少复制构造函数和析构函数),如果没有人通过当这篇文章出现时,我会:)

ps 可能还值得知道的是,C++0x 添加了一些称为右值引用(非常量)的东西,它优先绑定到右值并为程序员提供极其强大的优化机会(无需依赖编译器的优化功能) - 它们还有助于解决在 C++ 中创建完美转发函数的问题 - 但现在我们离题了 ;)

于 2009-06-07T16:30:26.637 回答