Const C ++ DRY策略


14

为了避免与C ++ const无关的重复,在某些情况下const_cast可以工作,但返回非const的私有const函数不起作用?

在Scott Meyers的有效C ++项目3中,他建议将const_cast与静态强制转换结合使用可以是避免重复代码的有效且安全的方法,例如

const void* Bar::bar(int i) const
{
  ...
  return variableResultingFromNonTrivialDotDotDotCode;
}
void* Bar::bar(int i)
{
  return const_cast<void*>(static_cast<const Bar*>(this)->bar(i));
}

Meyers继续解释说,让const函数调用非const函数是危险的。

下面的代码是一个反例,显示:

  • 与Meyers的建议相反,有时将const_cast与静态强制转换结合使用是危险的
  • 有时让const函数调用非const的危险性较小
  • 有时两种使用const_cast的方式都可能隐藏有用的编译器错误
  • 避免const_cast并让其他const私有成员返回非const是另一种选择

避免代码重复的const_cast策略中的一种是否被视为良好实践?您愿意使用私有方法策略吗?在某些情况下,const_cast可以工作,但私有方法不能工作吗?还有其他选择(除了重复)吗?

我对const_cast策略的担心是,即使编写时代码正确,以后在维护期间代码也可能变得不正确,并且const_cast将隐藏有用的编译器错误。似乎普通的私有功能通常更安全。

class Foo
{
  public:
    Foo(const LongLived& constLongLived, LongLived& mutableLongLived)
    : mConstLongLived(constLongLived), mMutableLongLived(mutableLongLived)
    {}

    // case A: we shouldn't ever be allowed to return a non-const reference to something we only have a const reference to

    // const_cast prevents a useful compiler error
    const LongLived& GetA1() const { return mConstLongLived; }
    LongLived& GetA1()
    {
      return const_cast<LongLived&>( static_cast<const Foo*>(this)->GetA1() );
    }

    /* gives useful compiler error
    LongLived& GetA2()
    {
      return mConstLongLived; // error: invalid initialization of reference of type 'LongLived&' from expression of type 'const LongLived'
    }
    const LongLived& GetA2() const { return const_cast<Foo*>(this)->GetA2(); }
    */

    // case B: imagine we are using the convention that const means thread-safe, and we would prefer to re-calculate than lock the cache, then GetB0 might be correct:

    int GetB0(int i) { return mCache.Nth(i); }
    int GetB0(int i) const { return Fibonachi().Nth(i); }

    /* gives useful compiler error
    int GetB1(int i) const { return mCache.Nth(i); } // error: passing 'const Fibonachi' as 'this' argument of 'int Fibonachi::Nth(int)' discards qualifiers
    int GetB1(int i)
    {
      return static_cast<const Foo*>(this)->GetB1(i);
    }*/

    // const_cast prevents a useful compiler error
    int GetB2(int i) { return mCache.Nth(i); }
    int GetB2(int i) const { return const_cast<Foo*>(this)->GetB2(i); }

    // case C: calling a private const member that returns non-const seems like generally the way to go

    LongLived& GetC1() { return GetC1Private(); }
    const LongLived& GetC1() const { return GetC1Private(); }

  private:
    LongLived& GetC1Private() const { /* pretend a bunch of lines of code instead of just returning a single variable*/ return mMutableLongLived; }

    const LongLived& mConstLongLived;
    LongLived& mMutableLongLived;
    Fibonachi mCache;
};

class Fibonachi
{ 
    public:
      Fibonachi()
      {
        mCache.push_back(0);
        mCache.push_back(1);
      }

      int Nth(int n) 
      {
        for (int i=mCache.size(); i <= n; ++i)
        {
            mCache.push_back(mCache[i-1] + mCache[i-2]);
        }
        return mCache[n];
      }

      int Nth(int n) const
      {
          return n < mCache.size() ? mCache[n] : -1;
      }
    private:
      std::vector<int> mCache;
};

class LongLived {};

仅返回一个成员的getter比用于强制转换并调用其自身另一个版本的getter短。该技巧适用于更复杂的功能,在这些功能中,重复数据删除的收益超过了转换的风险。
塞巴斯蒂安·雷德尔

