Как удалить дублирование кода между аналогичными функциями const и non-const?


170

Скажем, у меня есть следующие class X где я хочу, чтобы вернуть доступ к внутреннему члену:

class Z 
{ 
    // details 
}; 

class X 
{ 
    std::vector<Z> vecZ; 

public: 
    Z& Z(size_t index) 
    { 
     // massive amounts of code for validating index 

     Z& ret = vecZ[index]; 

     // even more code for determining that the Z instance 
     // at index is *exactly* the right sort of Z (a process 
     // which involves calculating leap years in which 
     // religious holidays fall on Tuesdays for 
     // the next thousand years or so) 

     return ret; 
    } 
    const Z& Z(size_t index) const 
    { 
     // identical to non-const X::Z(), except printed in 
     // a lighter shade of gray since 
     // we're running low on toner by this point 
    } 
}; 

две функции-члены X::Z() и X::Z() const имеют одинаковый код внутри скобок. Это дублированный код и может вызвать проблемы обслуживания для длинных функций со сложной логикой.

Есть ли способ избежать дублирования кода?

  0

В этом примере я бы вернул значение в случае const, так что вы не можете рефакторинг ниже. int Z() const {return z; } 23 сен. 082008-09-23 20:50:44

  0

Для фундаментальных типов вы абсолютно правы! Мой первый пример был не очень хорошим. Предположим, что вместо этого мы возвращаем экземпляр класса. (Я обновил вопрос, чтобы отразить это.) 23 сен. 082008-09-23 21:02:19

50

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

class X 
{ 
    std::vector<Z> vecZ; 

public: 
    const Z& Z(size_t index) const 
    { 
     // same really-really-really long access 
     // and checking code as in OP 
     // ... 
     return vecZ[index]; 
    } 

    Z& Z(size_t index) 
    { 
     // One line. One ugly, ugly line - but just one line! 
     return const_cast<Z&>(static_cast<const X&>(*this).Z(index)); 
    } 

#if 0 // A slightly less-ugly version 
    Z& Z(size_t index) 
    { 
     // Two lines -- one cast. This is slightly less ugly but takes an extra line. 
     const X& constMe = *this; 
     return const_cast<Z&>(constMe.Z(index)); 
    } 
#endif 
}; 

Примечание: важно, что вы делаете нЕ поставить логику в неконстантной функции и имеете сопзЬ-функция называть неконстантные функции - это может привести к непредсказуемому поведению. Причина в том, что экземпляр константного класса запускается как не постоянный экземпляр. Функция non-const member может случайно модифицировать класс, который соответствует стандартным состояниям C++, что приведет к неопределенному поведению.

+2

Ничего себе ... это ужасно. Вы просто увеличили количество кода, уменьшили ясность и добавили * two * stinkin 'const_cast <> s. Возможно, у вас есть пример, когда это действительно имеет смысл? 23 сен. 082008-09-23 20:50:53

  0

Я согласен с Шогом. Я предпочитаю оригинал! 23 сен. 082008-09-23 20:53:08

  0

Да, этот отвратительный зверь намного хуже, чем дублирование кода. 23 сен. 082008-09-23 20:55:48

  0

Хорошо, вы изменили его, чтобы работать над чем-то другим, кроме простого целого. «Дублирующий» код * еще * более понятен, чем не дублирующий код. 23 сен. 082008-09-23 20:59:51

+9

Эй, это не так, это может быть уродливо, но, по словам Скотта Мейерса, это (почти) правильный путь. См. _Effective C++ _, 3d ed, пункт 3 под заголовком «Избегание дублирования в константных и не входящих в стоимость функциях члена». 23 сен. 082008-09-23 21:06:15

+8

Хотя я понимаю, что решение может быть уродливым, представьте, что код, который определяет, что нужно вернуть, - это 50 строк долгое время, тогда дублирование крайне нежелательно - особенно когда вам приходится перегруппировать код. Я встречался много раз в своей карьере. 23 сен. 082008-09-23 21:06:53

  0

@jwfearn - Из любопытства, что неверно? И как Скотт Майерс и Скотт Майерс Я различаюсь? 23 сен. 082008-09-23 21:07:59

+2

Кевин - вам нужно хотя бы отредактировать # 6 - в уродливом наборе у вас есть «const_cast <int&>», который должен быть «const_cast <Z&>» 23 сен. 082008-09-23 21:59:06

+1

Есть. Спасибо, Майк! 23 сен. 082008-09-23 22:03:19

+7

Разница между этим и Meyers заключается в том, что у Meyers есть static_cast <const X&> (* this). const_cast предназначен для удаления const, а не добавления его. 23 сен. 082008-09-23 22:14:24

  0

Спасибо onebyone. Понял! 23 сен. 082008-09-23 22:24:54

  0

В соответствии со стандартом C++ недопустимо использовать 'const_cast' для удаления квалификатора' const' от объекта, который изначально был создан как 'const'. Насколько мне известно, этот код вызывает неопределенное поведение. 04 июл. 162016-07-04 11:11:57

+3

@ VioletGiraffe мы знаем, что объект изначально не был создан const, поскольку он является неконстантным членом не const-объекта, который мы знаем, потому что мы находимся в неконстантном методе указанного объекта. Компилятор не делает этого вывода, это следует за консервативным правилом. Почему вы думаете, что const_cast существует, если не для такой ситуации? 15 мар. 172017-03-15 12:14:17


-1

This DDJ article показывает способ использования специализированной специализации, которая не требует использования const_cast. Для такой простой функции это действительно не нужно.

boost :: any_cast (в какой-то момент это больше не используется) использует const_cast из версии const, вызывающей неконстантную версию, чтобы избежать дублирования. Вы не можете навязывать сонатную семантику неконстантной версии, хотя вам нужно быть очень осторожным с этим.

В конце концов, дублирование кода равно в порядке, если два фрагмента находятся непосредственно друг над другом.

  0

Статья DDJ, похоже, относится к итераторам - это не относится к вопросу. Const-iterators не являются постоянными данными - они являются итераторами, указывающими на постоянные данные. 23 сен. 082008-09-23 21:05:11


0

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

+2

Это может быть правда большую часть времени. Но есть исключения. 23 сен. 082008-09-23 21:14:11

+1

геттеры в любом случае, установщик констант не имеет большого смысла;) 23 сен. 082008-09-23 21:46:20

  0

Я имел в виду, что неконстантный геттер является эффективным сеттером. :) 23 сен. 082008-09-23 22:00:14


139

Для получения подробных разъяснений см. Заголовок «Избегайте дублирования в const и Non-const Функция участника» на стр. 23, в пункте 3 "Использование const всякий раз, когда это возможно," в Эффективное C++, 3D ред Скотт Мейерс, ISBN-13: 9780321334879.

alt text

Вот решение Мейерса (упрощенный):

struct C { 
    const char & get() const { 
    return c; 
    } 
    char & get() { 
    return const_cast<char &>(static_cast<const C &>(*this).get()); 
    } 
    char c; 
}; 

Два нажатия и вызов функции могут быть уродливыми, но это правильно. Мейерс объясняет, почему.

+27

Никто не был уволен за последующего Скотта Мейерса :-) 23 сен. 082008-09-23 22:15:10

  0

const_cast почти никогда не будет использоваться. Это нарушает консистенцию. Это особенно верно для внедренных систем, где у вас есть ПЗУ. В общем, неплохо использовать const_cast. 24 сен. 082008-09-24 00:03:13

+4

Возможно, у вас могут быть проблемы, если вы рассчитываете на const == ROM. Составители просто недостаточно хороши, чтобы доказать такое сильное утверждение. 24 сен. 082008-09-24 00:30:04

+7

witkamp прав, что в общем случае плохо использовать const_cast. Это конкретный случай, когда это не так, как объясняет Майерс. @Adam: ROM => const отлично. const == ROM - это, очевидно, бессмыслица, так как любой может использовать non-const для const willy-nilly: это эквивалентно простому выбору не изменять что-то. 24 сен. 082008-09-24 02:35:08

+28

В общем, я бы предложил использовать const_cast вместо static_cast, чтобы добавить const, так как он мешает вам случайно изменить тип. 23 ноя. 082008-11-23 07:49:39

+1

@witkamp Вам не хватает точки, есть два вида констант: физический и логический. 14 ноя. 122012-11-14 21:45:52

+2

Что делать, если 'const char & get() const' возвращает то, что является фактическим объявленным const? Тогда ваша неконвертная версия 'get' будет эффективно const_cast тем, что никогда не должно было быть доступно для записи. 28 мар. 132013-03-28 06:54:57

+6

@HelloGoodbye: Я думаю, что Мейерс предполагает * интеллектуальный интеллект от конструктора интерфейса класса. Если 'get() const' возвращает то, что было определено как объект const, то не должно быть неконстантной версии' get() 'вообще. На самом деле мое мышление об этом со временем изменилось: решение шаблона - единственный способ избежать дублирования * и * получить компилятор-проверенную const-correctness, поэтому лично я больше не буду использовать 'const_cast', чтобы избежать дублирования кода, я 'd выберите между помещением обдуманного кода в шаблон функции или оставив его обманутым. 05 апр. 132013-04-05 09:37:17

+6

Следующие два шаблона чрезвычайно помогают в удобочитаемости этого решения: 'template <typename T> const T & constant (T & _) {return const_cast <const T &>(_);}' и 'template <typename T> T & variable (const T & _) {return const_cast <T &>(_); } '. Тогда вы можете сделать: 'return variable (constant (* this) .get());' 21 июл. 142014-07-21 12:46:31

  0

Конечно, абсолютно равноправны, но я считаю этот вариант немного более эстетичным:' вернуть const_cast <char &> (const_cast <const C *> (это) -> получить()) ; ' 26 июн. 172017-06-26 14:43:16

  0

Не могу поверить, что Скотт сделал код следующим образом: 22 окт. 172017-10-22 09:23:15


16

Чуть более многословен, чем Мейерс, но я мог бы сделать это:

class X { 

    private: 

    // This method MUST NOT be called except from boilerplate accessors. 
    Z &_getZ(size_t index) const { 
     return something; 
    } 

    // boilerplate accessors 
    public: 
    Z &getZ(size_t index)    { return _getZ(index); } 
    const Z &getZ(size_t index) const { return _getZ(index); } 
}; 

Частным метод имеет нежелательное свойство, что она возвращает неконстантную Z & для константного экземпляра, поэтому это личное , Частные методы могут нарушать инварианты внешнего интерфейса (в этом случае желаемым инвариантом является «объект const не может быть изменен через ссылки, полученные через него, в объекты, которые он имеет - a»).

Обратите внимание, что комментарии являются частью шаблона. Интерфейс _getZ указывает, что он никогда не является действительным для его вызова (за исключением, очевидно, аксессуаров): в любом случае нет никакой возможной выгоды, поскольку это еще один символ для ввода и не приведет к меньшему или более быстрому коду. Вызов метода эквивалентен вызову одного из accessors с const_cast, и вы тоже не захотите этого делать. Если вы беспокоитесь о том, чтобы сделать ошибки очевидными (и это справедливая цель), тогда назовите ее const_cast_getZ вместо _getZ.

Кстати, я ценю решение Мейерса. У меня нет философских возражений против этого. Лично, однако, я предпочитаю крошечный бит контролируемого повторения и частный метод, который должен вызываться только в определенных жестко контролируемых обстоятельствах, по методу, который выглядит как линейный шум. Выберите свой яд и придерживайтесь его.

