10

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

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

4

11 回答 11

7

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

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

于 2009-08-26T21:39:39.933 回答
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); } 
};

请注意,可以This对任何 const/non-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; } 
};
于 2009-08-26T11:09:41.737 回答
3

你可以这样做:

class Bar;

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

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

  private:
    Bar* getBar() const {/* Actual code */ return NULL;}
};
于 2009-08-26T11:01:32.407 回答
2

我个人的感觉是,如果您经常这样做,那么您的设计中就会有些可疑之处。在我不得不做类似的事情的情况下,我通常使方法访问的东西是可变的。

于 2009-08-26T11:09:24.030 回答
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); }
};
于 2009-08-28T16:49:11.770 回答
1

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

于 2009-08-26T11:04:14.710 回答
1

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

于 2009-08-27T01:52:21.083 回答
0

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

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

如果 const 版本调用非常量,那么您不会得到 (2)。如果非常量版本调用 const 并且 const_casts 结果,那么您不会得到 (1)。例如假设Baris 实际上char,并且您编写的代码最终返回(在某些情况下)字符串文字。这将编译(并且 -Wwrite-strings 不会给出警告),但是您的调用者最终会得到一个指向字符串文字的非常量指针。这与“您更喜欢编译器检查的 const 正确性”相矛盾。

如果它们都调用辅助成员函数Bar *getBar() const,那么您将同时得到 (1) 和 (2)。但是,如果可以编写该辅助函数,那么当修改从 const Foo 返回的 Bar 完全可以时,为什么要首先搞乱 const 和非 const 版本?有时,实现的一些细节可能意味着您正在实现一个具有两个访问器的接口,即使您只需要一个访问器。否则,要么不能编写帮助器,要么只能用单个帮助器替换这两个函数。

只要代码大小不是问题,我认为实现(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 正确性。如果方法中的代码很简单,那么您可以复制它。如果它不是微不足道的,那么您或代码维护人员就不容易看到 const_cast 实际上是有效的。

于 2009-08-26T13:40:50.570 回答
-1

Bar 的 const-ness 会影响 Foo 的 const-ness 吗?还是您只是因为无法区分 const Bar* 与 Bar* 版本而这样做?如果是后者,我只需要一个返回 Bar* 的 bar() 并完成它。毕竟,无论如何,采用 ​​const Bar* 的东西可以采用 Bar* 就好了。不要忘记,bar() 方法的 const-ness 是关于 Foo 的,而不是关于 Bar 的。

此外,如果您同时提供 const 和非 const Bar* 并且很高兴 Foo 是非 const 的,那么您的 Foo 的 const (以及 bar() 方法)显然不是那么重要,你只是为了它而填写 const 和 non-const 版本。在这种情况下,如果您允许非 const 版本,再次只提供 /only/ 那个,const 的消费者会很乐意接受非常量。只是,你有 const 和 non-const 去那里的方式,看起来它对程序没有太大的影响。

如果您希望 const Foo 对象能够返回 const 和非 const Bar ,那么只需将 bar() 设为 const 并返回一个非 const Bar

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

不过仔细想想,哪些 const 是真正相关的,以及您的程序是否需要 const Foos 和非 const Foos。任性地允许所有可能性,您似乎还没有考虑过程序需要什么。

于 2009-08-26T16:38:25.393 回答
-1

您的代码暗示 const bar() 实际上是在创建并返回一个新 Bar,如果您这样做,我会发现这很奇怪。

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

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

于 2009-08-26T11:16:13.173 回答
-2

一张图片价值一万多字,所以:

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)
于 2009-08-26T11:26:10.237 回答