繁体   English   中英

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

[英]How to generate a non-const method from a const method?

在努力保持正确性的同时,我经常发现自己正在编写这样的代码

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的解决方案,我也喜欢我自己的解决方案。 :)

另一种方法是编写一个调用函数的模板(使用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>(); }
};

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

使用以下技巧:


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

注意,可以使用独特的功能, This对于任何常量/非const功能。

没有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; } 
};

你可以这样做:

class Bar;

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

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

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

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

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

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

我弯下脑子后,自己想出了答案。 但是,我想我可以使用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); }
};

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

  1. 当“this”为非const时,调用者可以安全地修改返回指针的referand。
  2. 当“this”是const时,无论你做什么工作都不对“this”执行任何非const操作。

如果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实际上是有效的并不容易。

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

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

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

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

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

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

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

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

一张图片价值超过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 <Item *>(...)
  • const_cast <const Storage *>(this)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM