这对const来说是不可变的吗?

时间:2014-03-26 15:56:26

标签: c++ const mutable

基本上,我有以下情况。注意:void*用于表示任意数据,它在实际应用程序中强类型。

class A
{
public:
   //uses intermediate buffer but doesn't change outward behavior
   //intermediate buffer is expensive to fill
   void foo(void* input_data);

   //uses intermediate buffer and DOES explicitly change something internally
   //intermediate buffer is expensive to fill
   void bar(void* input_data);

   //if input_data hasn't changed since foo, we can totally reuse what happened in foo
   //I cannot check if the data is equal quickly, so I allow the user to pass in the
   //assertion (put the honerous on them, explicitly tell them in the comments
   //that this is dangerous to do, ect)
   void bar(void* input_data, bool reuse_intermediate);
private:
   void* intermediate_buffer_;
   void* something_;
};

因此尝试const正确性时,middle_buffer_永远不会暴露,因此它适合使用mutable变量的定义。如果我从不重用这个缓冲区,或者我在使用缓存值之前检查了相等的input_data,那将是故事的结束,但是由于reuse_intermediate我觉得我有一半暴露它,所以我不确定是否或不是以下有意义。

class A
{
public:
   //uses intermediate buffer but doesn't change something
   //intermediate buffer is expensive to fill
   void foo(void* input_data) const;

   //uses intermediate buffer and DOES explicitly change something internally
   //intermediate buffer is expensive to fill
   void bar(void* input_data);

   //if input_data hasn't changed since foo, we can totally reuse what happened in foo
   //I cannot check if the data is equal quickly, so I allow the user to pass in the
   //assertion (put the honerous on them, explicitly tell them in the comments
   //that this is dangerous to do, ect)
   void bar(void* input_data, bool reuse_intermediate);

   //an example of where this would save a bunch of computation, though
   //cases outside the class can also happen
   void foobar(void* input_data)
   {
      foo(input_data);
      bar(input_data,true);
   }
private:
   mutable void* intermediate_buffer_;
   void* something_;
};

思想?

7 个答案:

答案 0 :(得分:6)

我认为这是对mutable的不当使用。根据我的经验,mutable用于辅助私有成员变量,这些变量本质上不能被声明为const,但不会修改"概念性常量"公共接口。

以Mutex成员变量和线程安全的getter'为例:

class Getter { 
public: 

    Getter( int d, Mutex & m ) : guard_( m ), data_( d ) { };

    int get( ) const { Lock l(guard_); return data_; };

private:

    mutable Mutex guard_;
    const int data_;
};

这里的要点是声明mutable(在这种情况下是守卫)的数据确实会改变(它被锁定和解锁)但这对constness没有影响从用户的角度来看。 最终,尽管存在可变的互斥锁,但您仍然无法更改const data_成员变量,编译器会强制执行此

在你的情况下,你真的希望intermediate_buffer是const,但你通过声明它是可变的来明确地告诉编译器它不是。结果是可以更改数据,编译器无法对其进行操作

看到区别?

如果您真的希望接口符合const协议,请通过以下方式明确说明:

    class A { 
    public:    

        A( void* input_data );// I assume this deep copies.

        void foo() const;

        void bar();

        private:    
            const void* intermediate_buffer_;   
            void* something_; 
    };

现在,用户的责任真正并由编译器强制执行,无论评论说什么,也不使用任何mutable。如果他们知道input_data已经改变,他们必须创建一个新的,最好是const。

答案 1 :(得分:4)

不要将中间对象隐藏在const对象中,而是将其公开,让foobar返回副本。您正在非常清楚地表明正在共享中间对象,并提供一种新功能来保留多个中间对象。如果要隐藏实现细节,可以公开一个空类,并使中间对象成为此基类的子级。

class A
{
public:
   class Intermediate
   {
      //empty
   };

   //uses intermediate buffer but doesn't change outward behavior
   //intermediate buffer is expensive to fill
   //if cache_ptr is NULL the intermediate buffer is discarded
   void foo(void* input_data, Intermediate** cache_ptr = NULL) const;

   //uses intermediate buffer and DOES explicitly change something internally
   //intermediate buffer is expensive to fill
   void bar(void* input_data, Intermediate** cache_ptr = NULL);
private:
   class IntermediateImpl : public Intermediate
   {
      //...
   };
   void* something_;
};

答案 2 :(得分:1)

直接回答您的问题。 如果函数foo是const,则在任何时候调用它都不应该改变下一个操作的结果。

例如:

A a(some_big_data);
a.bar(some_big_data, true);

应该给出完全相同的结果(不包括性能差异)

A a(some_big_data);
a.foo(some_different_big_data);
a.bar(some_big_data, true);

由于foo为const,因此用户希望结果相同。 如果是这种情况,那么制作缓冲区mutable是合理的。 否则,这可能是错误的。

希望这有帮助

答案 3 :(得分:0)

免责声明:我并不主张在我的回答中使用void*,我希望这只是为了演示目的,而您实际上并不需要自己使用它。

如果在重用相同的输入数据时可以保存很多计算,那么将input_data作为成员变量。

    class A
    {
      public:
        A(void * input_data)
        {
            set_input_data(input_data);
        }

        //Note, expensive operation
        void set_input_data(void* input_data)
        {
            this->input_data = input_data;
            fill_intermediate_buffer();
        }

        void foo() const;
        void bar() const;
      private:
        void * input_data;
        void * intermediate_buffer;
        void * something;
    };

这显然只是一个大纲,没有关于input_dataintermediate_buffersomething是什么或者如何使用或共享的更多细节,很多细节都将丢失。我肯定会从实现中删除指针并使用std::vector。对于input_data尤其如此,您需要存储传入缓冲区的副本。考虑:

    A a(input);
    //modifiy input
    a.foo();

使用不匹配的input_data/intermediate_buffer对可能会导致错误的结果。

另请注意,如果input_datafoo实际上不需要bar,那么您可以从类中删除void* input_data,但仍保留构造函数和引用它的setter。

答案 4 :(得分:0)

我说你使用mutable确实有一些意义 - 但这是不正确的,也可能是危险的。如果你创建一个函数const,那就需要它。

想象一下,如果你在一个多线程程序中使用你的类,有几个线程在同一个类的实例上运行 - 例如:

thread1:

while(1) {
    (...) //do some work
    sharedA->foo(thread1Data);
    sharedA->bar(thread1Data, true);
    (...) //do some more work
    sharedA->bar(thread1Data, true);
    (...) //do even more work
}

thread2:

while(1) {
    (...) //do some different work, maybe wait for an event
    sharedA->foo(thread2Data);
    (...) //sleep for a bit
}

由于thread2正在调用const函数,因此它不应该对从thread1调用bar的输出产生任何影响 - 但是如果时机正确(或错误!)它会 - 并且由于这些原因,我说在这种情况下使用mutable制作foo const是错误的,即使你是只使用单个线程中的类。

答案 5 :(得分:0)

鉴于您无法检查input_data的等效性,您可以对其进行加密哈希并将其用于比较。这将消除对reuse_intermediate标志的需要。

答案 6 :(得分:0)

mutable用于覆盖变量或数据成员的常量。 例如:

 Class A
  {
    public:
        int m;
  }

  void foo(const A& a);

在上面的例子中,即使你传递了一个const引用,也允许函数foo修改a.m。 在您的示例中,我认为您不需要使用可变 - 它可能导致非常糟糕的设计 - 因为您将写入该缓冲区。