【问题标题】:Const function calling non const or vice versa (to avoid duplication)? [duplicate]const 函数调用非 const 或反之亦然(避免重复)? [复制]
【发布时间】:2017-11-29 01:13:29
【问题描述】:

使用一个比另一个有什么优势:

class Foo
{
public:
    const int& get() const
    {
        // stuff here
        return myInt;
    }

    int& get()
    {
        return const_cast<int&>(static_cast<const Foo*>(this)->get());
    }
};

或者

class Foo
{
public:
    int& get()
    {
        // stuff here
        return myInt;
    }

    const int& get() const
    {
        return const_cast<Foo*>(this)->get();
    }
};

我只使用了第一个,但我看到第二个在某个地方使用过,所以我想知道。

评论// stuff here 可能是一项重要的检查,例如检索表的索引以返回表成员的引用(例如:myInt = myTable[myComputedIndex];),因此我不能将其公开。因此 table 和任何成员都不是 const。

【问题讨论】:

  • 为什么要在这种情况下使用吸气剂?改为公开myInt。
  • πάντα ῥεῖ 说的差不多。为了让自己成为一个笨重的可公开访问的成员,您会遇到很多麻烦和样板。
  • static_cast&lt;&gt; 也是多余的。
  • @Aconcagua 可以想象很多事情。我说的是问题中的示例。
  • 正确执行 getter/setter 函数的方法是不执行它们。

标签: c++ function constants


【解决方案1】:

你没有说myInt 来自哪里,最好的答案取决于那个。 有 2+1 种可能的情况:

1) 最常见的情况是myInt 来自类内部指针。

假设这是避免代码重复和强制转换的最佳解决方案。

class Foo{
    int* myIntP;
    ... 
    int& get_impl() const{
       ... lots of code
       return *myIntP; // even if Foo instance is const, *myInt is not
    }
public:
    int& get(){return get_impl();}
    const int& get() const{return get_impl();}
};

上面的这种情况适用于指针数组和(大多数)智能指针。

2) 另一种常见情况是myInt 是引用或值成员,那么前面的解决方案就行不通了。 但它也是根本不需要getter 的情况。 在这种情况下不要使用吸气剂。

class Foo{
     public:
     int myInt; // or int& myInt;
};

完成! :)

3) @Aconcagua 指出的第三种情况,即内部固定数组的情况。在那种情况下,这是一个折腾,这真的取决于你在做什么,如果找到索引真的是问题,那么可以将其排除在外。但目前尚不清楚应用程序是什么:

class Foo{
    int myInts[32];
    ... 
    int complicated_index() const{...long code...}
public:
    int& get(){return myInts[complicated_index()];}
    const int& get() const{return myInts[complicated_index()];}
};

我的意思是,了解问题,不要过度设计。 const_cast 或模板不需要解决这个问题。


完整的工作代码如下:

class Foo{
    int* myIntP;
    int& get_impl() const{
       return *myIntP; // even if Foo instance is const, *myInt is not
    }
public:
    int& get(){return get_impl();}
    const int& get() const{return get_impl();}

    Foo() : myIntP(new int(0)){}
    ~Foo(){delete myIntP;}
};

#include<cassert>

int main(){
    Foo f1; 
    f1.get() = 5;
    assert( f1.get() == 5 );

    Foo const f2;
//    f2.get() = 5; // compile error
    assert( f2.get() == 0 );    
    return 0;
}

【讨论】:

    【解决方案2】:

    如果您必须创建一个与 const 无关并避免重复的函数,那么一种巧妙的方法是将实现委托给模板,例如

    class Foo {
    private: 
    
        int my_int;
        template <typename ThisPtr>
        static auto& get(ThisPtr this_ptr) { 
            return this_ptr->my_int;
        }
    
    public:
        int& get() {
            return get(this);
        }
    
        const int& get() const {
            return get(this);
        }
    };
    

    这样你就不用担心在这种情况下使用const_cast、mutable 和其他试图减少代码重复的东西。如果出现错误,编译器会通知您。

    【讨论】:

    • 非常巧妙地使用模板参数推导。我仍然希望 C++ 能够避免这些重载导致代码重复。
    • @StoryTeller 可能类似于带有尾随&lt;const/mutable&gt; 的成员函数,它从使用上下文中推断出this ptr?
    • 我会使用它。如果只有委员会可以动摇接受它..
    • "通过 const_cast 将 const 对象转换为其非 const 版本是未定义的行为。" 这不是真的。试图修改这个非常量但实际上是常量的对象是 UB。
    • 是的,修改后的版本更好,谢谢。可惜你删除了它;)
    【解决方案3】:

    忽略是否真的需要 getter 的问题,在const 和非const 方法中复制功能时,最好的解决方案是让非const 方法调用const 方法和抛弃结果的const-ness(即您在问题中提出的两个备选方案中的第一个)。

    原因很简单:如果反其道而行之(使用非const 方法中的逻辑),您可能会意外修改const 对象,而编译器不会捕捉到它在编译时(因为该方法未声明 const) - 这将具有未定义的行为。

    当然,如果“getter”实际上不是一个 getter(即,如果它正在做一些比仅仅返回对私有字段的引用更复杂的事情),这只是一个问题。

    此外,如果您不受 C++11 的限制,Curious 在他们的回答中提出的基于模板的解决方案是避免此问题的另一种方法。

    【讨论】:

    • 所有都是很好的答案 - 那么为什么其他 2 个与此相同点的答案相比之下被大大低估了......?
    • @underscore_d 我不知道。冒险猜测:我认为其他一些答案已经过更彻底的编辑,所以它们最初可能不太清楚甚至不正确。 (在我写这个答案时,除了好奇之外,它是唯一一个根据实际提出的问题明确说明解决方案的答案)。
    • @underscore_d 好吧,我的回答从一开始就与这个问题相同-我从一开始就将问题解释为现在,但是人们似乎从某种意义上解释了这个问题πάντα ῥεῖ 起初因此不接受我的(同时OP编辑问题)......可惜人们从来没有注意到任何一个问题或答案已经改变,再次取消投票(更不用说转换为赞成票)。
    • @Aconcagua 再看一遍,我发现您的回答确实不错。我认为其中的代码示例让我失望,或者我最初没有看到它,不确定。我不知道为什么我的在你没有的地方得到了投票。给我一个赞成票。 :)
    • 我们经常希望从const 方法返回一个右值int,并从一个非const 方法返回一个引用int&amp;。在这种情况下,您不能将int 转换为int&amp;。这可能是不一致的根源。
    【解决方案4】:

    当您打算访问更复杂的内部结构时(通过您的编辑澄清;例如像 std::vector 那样为内部数组提供operator[](size_t index)),那么您必须确保不调用 undefined通过修改潜在的 const 对象来行为。

    第二种方法这样做的风险更高:

    int& get()
    {
        // stuff here: if you modify the object, the compiler won't warn you!
        // but you will modify a const object, if the other getter is called on one!!!
        return myInt;
    }
    

    在第一个变体中,您是安全的(除非您在这里也使用const_cast,这现在真的很糟糕......),这是这种方法的优势:

    const int& get() const
    {
        // stuff here: you cannot modify by accident...
        // if you try, the compiler will complain about
        return myInt;
    }
    

    如果你真的需要修改非常量getter中的对象,你无论如何都不能有一个通用的实现......

    【讨论】:

      【解决方案5】:

      使用一个比另一个有什么优势:...

      不,两者都不好,因为它们违反了数据封装原则。

      在您的示例中,您应该将myInt 设为公共成员。 在这种情况下使用吸气剂根本没有任何优势。

      如果你真的想要(需要)getter 和 setter 函数,它们应该如下所示:

      class Foo
      {
      private: 
          mutable int myInt_;
       // ^^^^^^^ Allows lazy initialization from within the const getter,
       //         simply omit that if you dont need it.
      
      public:
          void myInt(int value)
          {
              // Do other stuff ...
              myInt = value;
              // Do more stuff ...
          }
      
          const int& myInt() const
          {
              // Do other stuff ...
              return myInt_;
          }
      }
      

      【讨论】:

      • 添加mutable 的用法作为示例将涵盖大多数基础。
      • @aggsol mutable 与它有什么关系?正如我所提到的,按照 OP 所展示的方式进行操作,与拥有公共成员变量相比没有优势。
      • OP 的问题是注释// stuff here 是非常量的代码。她/他想要一个懒惰的评估获取者。
      • @WorldSEnder 这是支持始终编码 getter 的典型观点,但这种变化在现实中多久发生一次?如果大部分都不需要的话,它是否真的足够频繁来证明所有额外的样板类型是合理的?
      • @WorldSEnder 我完全同意 @underscore_d。我一直在使用很多 structs,其中不太可能需要 getter / setter 函数。尤其是诸如网络协议标头之类的东西。
      【解决方案6】:

      通过非 const 访问路径修改 const 对象 [...] 会导致未定义的行为。

      (来源:http://en.cppreference.com/w/cpp/language/const_cast)

      这意味着如果 myInt 实际上是 Foo 的 const 成员,第一个版本可能会导致未定义的行为:

      class Foo
      {
          int const myInt;
      public:
          const int& get() const
          {
              return myInt;
          }
          int& get()
          {
              return const_cast<int&>(static_cast<const Foo*>(this)->get());
          }
      };
      int main()
      {
          Foo f;
          f.get() = 10; // this compiles, but it is undefined behavior
      }
      

      第二个版本无法编译,因为get 的非常量版本格式不正确:

      class Foo
      {
          int const myInt;
      public:
          int& get()
          {
              return myInt;
              // this will not compile, you cannot return a const member
              // from a non-const member function
          }
          const int& get() const
          {
              return const_cast<Foo*>(this)->get();
          }
      };
      int main()
      {
          Foo f;
          f.get() = 10;  // get() is ill-formed, so this does not compile
      }
      

      这个版本实际上是 Scott Meyers 在Effective C++中Avoid Duplication in const and Non-const Member Function下推荐的。

      【讨论】:

      • 技术上正确 - 但从问题的性质来看,提供具有非常量参考的 getter,我认为假设 myInt 不是 const 更合适 - 并且在其他问题中存在 constness/UB 问题而不是关于const Foo f; const_cast&lt;Foo*&gt;(&amp;f)-&gt;get();。
      • @Aconcagua 是的,你是对的
      • 请注意,如果 myInt 不是 const,那么当非 const getter 修改某些状态时,第二个版本可能会在未来导致 UB,例如因为延迟初始化。
      猜你喜欢
      • 2012-01-28
      • 1970-01-01
      • 1970-01-01
      • 2011-01-10
      • 1970-01-01
      • 2021-07-07
      • 1970-01-01
      • 2020-02-02
      相关资源
      最近更新 更多