Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/134.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 这对常量来说是不是太不可变了?_C++_Constants_Mutable - Fatal编程技术网

C++ 这对常量来说是不是太不可变了?

C++ 这对常量来说是不是太不可变了?,c++,constants,mutable,C++,Constants,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 explic

基本上,我有以下情况。注意:
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_;
};
因此,为了确保常量的正确性,中间缓冲区从不公开,因此它符合使用
可变变量的定义。如果我从未重用过这个缓冲区,或者在使用缓存值之前检查了相同的输入数据,那么故事就到此结束了,但是由于重用中间层,我觉得我已经将它公开了一半,所以我不确定下面的内容是否有意义

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

想法?

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

如果在重用相同的输入数据时可以节省大量计算,则将
input\u 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;
    };
显然,这只是一个大纲,没有更多关于什么是
输入数据
中间缓冲区
什么东西
的细节,或者它们是如何使用或共享的,很多细节都会丢失。我肯定会从实现中删除指针并使用
std::vector
。这尤其适用于
输入\ u数据
,您希望在其中存储传入缓冲区的副本。考虑:

    A a(input);
    //modifiy input
    a.foo();
使用不匹配的
输入数据/中间缓冲区
对可能会得到错误的结果


还要注意的是,如果你不需要为
foo
bar
实际使用
input_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
}
A a(some_big_data);
a.bar(some_big_data, true);

由于thread2正在调用一个
常量
函数,因此它不应该对从thread1调用
的输出产生任何影响-但是如果时间是正确的(或错误的!),它会-由于这些原因,我认为在这种情况下,使用
可变
使
foo
常量
是错误的,即使您仅从单个线程使用该类。

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

以互斥体成员变量和“线程安全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_uuu成员变量,编译器将强制执行此操作

在您的例子中,您确实希望中间_缓冲区是常量,但您通过声明它是可变的来明确告诉编译器它不是常量。结果是,您可以更改数据,而编译器对此无能为力

看到区别了吗

如果您真的希望接口符合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。如果他们知道输入数据已经更改,他们将必须创建一个新的,最好是常量

鉴于您无法检查
输入数据的等效性,您可能需要对其进行加密哈希,并使用该哈希进行比较。这将消除对
reuse\u intermediate
标志的需要。

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

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,那么在任何时候调用它都不会改变下一个操作的结果

例如:

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
}
A a(some_big_data);
a.bar(some_big_data, true);
应给出与完全相同的结果(不包括性能差异)

由于foo是const
const
,用户希望结果是相同的。 如果是这种情况,那么使缓冲区可更改是合理的。 否则,这可能是错误的


希望这有助于

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

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

mutable
似乎是合理的。但也许只是将其重命名为
bar(void*input\u data,bool fast)
,而不是暗示存在中间层。如果不想让用户伤害自己,也可以将其设置为
private
而不是
public
。使用
t
而不是
void
会更清晰
void*
具有特殊的语义。根据该参数,您永远不应该使用mutable或mutex锁定任何使用mutable的函数调用。例如,我要说的是,在大多数情况下,
mutable
mutex不会改变运行函数的实际结果(除非在计算或类似情况下使用函数中花费的时间,在这种情况下,静音