[Редактировать: Кевин справедливо указал, что _getZ может захотеть вызвать дополнительный метод (например, generateZ), который является const-специализированным таким же образом, как getZ is. В этом случае _getZ увидит const Z & и должен будет const_cast перед возвратом. Это все еще безопасно, так как аксессуар шаблонов все проверяет, но совершенно не очевидно, что это безопасно. Кроме того, если вы это сделаете, а затем измените generateZ, чтобы всегда возвращать const, вам также нужно изменить getZ, чтобы всегда возвращать const, но компилятор не скажет вам, что вы это делаете.

Этот последний пункт о компиляторе также относится к рекомендованному шаблону Мейерса, но первая точка о неочевидном const_cast - нет. Таким образом, на балансе я думаю, что если _getZ оказывается нуждающимся в const_cast для его возвращаемого значения, то этот шаблон теряет большую часть своей ценности над Meyers's. Так как он также имеет недостатки по сравнению с Мейерсом, я думаю, что переключись на него в этой ситуации. Рефакторинг от одного к другому легко - он не влияет на какой-либо другой допустимый код в классе, поскольку только неверный код и шаблонные вызовы _getZ.]

+1

У вас все еще есть проблема, что возвращаемая вами вещь может быть постоянной для постоянного экземпляра X. В этом случае вам все еще требуется const_cast в _getZ (...). Если это будет неправильно использовано более поздними разработчиками, это все равно может привести к UB. Если вещь, которая возвращается, является «изменчивой», то это хорошее решение. 24 сен. 082008-09-24 00:48:24

  0

Когда я сказал, что «_getZ (...)» может быть неправильно использован, я имел в виду, что если будущий разработчик не понял, что он должен использоваться только публичными функциями и вызвал его напрямую, но изменил значение в постоянном экземпляре X, то это может привести к UB. Это возможно, если не задокументировано. 24 сен. 082008-09-24 00:50:53

  0

Любые частные функции (heck, public тоже) могут быть неправильно использованы более поздними разработчиками, если они предпочитают игнорировать инструкции BLOCK CAPITAL по его действительному использованию, в файле заголовка, а также в Doxygen и т. Д. Я не могу остановить это , и я не считаю это своей проблемой, так как инструкции легко понять. 24 сен. 082008-09-24 01:44:46

+8

-1: Это не работает во многих ситуациях. Что, если '' '' '' _getZ() 'функция является переменной экземпляра? Компилятор (или, по крайней мере, некоторые компиляторы) будет жаловаться на то, что поскольку '_getZ()' const, любая переменная экземпляра, на которую ссылается внутри, тоже const. Поэтому 'something' будет const (он будет иметь тип' const Z & ') и не может быть преобразован в' Z & '. В моем (по общему признанию, немного ограниченном) опыте большая часть времени 'something' является переменной экземпляра в таких случаях. 04 авг. 112011-08-04 21:10:14

+2

@GravityBringer: тогда «что-то» нужно задействовать 'const_cast'. Он должен был быть владельцем места для кода, требуемого для получения неконстантного возврата из объекта const, а не в качестве владельца места для того, что * бы * было в дублированном геттере. Итак, «что-то» - это не просто переменная экземпляра. 05 авг. 112011-08-05 08:54:13

+2

Я вижу. Тем не менее, это действительно уменьшает полезность техники. Я бы удалил нижний план, но я не позволю. 06 авг. 112011-08-06 22:43:06

  0

Это эффективно достигает того же, что и Meyers, только потому, что ваш 'const_cast' скрыт в' something'. Если 'something' не должен был бы быть' const_caste'd, это было бы потому, что его нельзя было бы использовать для изменения объекта, на который был вызван метод, и тогда вам не понадобится какая-либо версия неконвертной версии метод. 04 апр. 132013-04-04 19:52:52

+1

@HelloGoodbye: это неверно. Неверно, что отдельные версии const и non-const нужны только в том случае, если требуется 'const_cast'. Например, 'something' может сводиться к' * some_pointer_data_member' (и предположим, что вы хотите вернуть ссылку const в случае const), и в этом случае код Мейерса вводит приведение, но на самом деле он не нужен. Его код обеспечивает совершенно общий шаблон, но наиболее актуальные функции, которые вы пишете, более конкретны :-) 05 апр. 132013-04-05 09:26:18

  0

Ну, я думаю, в этом случае вы могли бы изменить свой собственный экземпляр класса даже из метода const, который вызывается в экземпляре, по крайней мере, если член-указатель указывает на член в экземпляре или на сам экземпляр, поскольку компилятор не использует " t знать, что указывает указатель. Это до сих пор не ударило меня. 05 апр. 132013-04-05 13:24:11

  0

Это очень концептуальный ответ, это имеет большой смысл, когда возвращаемая ссылка является указателем класса. 19 окт. 152015-10-19 18:45:44

  0

@HelloGoodbye, где скрытый 'const_cast'? Если возвращаемая вещь не является константой, код совершенно корректен. Суть этого ответа в том, что он не использует 'const_cast' неявный или явный. Он достигает эффекта, просто нарушая обычную конвенцию, но делает это в своей частной реализации. 19 окт. 152015-10-19 18:50:49


3

Как насчет перемещения логики в частный метод и только делать «получать ссылку и возвращать» вещи внутри геттеров? На самом деле, я был бы довольно смущен статическими и const-элементами внутри простой функции getter, и я бы счел это уродливым, за исключением крайне редких обстоятельств!

  0

Чтобы избежать неопределенного поведения, вам все равно нужен const_cast. См. Ответ Мартина Йорка и мой комментарий там. 23 сен. 082008-09-23 22:28:05

+1

Кевин, какой ответ Мартин Йорк 03 мар. 112011-03-03 18:46:57


6

Вы также можете решить это с помощью шаблонов. Это решение немного уродливое (но уродство скрыто в файле .cpp), но оно обеспечивает проверку компилятором константы и отсутствие дублирования кода.

.h файл:

#include <vector> 

class Z 
{ 
    // details 
}; 

class X 
{ 
    std::vector<Z> vecZ; 

public: 
    const std::vector<Z>& GetVector() const { return vecZ; } 
    std::vector<Z>& GetVector() { return vecZ; } 

    Z& GetZ(size_t index); 
    const Z& GetZ(size_t index) const; 
}; 

.cpp файл:

#include "constnonconst.h" 

template< class ParentPtr, class Child > 
Child& GetZImpl(ParentPtr parent, size_t index) 
{ 
    // ... massive amounts of code ... 

    // Note you may only use methods of X here that are 
    // available in both const and non-const varieties. 

    Child& ret = parent->GetVector()[index]; 

    // ... even more code ... 

    return ret; 
} 

Z& X::GetZ(size_t index) 
{ 
    return GetZImpl< X*, Z >(this, index); 
} 

const Z& X::GetZ(size_t index) const 
{ 
    return GetZImpl< const X*, const Z >(this, index); 
} 

Основной недостаток я вижу в том, что, поскольку все комплексная реализация метода в глобальной функции, вы либо необходимо получить элементы X, используя общедоступные методы, такие как GetVector() выше (из которых всегда должна быть константа и неконстантная версия), или вы можете сделать эту функцию другом. Но мне не нравятся друзья.

[Edit: Убраны включают в cstdio добавленным во время тестирования.]

+3

Вы всегда можете сделать сложную функцию реализации статическим членом, чтобы получить доступ к частным членам. Функция должна быть объявлена ​​только в заголовочном файле класса, определение может находиться в файле реализации класса. Это, в конце концов, часть реализации класса. 13 янв. 092009-01-13 17:49:34

  0

Да, хорошая идея! Мне не нравятся элементы шаблона, появляющиеся в заголовке, но если с этого момента он потенциально делает реализацию довольно простой, то, вероятно, стоит того. 14 янв. 092009-01-14 09:15:11

  0

+ 1 к этому решению, которое не дублирует никакого кода и не использует какой-либо уродливый 'const_cast' (который случайно может быть использован для создания чего-то, что _actually_ должно быть const, для чего-то, что не является). 04 апр. 132013-04-04 20:01:06


27

Я думаю, что решение Скотт Мейерс может быть улучшена в C++ 11, используя tempate вспомогательную функцию. Это делает намерение более очевидным и может использоваться повторно для многих других геттеров.

template <typename T> 
struct NonConst {typedef T type;}; 
template <typename T> 
struct NonConst<T const> {typedef T type;}; //by value 
template <typename T> 
struct NonConst<T const&> {typedef T& type;}; //by reference 
template <typename T> 
struct NonConst<T const*> {typedef T* type;}; //by pointer 
template <typename T> 
struct NonConst<T const&&> {typedef T&& type;}; //by rvalue-reference 