@SebastianRedl我同意,如果只是返回成员,重复会更好。请想象它更复杂,例如,代替返回mConstLongLived,我们可以在mConstLongLived上调用一个函数,该函数返回一个const引用,然后该函数用于调用另一个函数,该函数返回一个我们不拥有并且只能访问的const引用。的const版本。我希望重点很清楚,const_cast可以从我们原本无法通过非const访问的内容中删除const。
JDiMatteo

4
所有这些看起来都与简单的示例有关,这很荒谬,但是在实际代码中会出现与const相关的重复,在实践中const编译器错误非常有用(通常用于捕获愚蠢的错误),令我惊讶的是,提出的“有效C ++”解决方案很奇怪并且似乎容易出错。私有const成员返回非const似乎明显优于double强制转换,并且我想知道是否缺少某些内容。
JDiMatteo

Answers:


8

当实现仅根据返回的ptr / reference是否为const而有所不同的const和非const成员函数时,最佳的DRY策略是:

  1. 如果编写访问器,请考虑您是否真的需要访问器,请参阅cmaster的答案http://c2.com/cgi/wiki?
  2. 如果琐碎,只需复制代码(例如,返回一个成员)
  3. 永远不要使用const_cast来避免与const相关的重复
  4. 为了避免非平凡的重复,请使用私有const函数,该函数将返回const和non-const公共函数都调用的非const

例如

public:
  LongLived& GetC1() { return GetC1Private(); }
  const LongLived& GetC1() const { return GetC1Private(); }
private:
  LongLived& GetC1Private() const { /* non-trivial DRY logic here */ }

我们将此称为返回非常量模式私有const函数

这是避免直接重复的最佳策略,同时仍允许编译器执行潜在有用的检查并报告与const相关的错误消息。


您的论点颇具说服力,但令我感到困惑的是如何从const实例中获取对某些内容的非const引用(除非该引用涉及已声明的内容mutable,或者除非您使用a,const_cast但在两种情况下都没有开头)。我也找不到“返回非const模式的私有const函数”的任何内容(如果要开个玩笑称它为模式...。这不是很有趣;)
idclev 463035818

1
这是一个基于问题代码的编译示例:ideone.com/wBE1GB。抱歉,我并不是在开玩笑,而是在这里给它起个名字(在极少数情况下,它应有一个名字),我更新了答案中的措词,以使其更加清楚。自从我写这篇文章已有好几年了,我不记得为什么我认为在构造函数中传递引用的示例才有意义。
JDiMatteo

感谢您的示例,我现在没有时间,但是我一定会再来的。首先,这是一个提出相同方法的答案,并且在注释中指出了类似的问题:stackoverflow.com/a/124209/4117728
idclev 463035818 19/09/11

1

是的,您是对的:许多尝试const正确性的C ++程序都完全违反了DRY原理,即使返回非const的私有成员也有点过于舒适。

但是,您会错过一个观察结果:const正确性导致的代码重复仅在您向数据成员授予其他代码访问权限时才成为问题。这本身违反了封装。通常,这种代码重复大多发生在简单的访问器中(毕竟,您是将访问权移交给已经存在的成员,返回值通常不是计算的结果)。

我的经验是,好的抽象并不倾向于包含访问器。因此,我通过定义实际起作用的成员函数而不仅仅是提供对数据成员的访问,从而很大程度上避免了这个问题。我尝试对行为而不是数据进行建模。我的主要目的是实际上从我的类及其单个成员函数中获得一些抽象,而不是仅仅将我的对象用作数据容器。但是,这种样式在避免大量在大多数代码中如此常见的const / non-const重复单行访问器方面也很成功。


存取器是否良好似乎有待商debate,例如,请参阅c2.com/cgi/wiki?AccessorsAreEvil上的讨论。在实践中,不管您如何看待访问器,大型代码库都经常使用它们,如果确实使用它们,则最好遵循DRY原则。因此,我认为这个问题比您不应该提出的更多的答案。
JDiMatteo

1
这绝对是一个值得提出的问题:-)我什至不会否认您不时需要访问器。我只是说,不基于访问器的编程风格可以大大减少问题。它不能完全解决问题,但至少对我来说已经足够了。
cmaster-恢复莫妮卡2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.