代码之家  ›  专栏  ›  技术社区  ›  Tom

C++中“可变函数”的正确替换

c++
  •  12
  • Tom  · 技术社区  · 15 年前

    在C++中使用类算法时,经常会遇到const正确性的问题。我觉得我想要一个可变函数,尽管这是不允许的。有人能告诉我如何实现如下类吗?

    下面是我 希望 写作。

    • 函数run()不应是 const函数,因为它改变了 数据。
    • 函数get_result()应该 是一个常量函数(就用户而言),因为它返回 数据。

    但是,如果用户在不调用run()的情况下请求结果,我希望get_result()函数运行算法。这会破坏常量的正确性,因为我有一个常量函数调用了一个非常量函数。

    class operate_on_data
    {
      std::vector<double> m_data;  // the data to modify
      bool m_completed;  // check to see if the function run() has been called
    public:
      operate_on_data(std::vector<double> data)
        : m_data(data), m_completed(false) {}  //initialise
      void run() //I don't want this function to be const  
      {
        //The algorithm goes here - it alters m_data.
        m_completed = true;  //the algorithm has been run
      }
      std::vector<double> get_result() const //I want this function to be const
      {
        /*The following breaks const correctness because 
          I am calling a non-const function from a const function
          - but this feels like the right thing to do ... */ 
        if (!m_completed) run();  //run the algorithm if it has not run already
        return m_data; //return
      }
    };
    

    我编译上述类的唯一方法是

    • make run()const,并使m_data和m_completed可变。这是可行的,但概念上是错误的,因为run()明显地更改了数据。
    • 使get_result()不是常量函数。这似乎也是错误的,因为用户希望这个函数是一个简单的返回,因此是常量。
    • 将run()函数放入get_result()const函数中,并使数据变量可变。

    我的理解是可变关键字是为这些选项中的第三个设计的,在这些选项中,实现需要更改数据,但用户合理地期望一个简单的返回,因此需要const函数。

    但是,我不想做最后的选择,因为我希望用户能够准确地选择何时更改数据。但是,他们有可能忘记调用run(),因此如果他们在不调用run()的情况下请求结果,我希望强制使用算法。我想让run()变为可变的-但我不被允许。

    写这样一门课的正确方法是什么?

    8 回复  |  直到 10 年前
        1
  •  9
  •   Edward Strange    15 年前

    make run()const,并使m_data和m_completed可变。这是可行的,但概念上是错误的,因为run()明显地更改了数据。

    事实上,不是真的。这个 变量 事实上,在你的课堂里是改变的,但你永远也无法证明这一点。调用run()不会更改用户可以从类的接口中检索到的任何内容。如果你不能检索到关于这种变化的任何信息,那么你就不可能证明这种变化。这不仅仅是一个语义问题,它直接涉及到“可变”关键字的整个要点。

    “mutable”关键字被严重误解。

    也就是说,虽然我得到的信息最少,但我可能会用上面的方式来做,我并不推荐它。几乎可以肯定的是,如果对你的问题有一个更大的看法,有一个更好的方法是显而易见的。

    我可能会使用的另一个方法是您显然设置为避免的方法:强制用户在使用get_data()之前调用run()。说实话,这也是一个非常次优的方法。或许更甚。

    编辑:

    如果您决定使用可变方法,那么我建议您进行一些更改。拥有一个名为“run()”的函数,它是常量,不会返回任何感兴趣的内容,这会让人非常困惑。这个函数当然应该是非常量。因此,如果已经决定这样做,我要做的就是让run()调用一个const和private函数,该函数具有当前“run()”函数的行为,在指定的条件下,get_data()也会引用该函数。

        2
  •  5
  •   Kos    15 年前

    一些有助于你澄清问题的抽象评论:

    • const 方法是那些不修改对象的概念“状态”的方法,
    • 康斯特 方法就是这样做的。
    • 此外, mutable 字段是每个对象的字段,但不被视为对象概念的一部分。 state (就像一些缓存的值,它们被懒惰地评估并记住)。

    问题可能是 operate_on_data 可能不是一个定义明确的类。“在数据上操作”类的对象是什么?这个物体的“状态”是什么?什么不是?这听起来很尴尬(至少对我来说),对某些设计的笨拙描述可能表明设计有悖直觉。

    我的想法是,你将“操作”和“操作结果”的不同概念保留在一个奇怪的类中,这会导致混淆。

        3
  •  2
  •   Mark B    15 年前

    在运行算法之前,只需将获取结果作为一个错误(接口的一部分)。然后将工作与结果分开,允许两者都正确地指示它们的常量。如果您试图在运行算法之前调用它,以向客户机表明他们做错了什么,那么get方法可以抛出。

        4
  •  2
  •   DevSolar    15 年前

    我认为你的问题是语义上的,而不是句法上的。

    请求结果而不调用 run() 首先,在我看来,这是一个错误,应该导致一个异常。

    如果是的话 一个错误,应该是可能的,我觉得没有意义 () 首先,把它放下,在(非常量)中完成所有的工作。 get_result()

        5
  •  1
  •   Sven Marnach    15 年前

    如果 get_result() 可以实际更改数据,它不是常量。如果你想成为警察,不要打电话 run() 而是抛出一个例外。

    你应该使用 mutable 对于缓存数据,即不会更改实例状态并且仅出于性能原因存储的数据。

        6
  •  0
  •   dennycrane    15 年前

    如果我能进入那个位置,我可能会抛出一个例外。

    但是,你 可以 逃脱

    if (!m_completed) (const_cast <operate_on_data *> (this))->run();
    

    但是,如果 get_result 然后对的实例调用 operate_on_data 实际上被定义为 const 你进入拉拉土地。

        7
  •  0
  •   Max Lybbert    15 年前

    如果唯一的事情是 run() 对象中的更改是 m_completed 那就可以申报了 m_completed mutable () const . 如果 () 改变其他事情,然后打电话 get_result() 也会改变那些其他的东西,意义 获取结果() 绝对不应该 康斯特

    然而,为了进行讨论,您将注意到STL容器每个都有两个 begin() 功能和两个 end() 功能。一 开始() 还有一个 末端() 函数将返回可变迭代器,而另一个 开始() 末端() 函数将返回 const_iterator S.

    实际上有可能超载 () 用一个 康斯特 和非- 康斯特 版本。然后 获取结果() 会打电话给 康斯特 版本 () 因为它将被视为唯一的合法选择:

    class operate_on_data
    {
        std::vector<double> m_data;
        bool m_completed;
    public:
        operate_on_data(std::vector<double> data)
            : m_data(data), m_completed(false) { }
        void run()
        {
           //The algorithm goes here - it alters m_data.
           m_completed = true;
        }
    
        void run() const
        {
            // something that does not modify m_data or m_completed
        }
        std::vector<double> get_result() const
        {
            if (!m_completed)
                run();
            return m_data;
        }
    };
    

    然而,只有当 康斯特 版本 () 不会改变任何状态。否则,非- 康斯特 -的 () 会泄漏到 获取结果() 并使 康斯特 -的 获取结果() 公然的谎言


    我假设示例代码有点做作。如果不是,你基本上是在接受:

    std::vector<double> results = do_calculation(data);
    

    并将其包装在一个界面非常薄的对象中(即 get_results() 返回 std::vector<double> )在对象化版本中,我看不到什么改进。如果要缓存计算结果,通常只需保留 STD::向量&双; 在int周围,将创建并使用此对象的代码。

        8
  •  0
  •   gerardw    11 年前

    const的概念取决于类的实现;重要的是逻辑语义,而不是字段级/函数级的const。因此,如果需要从const调用非const方法,只需const cast。我更喜欢:

    void X::foo(  ) const {
          X & self = const_cast<X &>(*this);
          self.bar( ); //"bar" non-const function
    }