Допустим, у меня есть следующее, 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
имеют одинаковый код внутри фигурных скобок. Это дублирующий код и может вызвать проблемы с обслуживанием для длинных функций со сложной логикой .
Есть ли способ избежать этого дублирования кода?
Ответы:
Подробное объяснение см. В заголовке «Избегайте дублирования в функциях, не
const
являющихсяconst
членами», на с. 23, в пункте 3 «Использование поconst
возможности», в Effective C ++ , 3d ed . Scott Meyers, ISBN-13: 9780321334879.Вот решение Мейерса (упрощенно):
Два приведения и вызов функции могут быть уродливыми, но это правильно. Мейерс имеет полное объяснение, почему.
источник
get()const
возвращает что-то, что было определено как const-объект, тогда не должно быть неконстантной версииget()
вообще. На самом деле мое мышление об этом со временем изменилось: шаблонное решение - единственный способ избежать дублирования и получить проверенную компилятором константность, поэтому лично я бы больше не использовалconst_cast
, чтобы избежать дублирования кода, я бы выбрал установку дублированный код в шаблон функции или оставив его дублированным.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());
Да, можно избежать дублирования кода. Вам необходимо использовать функцию-член const, чтобы иметь логику и заставить неконстантную функцию-член вызывать функцию-член const и повторно приводить возвращаемое значение к неконстантной ссылке (или указателю, если функции возвращают указатель):
ПРИМЕЧАНИЕ. Важно, чтобы вы НЕ помещали логику в неконстантную функцию и чтобы константная функция вызывала неконстантную функцию - это может привести к неопределенному поведению. Причина в том, что экземпляр константного класса приводится как неконстантный экземпляр. Неконстантная функция-член может случайно изменить класс, что в стандартных состояниях C ++ приведет к неопределенному поведению.
источник
C ++ 17 обновил лучший ответ на этот вопрос:
Это имеет то преимущество, что:
volatile
только случайно, ноvolatile
это редкий квалификатор)Если вы хотите пройти полный путь вычета, это может быть достигнуто с помощью вспомогательной функции
Теперь вы даже не можете испортить
volatile
, и использование выглядитисточник
f()
возвращаетсяT
вместоT&
.f()
возвратаT
, мы не хотим иметь две перегрузки,const
одной версии достаточно.shared_ptr
. Так что на самом деле мне нужно было что-то вроде того,as_mutable_ptr
что выглядит почти так же, какas_mutable
указано выше, за исключением того, что оно принимает и возвращаетshared_ptr
и используетstd::const_pointer_cast
вместоconst_cast
.T const*
то это будетT const* const&&
скорее привязка, чем привязкаT const* const&
(по крайней мере, в моем тестировании это было сделано). Мне пришлось добавить перегрузку дляT const*
в качестве типа аргумента для методов, возвращающих указатель.Я думаю, что решение Скотта Мейерса может быть улучшено в C ++ 11 с помощью вспомогательной функции tempate. Это делает намерение намного более очевидным и может быть повторно использовано многими другими добытчиками.
Эта вспомогательная функция может быть использована следующим образом.
Первым аргументом всегда является указатель this. Второй - указатель на функцию-член для вызова. После этого можно передать произвольное количество дополнительных аргументов, чтобы они могли быть переданы в функцию. Это требует C ++ 11 из-за шаблонов с переменным числом аргументов.
источник
std::remove_bottom_const
идтиstd::remove_const
.const_cast
. Вы можете создатьgetElement
сам шаблон и использовать черту типа внутри дляmpl::conditional
типов, которые вам нужны, например,iterator
s илиconstiterator
s, если это необходимо. Реальная проблема заключается в том, как сгенерировать постоянную версию метода, когда эта часть сигнатуры не может быть шаблонизирована?std::remove_const<int const&>
естьint const &
(удалитьconst
квалификацию высшего уровня ), отсюда и гимнастикаNonConst<T>
в этом ответе. Предполагаемыйstd::remove_bottom_const
может удалитьconst
квалификацию нижнего уровня , и делать именно то,NonConst<T>
что здесь:std::remove_bottom_const<int const&>::type
=>int&
.getElement
перегружено. Тогда указатель на функцию не может быть разрешен без явного указания параметров шаблона. Зачем?likeConstVersion(TObj const* obj, TConstReturn (TObj::*memFun)(TArgs...) const, TArgs&&... args) { return const_cast<typename NonConst<TConstReturn>::type>((obj->*memFun)(std::forward<TArgs>(args)...)); }
Завершено: gist.github.com/BlueSolei/bca26a8590265492e2f2760d3cefcf83Немного более многословно, чем Мейерс, но я мог бы сделать это:
Закрытый метод имеет нежелательное свойство, что он возвращает неконстантный Z & для константного экземпляра, поэтому он является закрытым. Частные методы могут нарушать инварианты внешнего интерфейса (в этом случае желаемый инвариант - «объект const не может быть изменен через ссылки, полученные через него на объекты, которые он имеет -a»).
Обратите внимание, что комментарии являются частью шаблона. Интерфейс _getZ указывает, что его никогда нельзя назвать действительным (за исключением средств доступа, очевидно): в любом случае, это не представляется очевидным преимуществом, потому что вводить его будет еще 1 символ и не будет результат в меньшем или более быстром коде. Вызов метода эквивалентен вызову одного из методов доступа с const_cast, и вам бы этого тоже не хотелось. Если вы беспокоитесь о том, чтобы сделать ошибки очевидными (и это справедливая цель), тогда назовите это const_cast_getZ вместо _getZ.
Кстати, я ценю решение Мейерса. У меня нет философских возражений против этого. Лично я предпочитаю немного контролируемого повторения и частный метод, который должен вызываться только в определенных строго контролируемых обстоятельствах, а не метод, похожий на шум линии. Выберите свой яд и придерживайтесь его.
[Редактировать: Кевин справедливо указал, что _getZ, возможно, захочет вызвать еще один метод (скажем, generateZ), который специализируется на const так же, как getZ. В этом случае _getZ увидит const Z & и будет вынужден const_cast перед возвращением. Это все еще безопасно, так как шаблонный аксессуар контролирует все, но не совсем очевидно, что это безопасно. Кроме того, если вы сделаете это, а затем измените generateZ, чтобы он всегда возвращал const, вам также нужно изменить getZ, чтобы он всегда возвращал const, но компилятор не скажет вам, что вы делаете.
Это последнее замечание о компиляторе также верно для рекомендованного Мейерса шаблона, но первое замечание относительно неочевидного const_cast - нет. Таким образом, в итоге я думаю, что если _getZ окажется нужным const_cast для своего возвращаемого значения, то этот шаблон теряет большую часть своего значения по сравнению с Мейерсом. Поскольку он также страдает недостатками по сравнению с Мейерсом, я думаю, что я бы переключился на его в этой ситуации. Рефакторинг от одного к другому легко - он не влияет на любой другой допустимый код в классе, так как только неверный код и шаблон вызывают _getZ.]
источник
something
в_getZ()
функции есть переменная экземпляра? Компилятор (или, по крайней мере, некоторые компиляторы) будут жаловаться на то, что, поскольку он_getZ()
является константой, любая переменная экземпляра, на которую ссылается внутри, тоже является константой. Такsomething
что тогда будет const (это будет типconst Z&
) и не может быть преобразован вZ&
. По моему (по общему признанию, несколько ограниченному) опыту, большую часть времениsomething
является переменной экземпляра в подобных случаях.const_cast
. Предполагалось, что он будет заполнителем для кода, необходимого для получения неконстантного возврата от объекта const, а не как заполнитель для того, что было бы в дублированном геттере. Так что «что-то» - это не просто переменная экземпляра.Хороший вопрос и хорошие ответы. У меня есть другое решение, которое не использует приведения:
Однако у него есть уродство, требующее статического члена и необходимость использования
instance
переменной внутри него.Я не учел все возможные (негативные) последствия этого решения. Пожалуйста, дайте мне знать, если таковые имеются.
источник
auto get(std::size_t i) -> auto(const), auto(&&)
. Зачем '&&'? Ааа, так что я могу сказать:auto foo() -> auto(const), auto(&&) = delete;
this
ключевое слово. Я предлагаюtemplate< typename T > auto myfunction(T this, t args) -> decltype(ident)
ключевое слово this будет распознано как неявный аргумент экземпляра объекта и позволит компилятору распознать, что myfunction является членом илиT
.T
будет автоматически выведен на сайт вызова, который всегда будет типом класса, но с бесплатной квалификацией cv.const_cast
одним), позволяющее вернутьсяiterator
иconst_iterator
.static
можно сделать в области видимости файла вместо области видимости класса. :-)Вы также можете решить это с помощью шаблонов. Это решение немного уродливо (но уродство скрыто в файле .cpp), но оно обеспечивает проверку константности компилятором и не дублирует код.
.h файл:
Файл .cpp:
Основной недостаток, который я вижу, состоит в том, что, поскольку вся сложная реализация метода находится в глобальной функции, вам нужно либо овладеть членами X, используя открытые методы, такие как GetVector () выше (из которых всегда должен быть константная и неконстантная версия) или вы можете сделать эту функцию другом. Но я не люблю друзей.
[Редактировать: удалено ненужное включение cstdio, добавленное во время тестирования.]
источник
const_cast
(которое может случайно использоваться, чтобы заменить что-то, что на самом деле должно совпадать с чем-то, что не является).Как насчет того, чтобы переместить логику в закрытый метод и выполнять только «получить ссылку и вернуть» внутри методов получения? На самом деле, я был бы довольно смущен статическими и константными приведениями внутри простой функции получения, и я бы счел это уродливым, за исключением крайне редких обстоятельств!
источник
Это обманывает использовать препроцессор?
Это не так красиво, как шаблоны или приведение, но оно делает ваше намерение («эти две функции должны быть идентичными») довольно явным.
источник
Меня удивляет, что существует так много разных ответов, но почти все полагаются на тяжелую магию шаблонов. Шаблоны мощные, но иногда макросы бьют их кратко. Максимальная универсальность часто достигается сочетанием обоих.
Я написал макрос,
FROM_CONST_OVERLOAD()
который можно поместить в неконстантную функцию для вызова константной функции.Пример использования:
Простая и многоразовая реализация:
Объяснение:
Как написано во многих ответах, типичный шаблон, позволяющий избежать дублирования кода в неконстантной функции-члене, таков:
Этого можно избежать, используя вывод типа. Во-первых,
const_cast
может быть инкапсулировано вWithoutConst()
, что определяет тип его аргумента и удаляет спецификатор const. Во-вторых, аналогичный подход можно использоватьWithConst()
для константной квалификацииthis
указателя, что позволяет вызвать перегруженный константный метод.Остальное - это простой макрос, который ставит префикс перед правильно выбранными
this->
и удаляет const из результата. Поскольку выражение, используемое в макросе, почти всегда является простым вызовом функции с переадресованными аргументами 1: 1, недостатки макросов, такие как множественная оценка, не устраняются. Многоточие__VA_ARGS__
также может использоваться, но не должно быть необходимо, потому что запятые (как разделители аргументов) встречаются в скобках.Этот подход имеет несколько преимуществ:
FROM_CONST_OVERLOAD( )
const_iterator
,std::shared_ptr<const T>
и т. Д.). Для этого просто перегрузитеWithoutConst()
для соответствующих типов.Ограничения: это решение оптимизировано для сценариев, где неконстантная перегрузка выполняется точно так же, как и константная перегрузка, так что аргументы могут быть переданы 1: 1. Если ваша логика отличается и вы не вызываете версию const через
this->Method(args)
, вы можете рассмотреть другие подходы.источник
Для тех (как я), кто
вот еще один дубль:
В основном это смесь ответов @Pait, @DavidStone и @ sh1 ( РЕДАКТИРОВАТЬ : и улучшение от @cdhowie). То, что он добавляет в таблицу, - это то, что вы получаете только одну дополнительную строку кода, которая просто называет функцию (но без аргумента или дублирования возвращаемого типа):
Примечание: gcc не может скомпилировать это до 8.1, clang-5 и выше, а также MSVC-19 счастливы (согласно исследователю компилятора ).
источник
decltype()
s не должен также использоватьstd::forward
аргументы, чтобы убедиться, что мы используем правильный тип возвращаемого значения в случае, когда у нас есть перегрузки,get()
которые принимают разные типы ссылок?NON_CONST
макрос выводит тип возврата неправильно иconst_cast
ю к несоответствующей из - за отсутствия экспедирования вdecltype(func(a...))
типах. Замена ихdecltype(func(std::forward<T>(a)...))
решает это . (Есть только ошибка компоновщика, потому что я никогда не определял ни одну из объявленныхX::get
перегрузок.)Вот версия C ++ 17 статической вспомогательной функции шаблона с дополнительным тестом SFINAE.
Полная версия: https://godbolt.org/z/mMK4r3
источник
Я придумал макрос, который автоматически генерирует пары константных / неконстантных функций.
Смотрите конец ответа для реализации.
Аргумент
MAYBE_CONST
дублируется. В первом экземпляреCV
ничего не заменяется; и во втором экземпляре он заменен наconst
.Нет ограничений на количество раз, которое
CV
может появиться в аргументе макроса.Там есть небольшое неудобство, хотя. Если
CV
внутри скобок указано, эта пара скобок должна начинаться с префиксаCV_IN
:Реализация:
Реализация до C ++ 20, которая не поддерживает
CV_IN
:источник
Как правило, функции-члены, для которых вам нужны константные и неконстантные версии, являются геттерами и сеттерами. В большинстве случаев они являются однострочными, поэтому дублирование кода не является проблемой.
источник
Я сделал это для друга, который по праву оправдывал использование
const_cast
... не зная об этом, я, вероятно, сделал бы что-то вроде этого (не очень элегантно):источник
Я бы предложил шаблон статической функции частного помощника, например:
источник
Эта статья о DDJ показывает способ использования шаблонов, который не требует использования const_cast. Для такой простой функции она действительно не нужна.
boost :: any_cast (в какой-то момент он больше не использует) использует const_cast из const-версии, вызывая неконстантную версию, чтобы избежать дублирования. Вы не можете навязать семантику const для неконстантной версии, хотя вы должны быть очень осторожны с этим.
В конце концов, некоторое дублирование кода в порядке, если два фрагмента находятся прямо друг над другом.
источник
Чтобы добавить к решению jwfearn и kevin, вот соответствующее решение, когда функция возвращает shared_ptr:
источник
Не нашел то, что искал, поэтому я прокатил пару своих ...
Это немного многословно, но имеет преимущество обработки сразу нескольких перегруженных методов с одним и тем же именем (и типом возврата):
Если у вас есть только один
const
метод для каждого имени, но все еще много методов для дублирования, то вы можете предпочесть это:К сожалению, это выходит из строя, как только вы начинаете перегружать имя (список аргументов аргумента указателя функции кажется нерешенным в этот момент, поэтому он не может найти соответствие для аргумента функции). Хотя вы также можете выбрать выход из этого:
Но ссылочные аргументы к
const
методу не совпадают с внешне-значимыми аргументами шаблона, и он ломается.Не уверен почему.Вот почему .источник