template<typename TConstReturn, class TObj, typename... TArgs> 
typename NonConst<TConstReturn>::type likeConstVersion(
    TObj const* obj, 
    TConstReturn (TObj::* memFun)(TArgs...) const, 
    TArgs&&... args) { 
     return const_cast<typename NonConst<TConstReturn>::type>(
     (obj->*memFun)(std::forward<TArgs>(args)...)); 
} 

Эта вспомогательная функция может использоваться следующим образом.

struct T { 
    int arr[100]; 

    int const& getElement(size_t i) const{ 
     return arr[i]; 
    } 

    int& getElement(size_t i) { 
     return likeConstVersion(this, &T::getElement, i); 
    } 
}; 

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

+1

Жаль, что у нас нет 'std :: remove_bottom_const', чтобы перейти с' std :: remove_const'. 04 фев. 162016-02-04 03:58:38

  0

@TBBle как так? http://www.cplusplus.com/reference/type_traits/remove_const/ 04 мар. 162016-03-04 03:51:38

  0

Мне не нравится это решение, потому что оно все еще содержит 'const_cast'. Вы можете сделать 'getElement' сам шаблон и использовать черту типа внутри для' mpl :: условных' типов, которые вам нужны, например, 'iterator' или 'constiterator', если это необходимо. Реальная проблема заключается в том, как сгенерировать версию const, когда эта часть подписи не может быть подвергнута шаблонизации? 04 мар. 162016-03-04 03:54:56

+2

@ v.oddou: 'std :: remove_const <int const&>' is 'int const &' (удалить верхнюю квалификацию 'const'), отсюда и гимнастика' NonConst <T> 'в этом ответе. Предполагаемый 'std :: remove_bottom_const' может удалить квалификацию' const' нижнего уровня и делать то, что 'NonConst <T>' здесь: 'std :: remove_bottom_const <int const&> :: type' =>' int & '. 04 мар. 162016-03-04 08:22:13

  0

@ v.oddou: Я думаю, вы не можете обойти две отдельные декларации по той причине, о которой вы говорили. 04 мар. 162016-03-04 09:51:50

  0

И никто не должен использовать это решение в программном обеспечении для производства. Это слишком «сложно». Либо дублируйте свой код, если он достаточно прост, либо воспользуйтесь решением Скотта Мейера, потому что это уродливая, но хорошо известная идиома. 04 мар. 162016-03-04 09:59:01

+3

Это решение не работает, если 'getElement' перегружен. Тогда указатель функции не может быть разрешен без явного указания параметров шаблона. Зачем? 20 июн. 162016-06-20 14:09:04

  0

Я согласен с Джоном.Чтобы поддержать, вот тест: http://ideone.com/fwqmsB. В настоящее время он компилируется, но если uncomment/**/(функция перегрузки), он больше не будет компилироваться. Было бы неплохо, если бы это решение было улучшено немного дальше. 22 мар. 172017-03-22 05:51:19

  0

