这是否太不可变而不能使用const

Is this too non-mutable to const?

本文关键字:const 不能 是否 不可变      更新时间:2023-10-16

基本上,我有以下情况。注: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的正确性,intermediate_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_;
};

想法吗?

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

以互斥对象成员变量和'线程安全的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_; 
    };

现在责任是真正的在用户身上,由编译器强制执行,不管注释说什么,也不使用任何可变的。如果他们知道input_data已经改变,他们将不得不创建一个新的,最好是const。

不将中间对象隐藏在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_;
};

直接回答你的问题。如果函数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是合理的。否则,很可能是错误的。

希望这对您有所帮助

免责声明:我的回答并不是在提倡使用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_data, intermediate_buffersomething是什么的细节,或者它们是如何使用或共享的,很多细节将会缺失。我肯定会从实现中删除指针并使用std::vector。对于input_data尤其如此,您可能希望在其中存储传入缓冲区的副本。考虑:

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

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

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

我想说您使用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是错误的,即使你只从单个线程使用类。

既然您不能检查input_data是否等价,那么您也许可以对它进行加密散列并将其用于比较。这将消除对reuse_intermediate标志的需要。

mutable用于覆盖变量或数据成员的const-ness。例子:

 Class A
  {
    public:
        int m;
  }
  void foo(const A& a);

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

相关文章: