4

考虑在某个库中(我们无权更改),我们有一个 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's 计数器,重置计数。这会让来电者有些悲伤。(想象一些更有害的事情发生,你会看到我要去哪里。)

我需要能够碰撞c(因此,我需要它是可变的),但breakTheHellOutOfCounter()由于试图替换 c. 有没有办法我可以改变事情(除了Counter班级)来实现这一点?

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

4

4 回答 4

4

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

#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自身,我认为这是无法避免的。

于 2012-08-06T00:04:10.763 回答
3

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

于 2012-08-05T23:58:47.907 回答
2

正如迈克尔安德森所说,您可以将您的计数器对象包装在一个防止分配的类中。

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;
}

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

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

于 2012-08-06T00:51:12.473 回答
1

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

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

class IBumpable
{
    void bump() ...
};

class Counter : IBumpable
{
    ....
};

void functionThatCannotBreakCounter(IBumpable& counter) { ... }
于 2012-08-06T00:04:00.907 回答