@John Это проблема с разрешением перегрузки C++ для указателей на функции-члены (см. Также http://stackoverflow.com/questions/2942426/how-do-i-pecpeci-a-pointer-to-an-overloaded-function) , В настоящее время это может быть разрешено только на стороне вызывающего абонента. Однако вам не нужно указывать все параметры шаблона. Работает только первый «TConstReturn». Например: 'return likeConstVersion <int const&> (this, & T :: getElement, i);' 11 апр. 172017-04-11 10:42:13

  0

Вам нужно исправить ответ, чтобы использовать совершенную пересылку C++ 11: 'likeConstVersion (TObj const * obj, TConstReturn (TObj :: * memFun) (TArgs ...) const, TArgs && ... args) { return const_cast <typename NonConst <TConstReturn> :: type> ((obj -> * memFun) (std :: forward <TArgs> (args) ...)); } ' Complete: https://gist.github.com/BlueSolei/bca26a8590265492e2f2760d3cefcf83 18 май. 172017-05-18 17:34:44


13

Хороший вопрос и приятные ответы. У меня есть еще одно решение, что не использует слепков:

class X { 

private: 

    std::vector<Z> v; 

    template<typename InstanceType> 
    static auto get(InstanceType& instance, std::size_t i) -> decltype(instance.get(i)) { 
     // massive amounts of code for validating index 
     // the instance variable has to be used to access class members 
     return instance.v[i]; 
    } 

public: 

    const Z& get(std::size_t i) const { 
     return get(*this, i); 
    } 

    Z& get(std::size_t i) { 
     return get(*this, i); 
    } 

}; 

Однако он имеет уродство требует статического члена и необходимости использования переменной instance внутри него.

Я не рассматривал все возможные (отрицательные) последствия этого решения. Пожалуйста, дайте мне знать, если они есть.

+3

Ну, давайте рассмотрим тот простой факт, что вы добавили больше шаблонов. Во всяком случае, это должно использоваться в качестве примера того, почему языку нужен способ изменить квалификаторы функций вместе с возвращаемым типом 'auto get (std :: size_t i) -> auto (const), auto (&&) '. Зачем '&&'? Ahh, так что я могу сказать: 'auto foo() -> auto (const), auto (&&) = delete;' 27 апр. 152015-04-27 23:40:59

  0

gd1: именно то, что я имел в виду. @kfsone и именно то, что я тоже сделал. 04 мар. 162016-03-04 03:57:56

+1

@kfsone синтаксис должен включать ключевое слово this. Я предлагаю 'template <typename T> auto myfunction (T this, t args) -> decltype (ident)' Это ключевое слово будет распознано как неявный аргумент экземпляра объекта и пусть компилятор распознает, что myfunction является членом или 'T' , 'T' будет автоматически выводиться на сайт вызова, который всегда будет типом класса, но со свободной квалификацией cv. 04 мар. 162016-03-04 03:58:55

  0

Это решение соответствует стандарту http://stackoverflow.com/a/38751554/259543. То есть, статическая функция-член для интерполяции на классификаторах '* this', используя универсальные ссылки. 10 авг. 162016-08-10 22:24:10

  0

Это решение имеет также преимущество (по сравнению с' const_cast'), позволяющее возвращать 'iterator' и' const_iterator'. 08 авг. 172017-08-08 11:47:30

  0

Если реализация перемещается в файл cpp (и поскольку метод, который не дублируется, не должен быть тривиальным, это, вероятно, будет иметь место), 'static' может выполняться в области файлов, а не в классе. :-) 08 авг. 172017-08-08 11:51:45

  0

@ Jarod42 mmm частных членов? 08 авг. 172017-08-08 11:53:01

  0

@ gd1: Вы все равно можете передать их в дополнение/замену на '* this', если это необходимо. 08 авг. 172017-08-08 11:58:20

  0

@ Jarod42 немного уродливый 08 авг. 172017-08-08 12:00:16

  0

'return get_impl (v, i);' vs 'return get_impl (* this, i);' is not IMO, но 'return get_impl (* this, v, member1, member2, i); 'будет, и в этом случае статический член шаблона все еще доступен (и может быть реализован в файл cpp (с соответствующими геттерами), как используется только в 2-х местах). 08 авг. 172017-08-08 12:07:02


0

Чтобы добавить к jwfearn раствора и Кевином при условии, вот соответствующее решение, когда функция возвращает shared_ptr:

struct C { 
    shared_ptr<const char> get() const { 
    return c; 
    } 
    shared_ptr<char> get() { 
    return const_pointer_cast<char>(static_cast<const C &>(*this).get()); 
    } 
    shared_ptr<char> c; 
}; 

1

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

#include <iostream> 

class MyClass 
{ 

public: 

    int getI() 
    { 
     std::cout << "non-const getter" << std::endl; 
     return privateGetI<MyClass, int>(*this); 
    } 

    const int getI() const 
    { 
     std::cout << "const getter" << std::endl; 
     return privateGetI<const MyClass, const int>(*this); 
    } 

private: 

    template <class C, typename T> 
    static T privateGetI(C c) 
    { 
     //do my stuff 
     return c._i; 
    } 

    int _i; 
}; 

int main() 
{ 
    const MyClass myConstClass = MyClass(); 
    myConstClass.getI(); 

    MyClass myNonConstClass; 
    myNonConstClass.getI(); 

    return 0; 
} 

0

Я предлагаю шаблон статической функции частного помощника, как это:

