Как создать неконстантный метод из константного метода? - PullRequest
9 голосов
/ 26 августа 2009

Стремясь к правильности, я часто пишу такой код, как этот

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(). Написание этих неконстантных методов, которые вызывают константные методы вручную, утомительно; кроме того, я чувствую, что повторяюсь - что мне плохо.

Что я могу сделать, чтобы облегчить эту задачу? (Макросы и генераторы кода не допускаются.)

Редактировать: Помимо решения Литба мне также нравится мое собственное. :)

Ответы [ 11 ]

6 голосов
/ 27 августа 2009

Другим способом может быть написание шаблона, который вызывает функцию (используя 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>(); }
};

Обратите внимание, что при вызове производительность не теряется: поскольку указатель члена передается как параметр шаблона, вызов может быть встроен как обычно.

4 голосов
/ 26 августа 2009

Используйте следующий трюк:


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; } 
};
3 голосов
/ 26 августа 2009

Вы можете сделать что-то вроде этого:

class Bar;

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

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

  private:
    Bar* getBar() const {/* Actual code */ return NULL;}
};
2 голосов
/ 26 августа 2009

Мое личное ощущение, что если вы делаете это много, в вашем дизайне есть что-то немного подозрительное.В некоторых случаях мне приходилось делать что-то похожее, я обычно делал так, чтобы к ним обращались методы, изменяемые.

1 голос
/ 28 августа 2009

Ответ, который я придумал сам, немного согнувшись. Тем не менее, я думаю, что могу улучшить его, используя идеи из ответа Литба, которые я опубликую позже. Итак, мое решение пока выглядит так:

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); }
};
1 голос
/ 27 августа 2009

К вашему сведению - код, размещенный ФП, является предпочтительным методом, приведенным в «Эффективном C ++ - Третье издание» Скотта Мейерса. См. Пункт № 3.

1 голос
/ 26 августа 2009

Я также чувствовал эту боль раньше - по сути, вы пытаетесь сказать компилятору, что constness «распространяется» через bar(). К сожалению, насколько я знаю, нет способа сделать это автоматически ... вам просто нужно написать вторую версию функции вручную.

0 голосов
/ 26 августа 2009

Влияет ли постоянство Bar на постоянство вашего Foo? Или вы просто делаете это, потому что нет никакого способа отличить версии const Bar * от Bar *? Если это последний случай, я бы просто имел один bar (), который возвращал Bar *, и с этим покончено. В конце концов, то, что занимает постоянный бар *, может в любом случае взять бар * отлично. Не забывайте, что постоянство методов bar () связано с Foo, а не с Bar.

Кроме того, если вы предлагаете как const-, так и неконстантный Bar * и хотите, чтобы Foo был неконстантным, волей-неволей, то константа вашего Foo (и, следовательно, методов bar ()), очевидно, не Это важно, вы просто заполняете константные и неконстантные версии. В этом случае, если вы разрешите неконстантную версию, снова просто предложите / только / эту версию, потребители const с радостью примут неконстантную версию. Просто то, как вы получаете const и non-const, похоже, что это не имеет большого значения для программы.

Если вы хотите, чтобы объекты const Foo могли возвращать как const, так и неконстантные значения Bar s, просто сделайте bar () const и верните неконстантный Bar .

Не знаю, мне нужно увидеть больше, чтобы дать реальный ответ.

Подумайте, какие константы действительно связаны, и нужна ли вашей программе как const Foos, так и неконстантные Foos. Предоставляя все возможности волей-неволей, кажется, что вы не продумали, что требуется программе.

0 голосов
/ 26 августа 2009

Предполагая, что вы принимаете константность как технику, я думаю, это означает, что вы предпочитаете константность, проверенную компилятором, краткости. Итак, вы хотите, чтобы компилятор проверил две вещи:

  1. То, что "this" не является константным, вызывающая сторона может безопасно изменить референт возвращаемого вами указателя.
  2. То, что когда "this" является const, любая выполняемая вами работа не выполняет никаких неконстантных операций над "this".

Если константная версия вызывает неконстантную, то вы не получите (2). Если неконстантная версия вызывает константную и const_casts результат, то вы не получите (1). Например, предположим, что Bar на самом деле char, и код, который вы пишете, в конечном итоге возвращает (в некоторых случаях) строковый литерал. Это скомпилирует (а -Wwrite-strings не выдает предупреждения), но ваш вызывающий в итоге получит неконстантный указатель на строковый литерал. Это противоречит «вы предпочитаете проверенную компилятором константность».

Если они оба вызывают вспомогательную функцию-член Bar *getBar() const, тогда вы получаете оба (1) и (2). Но если возможно написать эту вспомогательную функцию, то почему вы вообще возитесь с константной и неконстантной версиями, когда вполне нормально модифицировать Bar, возвращаемый из const Foo? Иногда, возможно, некоторые детали реализации означают, что вы реализуете интерфейс с двумя средствами доступа, даже если вам нужен только один. В противном случае либо помощник не может быть записан, либо две функции могут быть заменены только одним помощником.

До тех пор, пока размер кода не имеет значения, я думаю, что лучший способ достичь и (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_cast действительно действителен.

0 голосов
/ 26 августа 2009

Одна картинка стоит более 10 тысяч слов, а значит:

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 и пишете неконстантный метод, который использует первый. Вам нужно навести указатель «this» на «const» и отбросить «const» из возвращаемого «const Item *».

Вы можете заменить приведение в стиле C на приведение в стиле C ++

  • static_cast (...)
  • const_cast (this)
...