如何从const方法生成非const方法?

时间:2009-08-26 10:51:46

标签: c++ dry const-correctness

在努力实现const-correctness的同时,我经常发现自己编写的代码如此

class Bar;

class Foo {
public:
  const Bar* bar() const { /* code that gets a Bar somewhere */ }

  Bar* bar() {
    return const_cast< Bar* >(
      static_cast< const Foo* >(this)->bar());
  }
};

有很多方法,例如bar()。编写这些非const方法,手动调用常量方法是乏味的;此外,我觉得我在重复自己 - 这让我心疼。

我可以做些什么来缓解这项任务? (不允许使用宏和代码生成器。)

编辑:除了litb的解决方案,我也喜欢自己的解决方案。 :)

11 个答案:

答案 0 :(得分:6)

另一种方法是编写一个调用该函数的模板(使用CRTP)并从中继承。

template<typename D>
struct const_forward {
protected:
  // forbid deletion through a base-class ptr
  ~const_forward() { }

  template<typename R, R const*(D::*pf)()const>
  R *use_const() {
    return const_cast<R *>( (static_cast<D const*>(this)->*pf)() );
  }

  template<typename R, R const&(D::*pf)()const>
  R &use_const() {
    return const_cast<R &>( (static_cast<D const*>(this)->*pf)() );
  }
};

class Bar;

class Foo : public const_forward<Foo> {
public:
  const Bar* bar() const { /* code that gets a Bar somewhere */ }
  Bar* bar() { return use_const<Bar, &Foo::bar>(); }
};

请注意,调用没有丢失性能:由于成员指针作为模板参数传递,因此可以像往常一样内联调用。

答案 1 :(得分:4)

使用以下技巧:


class Bar;
class Foo {
public:  
  Bar* bar() { 
    // non-const case
    /* code that does something */ 
  }  
  const Bar* bar() const {    
      return This().bar();  // use non-const case
   }

private:
  //trick: const method returns non-const reference
  Foo & This() const { return const_cast<Foo &>(*this); } 
};

注意可以对任何const /非const函数使用唯一函数This

没有static_cast的替代解决方案(但我更喜欢第一个):

class Bar;
class Foo {
public:  
  const Bar* bar() const { /* code that does something */ }  
  Bar* bar() { return const_cast<Bar*>(cthis().bar()); } // use const case
private:
  const Foo & cthis() const { return *this; } 
};

答案 2 :(得分:3)

您可以这样做:

class Bar;

class Foo {
public:
  const Bar* bar() const { return getBar(); }

  Bar* bar() {
   return getBar();
  }

  private:
    Bar* getBar() const {/* Actual code */ return NULL;}
};

答案 3 :(得分:2)

我个人的感觉是,如果你这么做很多,你的设计中会有一些怀疑。在我不得不做类似事情的情况下,我通常会通过可变方法来访问这个东西。

答案 4 :(得分:1)

我之前也感受到了这种痛苦 - 本质上,你试图告诉编译器constness通过bar()“传播”。不幸的是,据我所知,没有办法自动执行此操作...您只需手动编写该函数的第二个版本。

答案 5 :(得分:1)

仅供参考 - OP发布的代码是Scott Meyers的“Effective C ++ - Third Edition”中给出的首选方法。见第3项。

答案 6 :(得分:1)

我弯下脑子后,自己想出了答案。但是,我想我可以使用litb的答案中的想法来改进它,我稍后会发布。所以我到目前为止的解决方案看起来像这样:

class ConstOverloadAdapter {
protected:

  // methods returning pointers 

  template< 
    typename R, 
    typename T >
  R* ConstOverload(
    const R* (T::*mf)(void) const) {
      return const_cast< R* >(
        (static_cast< const T* >(this)->*mf)());
    }

  // and similar templates for methods with parameters 
  // and/or returning references or void
};

class Bar;

class Foo : public ConstOverloadAdapter {
public:
  const Bar* bar() const { 
    /* implementation */ }

  Bar* bar(void* = 0) {  // the dummy void* is only needed for msvc
                         // since it cannot distinguish method overloads
                         // based on cv-type. Not needed for gcc or comeau.
    return ConstOverload(&Foo::bar); }
};

答案 7 :(得分:0)