class X 
{ 
    std::vector<Z> vecZ; 

    // ReturnType is explicitly 'Z&' or 'const Z&' 
    // ThisType is deduced to be 'X' or 'const X' 
    template <typename ReturnType, typename ThisType> 
    static ReturnType Z_impl(ThisType& self, size_t index) 
    { 
     // massive amounts of code for validating index 
     ReturnType ret = self.vecZ[index]; 
     // even more code for determining, blah, blah... 
     return ret; 
    } 

public: 
    Z& Z(size_t index) 
    { 
     return Z_impl<Z&>(*this, index); 
    } 
    const Z& Z(size_t index) const 
    { 
     return Z_impl<const Z&>(*this, index); 
    } 
}; 
  0

darn! тот же ответ, что и gd1, я отвечу на его ответ, поскольку я считаю, что это правильный способ сделать это. 07 окт. 152015-10-07 14:26:09


0

Не нашли то, что я искал, так что я катал пару моих собственных ...

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

struct C { 
    int x[10]; 

    int const* getp() const { return x; } 
    int const* getp(int i) const { return &x[i]; } 
    int const* getp(int* p) const { return &x[*p]; } 

    int const& getr() const { return x[0]; } 
    int const& getr(int i) const { return x[i]; } 
    int const& getr(int* p) const { return x[*p]; } 

    template<typename... Ts> 
    auto* getp(Ts... args) { 
    auto const* p = this; 
    return const_cast<int*>(p->getp(args...)); 
    } 

    template<typename... Ts> 
    auto& getr(Ts... args) { 
    auto const* p = this; 
    return const_cast<int&>(p->getr(args...)); 
    } 
}; 

Если у вас есть только один метод const одного имени, но все еще много методов дублируют, то вы можете предпочесть это:

template<typename T, typename... Ts> 
    auto* pwrap(T const* (C::*f)(Ts...) const, Ts... args) { 
    return const_cast<T*>((this->*f)(args...)); 
    } 

    int* getp_i(int i) { return pwrap(&C::getp_i, i); } 
    int* getp_p(int* p) { return pwrap(&C::getp_p, p); } 

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

template<typename... Ts> 
    auto* getp(Ts... args) { return pwrap<int, Ts...>(&C::getp, args...); } 

Но ссылочные аргументы методы const не совпасть против явно по значению аргументов шаблона, и он рвется. Не знаете, почему.Here's why.


2

Можете ли вы использовать препроцессор?

struct A { 

    #define GETTER_CORE_CODE  \ 
    /* line 1 of getter code */ \ 
    /* line 2 of getter code */ \ 
    /* .....etc............. */ \ 
    /* line n of getter code */  

    //^NOTE: line continuation char '\' on all lines but the last 

    B& get() { 
     GETTER_CORE_CODE 
    } 

    const B& get() const { 
     GETTER_CORE_CODE 
    } 

    #undef GETTER_CORE_CODE 

}; 

Это не фантазии, как шаблоны или забросов, но он делает свое намерение («эти две функции должны быть идентичными») довольно четко.

  0

Но тогда вы должны быть осторожны с обратными косыми чертами (как обычно, для многострочных макросов), и, кроме того, вы теряете подсветку синтаксиса в большинстве (если не во всех) редакторах. 23 янв. 172017-01-23 14:01:40


3

C++ 17 обновил лучший ответ на этот вопрос:

T const & f() const { 
    return something_complicated(); 
} 
T & f() { 
    return const_cast<T &>(std::as_const(*this).f()); 
} 

Это имеет то преимущество, что она:

  • Очевидна, что происходит
  • имеет минимальные накладные расходы кода - он вписывается в одну линию
  • Трудно ошибиться (можно только отбросить volatile случайно, но volatile является редким qu alifier) ​​
  0

Должно быть ['std :: as_const'] (http://en.cppreference.com/w/cpp/utility/as_const) вместо [' std: : add_const'] (http://en.cppreference.com/w/cpp/types/add_cv). 18 янв. 182018-01-18 09:13:00

  0

@ s3rvac: Спасибо, исправлено 19 янв. 182018-01-19 15:26:56