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

发布于 2024-08-02 21:35:15 字数 453 浏览 6 评论 0原文

编写了这样的代码

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

在努力实现 const 正确性的同时,我经常发现自己为许多方法(如 bar()) 。编写这些手动调用 const 方法的非常量方法是乏味的;此外,我觉得我在重复自己——这让我感觉很糟糕。

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

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

While striving for const-correctness, I often find myself writing code such as this

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

for lots of methods like bar(). Writing these non-const methods which call the const ones by hand is tedious; besides, I feel I am repeating myself – which makes me feel bad.

What can I do to alleviate this task? (Macros and code generators are not allowed.)

Edit: Besides litb's solution I also like my own. :)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

荒岛晴空 2024-08-09 21:35:15

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

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

Another way could be to write a template that calls the function (using CRTP) and inherit from it.

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

Note that the call has no performance lost: Since the member pointer is passed as a template parameter, the call can be inlined as usual.

简单爱 2024-08-09 21:35:15

使用以下技巧:


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 用于任何常量/非常量函数。

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

Use following trick:


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

Note it is possible to use unique function This for any const/non-const functions.

Alternative solution without static_cast (but I prefer the first one):

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; } 
};
归属感 2024-08-09 21:35:15

你可以这样做:

class Bar;

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

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

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

You can do something like this:

class Bar;

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

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

  private:
    Bar* getBar() const {/* Actual code */ return NULL;}
};
江南烟雨〆相思醉 2024-08-09 21:35:15

我个人的感觉是,如果你经常这样做,那么你的设计就有点可疑了。有时我不得不做类似的事情,我通常会让方法访问的东西可变。

My personal feeling is that if you are doing this a lot, there is something a bit suspect in your design. On the occasions I have had to do something similar, I've usually made the thing being accessed by the methods mutable.

荒芜了季节 2024-08-09 21:35:15

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

I've also felt this pain before -- in essence, you're trying to tell the compiler that constness "propagates" through bar(). Unfortunately, as far as I'm aware, there is no way to do this automatically... you'll just have to write the second version of the function by hand.

勿忘初心 2024-08-09 21:35:15

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

FYI - The code posted by the OP is the preferred method given in Scott Meyers' "Effective C++ - Third Edition". See Item #3.

唔猫 2024-08-09 21:35:15

这个答案是我经过一番思考后自己得出的。但是,我认为我可以使用 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); }
};

The answer I came up with myself after bending my mind a bit. However, I think I can improve it using the ideas from litb's answer, which I'll post later. So my solution so far looks like this:

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); }
};
情域 2024-08-09 21:35:15

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

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

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

如果它们都调用辅助成员函数 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[]< /code> 它访问对象拥有的一些数组,然后我只需复制代码。在某些时候,上面的函数模板比​​重复的编码工作量要少,此时就使用模板。

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

Assuming you accept const-correctness as a technique, then I think that means you prefer compiler-checked const-correctness to brevity. So you want the compiler to check two things:

  1. That when "this" is non-const, the referand of the pointer you return can safely be modified by the caller.
  2. That when "this" is const, whatever work you do does not perform any non-const operations on "this".

If the const version calls the non-const then you don't get (2). If the non-const version calls the const one and const_casts the result, then you don't get (1). For instance suppose Bar is actually char, and the code you write ends up returning (in some cases) a string literal. This will compile (and -Wwrite-strings gives no warning), but your caller ends up with a non-const pointer to a string literal. This contradicts "you prefer compiler-checked const-correctness".

If they both call a helper member function Bar *getBar() const, then you get both (1) and (2). But if it's possible to write that helper function, then why are you messing about with const and non-const versions in the first place, when it's perfectly OK to modify the Bar returned from a const Foo? Occasionally perhaps some detail of implementation means you're implementing an interface with two accessors even though you only need the one. Otherwise either the helper can't be written, or else the two functions can be replaced just by the single helper.

As long as code size is not a concern, I think the best way to achieve both (1) and (2) is to have the compiler actually consider both cases:

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

If the code is trivial, like an operator[] which accesses some array owned by the object, then I would just duplicate the code. At some point, the above function template is less coding effort than the duplication, at which point use the template.

I think the const_cast approach, while clever and seemingly standard, just isn't helpful, because it chooses brevity over compiler-checked const-correctness. If the code in the method is trivial, then you can duplicate it. If it's not trivial, then it's not easy for you or a code maintainer to see that the const_cast is actually valid.

北方的韩爷 2024-08-09 21:35:15

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

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

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

Your code implies that the const bar() is actually creating and returning a new Bar, and I find that peculiar if you're doing that lot.

For me a bigger problem is that constness in a member function is limited to reference and non-pointer members. The moment you have (non-const) pointer members, a const function can change them even though it claims to be const on the box.

If your Bar instance is a member variable, consider returning a reference instead.

哑剧 2024-08-09 21:35:15

Bar 的常量会影响 Foo 的常量吗?或者您这样做只是因为无法区分 const Bar* 与 Bar* 版本?如果是后一种情况,我只需一个返回 Bar* 的 bar() 即可完成它。毕竟,无论如何,接受 const Bar* 的东西也可以接受 Bar* 。不要忘记, bar() 方法的常量性是关于 Foo 的,而不是关于 Bar 的。

另外,如果您同时提供 const 和非 const Bar* 并且很高兴让 Foo 成为非 const,无论愿意与否,那么 Foo 的常量性(以及 bar() 方法)显然不是那么重要,您只需填写 const 和非 const 版本即可。在这种情况下,如果您允许非常量版本,再次只提供/仅/该版本,const 的消费者会很乐意接受非常量。只是,你在那里使用 const 和非常量的方式,看起来对程序没有太大影响。

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

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

但请仔细考虑一下,哪些常量是真正相关的,以及您的程序是否需要 const Foos 和非 const Foos。无论愿意与否,都允许所有可能性,看起来您还没有考虑清楚该程序需要什么。

Does the const-ness of Bar affect the const-ness of your Foo? Or are you just doing that because there's no way to distinguish the const Bar* vs Bar* versions? If this latter is the case, I'd just have one bar() that returned a Bar* and be done with it. Afterall, something that takes a const Bar* can take a Bar* just fine, anyway. Don't forget, the const-ness of the bar() methods is about Foo, not about Bar.

Also, if you offer both const- and non-const Bar* and are happy to have Foo be non-const, willy-nilly, then the constness of your Foo (and thus the bar() methods) obviously isn't that important, you're just filling in const and non-const versions for the heck of it. In which case, if you allow a non-const version, again just offer /only/ that one, consumers of const will happily take a non-const. Just, the way you've got const and non-const going there, it looks like it doesn't make much difference to the program.

If you want const Foo objects to be able to return both const and non-const Bars, then just make bar() const and return a non-const Bar.

I don't know, I'd have to see more to give a real answer.

Think through though, which const-nesses are truly connected, and whether your program needs both const Foos and non-const Foos. Allowing all possibilities willy-nilly, it seems like you haven't thought through what the program requires.

年少掌心 2024-08-09 21:35:15

一张图胜过一万字,所以:

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( ... )
  • const_cast(this)

One picture worth more than 10 thousand words, so:

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

Copied and altered from actual code. General idea is that you implement your const method and you write the non const method which uses the first. You need to cast the "this" pointer to "const" and you need to cast away the "const" from the returning "const Item*".

You can replace the C-style casts to C++ style casts

  • static_cast<Item*>( ... )
  • const_cast<const Storage*>(this)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文