假设你接受const-correctness作为一种技术,那么我认为这意味着你更喜欢编译器检查的const-correctness以简洁。所以你希望编译器检查两件事:

  1. 当“this”为非const时,调用者可以安全地修改返回指针的referand。
  2. 当“this”为const时,无论你做什么工作都不会对“this”执行任何非const操作。
  3. 如果const版本调用非const,则不会得到(2)。如果非const版本调用const one并且const_casts结果,那么你不会得到(1)。例如,假设Bar实际上是char,并且您编写的代码最终返回(在某些情况下)字符串文字。这将编译(并且-Wwrite-strings不会发出警告),但是调用者最终会得到一个指向字符串文字的非const指针。这与“您更喜欢编译器检查的const-correctness”相矛盾。

    如果他们都调用辅助成员函数Bar *getBar() const,那么你得到(1)和(2)。但是如果可以编写那个辅助函数,那么为什么你首先要搞乱const和非const版本,那么修改const Foo返回的Bar是完全可以的?有时候,实现的一些细节可能意味着你实现了一个带有两个访问器的接口,即使你只需要一个。否则无法写入帮助程序,或者只能由单个帮助程序替换这两个函数。

    只要代码大小不是问题,我认为实现(1)和(2)的最佳方法是让编译器实际考虑这两种情况:

    struct Bar { int a; };
    struct Foo {
              Bar *bar()       { return getBar<Bar>(this); }
        const Bar *bar() const { return getBar<const Bar>(this); }
    
        Bar *bar2() const { return getBar<Bar>(this); } // doesn't compile. Good.
        Bar *bar3() const { return getBar<const Bar>(this); } // likewise
    
        private:
        template <typename B, typename F>
        static B *getBar(F *self) {
            // non-trivial code, which can safely call other functions with 
            // const/non-const overloads, and we don't have to manually figure out
            // whether it's safe to const_cast the result.
            return &self->myBar;
        }
        Bar myBar;
    };
    

    如果代码是微不足道的,就像访问对象拥有的某个数组的operator[]那么我只会复制代码。在某些时候,上面的函数模板比​​复制更少编码工作,此时使用模板。

    我认为const_cast方法虽然聪明且看似标准,但是没有用,因为它选择了简洁性而不是编译器检查的const-correctness。如果方法中的代码很简单,那么您可以复制它。如果它不是微不足道的,那么你或代码维护者就不容易看到const_cast实际上是有效的。

答案 8 :(得分:-1)

你的代码暗示const bar()实际上正在创建并返回一个新的Bar,如果你正在那么多,我发现这很奇怪。

对我来说,更大的问题是成员函数中的常量仅限于引用和非指针成员。你有(非常量)指针成员的那一刻,const函数可以改变它们,即使它声称它是盒子上的常量。

如果您的Bar实例是成员变量,请考虑返回引用。

答案 9 :(得分:-1)

Bar的常量会影响你Foo的常数吗?或者你只是这样做,因为没有办法区分const Bar * vs Bar *版本?如果是后一种情况,我只需要一个bar()返回一个Bar *并完成它。无论如何,毕竟,需要一个const Bar *才能拿到Bar *就好了。别忘了,bar()方法的常量是关于Foo,而不是关于Bar。

另外,如果你同时提供const和非const Bar *并且很高兴让Foo成为非const,那么你的Foo(以及bar()方法)的常量显然不是'重要的是,你只需填写const和非const版本就可以了。在这种情况下,如果你允许非const版本,再次只提供/ only /那个,const的使用者将愉快地采用非const。只是,你有const和非const去那里的方式,看起来它对程序没有太大的影响。

如果你想让const Foo对象能够返回const和非const Bar ,那么只需要使用bar()const并返回一个非const bar

我不知道,我必须看到更多才能给出真正的答案。

仔细思考,哪些常量是真正连接的,以及你的程序是否同时需要const Foos和非const Foos。似乎你没有考虑到程序需要的所有可能性。

答案 10 :(得分:-2)

一张价值超过1万字的图片,所以:

const Item*
Storage::SearchItemBySomething( const Something &something ) const
{
    const Item* pData = NULL;

    // Algorythm for searching.

    return pData;
}

Item*
Storage::SearchItemBySomething( const Something &something )
{
    return (Item*) ((const Storage*)this)->_SearchItemBySomething(something);
}

从实际代码中复制并更改。一般的想法是你实现你的const方法,你编写使用第一个方法的非const方法。你需要将“this”指针强制转换为“const”,你需要从返回的“const Item *”中抛弃“const”。

您可以将C风格的强制转换替换为C ++样式转换

  • static_cast&lt; Item *&gt;(...)
  • const_cast&lt; const Storage *&gt;(this)