【问题标题】:Prevent reassignment of a reference?防止重新分配参考?
【发布时间】:2012-08-05 23:50:41
【问题描述】:

考虑在某个库中(我们无权更改),我们有一个 Counter 类:

class Counter {
    int count;
  public:
    Counter() : count(0) { }
    void bump() { ++count; }
    int getCount() const { return count; }
};

就其本质而言,它是可变的。如果它是 const,那它就毫无价值了。

在我们的代码中,我们“使用”了那个计数器。很糟糕。

#include <string>
#include <iostream>
#include <Counter.hpp>

using std::cout;
using std::endl;

void breakTheHellOutOfCounter(Counter &c) {
    // This is OK
    c.bump();

    // Oh noes!
    c = Counter();
}

int main() {
    Counter c;
    c.bump(); c.bump(); c.bump();
    std::cout << "Count was " << c.getCount() << std::endl;
    breakTheHellOutOfCounter(c);
    std::cout << "Count is now " << c.getCount() << std::endl;
}

注意breakTheHellOutOfCounter 用闪亮的新计数器覆盖main 的计数器,重置计数。这会让来电者有些悲伤。 (想象一下更有害的事情发生了,你就会明白我要去哪里了。)

我需要能够碰撞c(因此,我需要它是可变的),但我希望breakTheHellOutOfCounter() 由于试图替换 c 而惨遭失败。有没有办法我可以改变一些事情(Counter 类除外)来实现这一点?

(我知道在最低级别,这几乎是不可能强制执行的。我想要的是一种让意外难以做到的方法。)

【问题讨论】:

  • 要解决这个问题:将Counter 更改为不包含operator= 不是一种选择?
  • 只有几点。 count 成员不可变本身并没有错;如果合适的话,你可以让它成为你使用它的任何其他类的mutable 成员。没有客户端代码会写入c = Counter() 并意外重置计数器;有问题的场景真的是它嵌入的对象的意外成员复制吗?如果是这样,您可能会使容器类不可复制...(最佳语法因 C++03/C++11 不同而异,但很容易在 Google 上搜索到)。
  • @delnan:不是这样。我的意思是,对于我创建的类型,它是一种选择,但如果我无法使用别人的东西,我需要知道如何将潜在的损害降到最低。 :)

标签: c++ pass-by-reference


【解决方案1】:

在不修改计数器本身的情况下,我能看到的最干净的解决方案是:

#include <string>
#include <iostream>
#include <Counter.hpp>

template <typename T>
struct Unbreakable : public T {
  Unbreakable<T>& operator=(const Unbreakable<T>&) = delete;
  Unbreakable<T>& operator=(Unbreakable<T>&&) = delete;

  template <typename ...Args>
  Unbreakable(Args&& ...args) : T(std::forward<Args>(args)...) {}
};

using std::cout;
using std::endl;

void breakTheHellOutOfCounter(Unbreakable<Counter> &c) {
    // this is ok
    c.bump();

    // oh noes!
    c = Counter();
}


int main() {
    Unbreakable<Counter> c;
    c.bump(); c.bump(); c.bump();
    std::cout << "Count was " << c.getCount() << std::endl;
    breakTheHellOutOfCounter(c);
    std::cout << "Count is now " << c.getCount() << std::endl;
}

这从您的“哦,不”行中正确给出了错误。 (示例使用 C++11,但 C++98 解决方案类似)

不排除这样的用法:

Counter& br = c;
br = Counter();

当然,但如果不修改 Counter 本身,我认为这是无法避免的。

【讨论】:

    【解决方案2】:

    最简单的方法是从 Counter 类中删除赋值运算符。但是,由于您无法更改 Counter 类,因此您唯一真正的选择是将 Counter 类包装在一个没有赋值运算符的类中,然后改用它。

    【讨论】:

      【解决方案3】:

      正如 Michael Anderson 所说,您可以将计数器对象包装在一个防止分配的类中。

      class CounterProxy {
          Counter& counter;
          CounterProxy & operator=(const CounterProxy&);
      public:
          CounterProxy(Counter& c) : counter(c) {}
          void bump() { counter.bump(); }
          int getCount() const { return counter.getCount(); }
      };
      
      void breakTheHellOutOfCounter(CounterProxy &c) {
          // this is ok
          c.bump();
      
          // not oh noes!
          c = CounterProxy(Counter());
      }
      
      int main() {
          Counter c;
          c.bump(); c.bump(); c.bump();
          std::cout << "Count was " << c.getCount() << std::endl;
          breakTheHellOutOfCounter(CounterProxy(c));
          std::cout << "Count is now " << c.getCount() << std::endl;
      }
      

      您可以在想要限制可以对对象执行的操作时使用此方法。

      编辑:您可能已经意识到这一点并正在寻找更优雅的解决方案,但代码可能对其他人有所帮助。

      【讨论】:

      • 它可能没有那么优雅,但它可以完成任务。而且由于它不涉及从Counter 继承(因此在某些情况下重新获得不需要的= 运算符),它也更难破解。 :) +1
      【解决方案4】:

      通过可变引用允许bump,您正在授予函数访问权限以弄乱对象状态。分配没有什么特别之处;它只是一个以某种方式改变对象的函数。它也可以是一个名为 CopyStateFromAnotherInstance() 而不是 operator =() 的函数。

      所以真正的问题是:如何只允许某些功能而隐藏其他功能?通过使用接口:

      class IBumpable
      {
          void bump() ...
      };
      
      class Counter : IBumpable
      {
          ....
      };
      
      void functionThatCannotBreakCounter(IBumpable& counter) { ... }
      

      【讨论】:

      • 这如何阻止我说counter = Counter();
      • 因为IBumpable 没有operator =()...它不会编译。
      • 我认为有一个微不足道的赋值运算符,除非您将其删除或将其声明为私有或其他?
      • 无论如何...这需要修改Counter,这不是本示例中的选项。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-22
      • 1970-01-01
      • 2021-07-29
      • 2010-12-06
      • 1970-01-01
      相关资源
      最近更新 更多