Как генерировать метод неконстанты из метода константы?

Бросок (а не выгода) является дорогим.

не вставляют блок выгоды, если Вы не собираетесь сделать что-то полезное (т.е. преобразовать в более полезное исключение, обработайте ошибку).

Просто перебросок исключения (оператор броска без аргумента) или, еще хуже, бросает тот же объект, как просто поймано определенно неправильная вещь.

РЕДАКТИРОВАНИЕ: избегать неоднозначности:

Перебросок:

catch (SomeException) {
  throw;
}

Создают исключение из предыдущего объекта исключения, где все время выполнения, обеспеченное состояние (особенно отслеживание стека), перезаписывается:

catch (SomeException e) {
  throw e;
}

последний случай является бессмысленным способом выбросить информацию об исключении. и ни с чем предшествующим броску в блоке выгоды вдвойне бессмысленно. Это может быть хуже:

catch (SomeException e) {
  throw new SomeException(e.Message);
}

, который теряет почти всю полезную информацию состояния e содержавший (включая то, что было первоначально брошено.)

9
задан Tobias 18 September 2010 в 18:16
поделиться

11 ответов

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

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

6
ответ дан 4 December 2019 в 14:29
поделиться

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


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; } 
};
4
ответ дан 4 December 2019 в 14:29
поделиться

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

class Bar;

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

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

  private:
    Bar* getBar() const {/* Actual code */ return NULL;}
};
3
ответ дан 4 December 2019 в 14:29
поделиться

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

2
ответ дан 4 December 2019 в 14:29
поделиться

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

1
ответ дан 4 December 2019 в 14:29
поделиться

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

1
ответ дан 4 December 2019 в 14:29
поделиться

Ответ я придумал сам, немного поразмыслив. Однако я думаю, что могу улучшить его, используя идеи из ответа 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); }
};
1
ответ дан 4 December 2019 в 14:29
поделиться

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

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

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

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

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

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

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

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

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

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

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

похоже, вы не продумали, что требует программа.

похоже, вы не продумали, что требует программа.

-1
ответ дан 4 December 2019 в 14:29
поделиться

Ваш код подразумевает, что const bar () фактически создает и возвращает новый Bar, и я нахожу это странным, если вы так много делаете.

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

Если ваш экземпляр Bar является переменной-членом, рассмотрите возможность возврата ссылки вместо этого.

-1
ответ дан 4 December 2019 в 14:29
поделиться

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( ... )
  • const_cast(this)
-2
ответ дан 4 December 2019 в 14:29
поделиться

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

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

Если версия с константой вызывает неконстантную, тогда вы не получите (2). Если неконстантная версия вызывает константную и const_cast результат, то вы не получите (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-корректность. Если код в методе тривиален, вы можете его продублировать. Если это нетривиально, то вам или разработчику кода нелегко убедиться, что const_cast действительно действителен.

например operator [] , который обращается к некоторому массиву, принадлежащему объекту, тогда я бы просто продублировал код. В какой-то момент вышеупомянутый шаблон функции требует меньше усилий по кодированию, чем дублирование, и в этот момент используйте шаблон.

Я думаю, что подход const_cast, хотя и умный и, казалось бы, стандартный, просто бесполезен, потому что он предпочитает краткость компилятору -проверено const-корректность. Если код в методе тривиален, вы можете его продублировать. Если это нетривиально, то вам или разработчику кода нелегко убедиться, что const_cast действительно действителен.

например operator [] , который обращается к некоторому массиву, принадлежащему объекту, тогда я бы просто продублировал код. В какой-то момент вышеупомянутый шаблон функции требует меньше усилий по кодированию, чем дублирование, и в этот момент используйте шаблон.

Я думаю, что подход const_cast, хотя и умный и, казалось бы, стандартный, просто бесполезен, потому что он предпочитает краткость компилятору -проверено const-корректность. Если код в методе тривиален, вы можете его продублировать. Если это нетривиально, то вам или разработчику кода нелегко убедиться, что const_cast действительно действителен.

в то время как умный и, казалось бы, стандартный, просто бесполезен, потому что он предпочитает краткость проверенной компилятором константной корректности. Если код в методе тривиален, вы можете его продублировать. Если это нетривиально, то вам или разработчику кода нелегко убедиться, что const_cast действительно действителен.

в то время как умный и, казалось бы, стандартный, просто бесполезен, потому что он предпочитает краткость проверенной компилятором константной корректности. Если код в методе тривиален, вы можете его продублировать. Если это нетривиально, то вам или разработчику кода нелегко убедиться, что const_cast действительно действителен.

0
ответ дан 4 December 2019 в 14:29
поделиться
Другие вопросы по тегам:

Похожие вопросы: