#c #templates #boost #c 11 #generic-programming
#c #шаблоны #повышение #c 11 #generic-программирование
Вопрос:
Это мой вопрос. Мне просто любопытно, каков консенсус по ограничению типов, которые могут быть переданы в универсальную функцию или класс. Мне показалось, что я где-то читал, что если вы занимаетесь универсальным программированием, обычно лучше оставлять вещи открытыми, а не пытаться их закрыть (не помню источник).
Я пишу библиотеку, которая имеет некоторые внутренние общие функции, и я чувствую, что они должны разрешать использовать с ними только типы внутри библиотеки, просто потому, что я имею в виду, что именно так они должны использоваться. С другой стороны, я не совсем уверен, что мои усилия по блокировке того стоят.
Может быть, у кого-нибудь есть какие-нибудь источники статистики или авторитетные комментарии по этой теме? Меня также интересуют обоснованные мнения. Надеюсь, это не отменяет этот вопрос в целом :
Кроме того, есть ли здесь какие-либо теги на SO, которые соответствуют «наилучшей практике»? Я не видел этого конкретно, но, похоже, было бы полезно иметь возможность отображать всю информацию о наилучшей практике для данной темы SO … возможно, нет, просто мысль.
Редактировать: В одном из ответов до сих пор упоминалось, что тип библиотеки, которую я делаю, был бы значительным. Это библиотека базы данных, которая в конечном итоге работает с контейнерами STL, переменными (tuple), ускоряющим объединением и тому подобными вещами. Я понимаю, насколько это было бы актуально, но меня также интересовали бы эмпирические правила для определения того, каким путем идти.
Комментарии:
1. «если вы занимаетесь универсальным программированием, обычно было лучше оставить вещи открытыми, а не пытаться их закрыть»: это очень спорно. Но поскольку разработчики C решили, что концепции не будут частью языка в ближайшие несколько лет (десятилетий ?), намного удобнее оставлять вещи открытыми, чем удаленно пытаться их закрыть.
2. @Alexandre Мне было интересно, когда будут упомянуты концепции: (
Ответ №1:
Всегда оставляйте его как можно более открытым, но обязательно
- задокументируйте требуемый интерфейс и поведение допустимых типов для использования с вашим универсальным кодом.
- используйте характеристики интерфейса типа (черты), чтобы определить, разрешать или запрещать его. Не основывайте свое решение на имени типа.
- поставьте разумный диагноз, если кто-то использует неправильный тип. Шаблоны C отлично подходят для создания множества глубоко вложенных ошибок, если они создаются с неправильными типами — используя признаки типа, статические утверждения и связанные с ними методы, можно легко создавать более краткие сообщения об ошибках.
Комментарии:
1. Отличный ответ, но последний пункт не всегда тривиален для реализации.
2. @James: с C 0x
static_assert
это намного проще.3. вы также можете использовать,
BOOST_MPL_ASSERT_MSG
если вы не хотите полагаться наstatic_assert
. Кроме того, следующая ссылка содержит несколько советов по уменьшению количества ошибок, которые может вызвать неправильный тип. cpp-next.com/archive/2010/09 /…
Ответ №2:
В моей платформе database Framework я решил отказаться от шаблонов и использовать один базовый класс. Универсальное программирование означало, что можно использовать любой или все объекты. Конкретные классы типов перевешивали несколько общих операций. Например, строки и числа можно сравнивать на предмет равенства; большие двоичные объекты) могут захотеть использовать другой метод (например, сравнение контрольных сумм MD5, хранящихся в другой записи).
Кроме того, существовала ветвь наследования между строками и числовыми типами.
Используя иерархию наследования, я могу ссылаться на любое поле с помощью Field
класса или специализированного класса, такого как Field_Int
.
Комментарии:
1. Универсальное программирование — это не только время компиляции, как вы мудро заметили.
Ответ №3:
Одним из самых сильных преимуществ STL является то, что он настолько открыт, и что его алгоритмы работают с моими структурами данных так же хорошо, как с той, которую он предоставляет сам, и что мои алгоритмы работают с его структурами данных так же хорошо, как с моими.
Имеет ли смысл оставлять ваши алгоритмы открытыми для всех типов или ограничивать их вашими, во многом зависит от библиотеки, которую вы пишете, о которой мы ничего не знаем.
(Изначально я хотел ответить, что универсальное программирование — это то, что нужно для широкой открытости, но теперь я вижу, что универсальности всегда есть пределы, и что вам нужно где-то провести черту. С таким же успехом это может быть ограничено вашими типами, если это имеет смысл.)
Комментарии:
1. Я опубликовал правку по своему вопросу внизу. Это библиотека базы данных. В итоге получается работа с STL, кортежами, повышающим слиянием, множеством универсальностей : D Один вопрос для меня: как определить, где провести эту линию?
2. Этот комментарий заставляет вспомнить меня, книгу Малкольма Гладуэлла «Давид и Голиаф», глава «Предел мощности» :).
Ответ №4:
По крайней мере, IMO, правильная вещь — это примерно то, что пытались сделать concepts: вместо того, чтобы пытаться проверить, что вы получаете указанный тип (или один из набора указанных типов), сделайте все возможное, чтобы указать требования к типу и убедиться, что полученный вами тип имеет правильные характеристики и может соответствовать требованиям вашего шаблона.
Как и в случае с концепциями, большая часть мотивации для этого заключается в том, чтобы просто предоставлять хорошие, полезные сообщения об ошибках, когда эти требования не выполняются. В конечном счете, компилятор выдаст сообщение об ошибке, если кто-то попытается создать экземпляр вашего шаблона для типа, который не соответствует его требованиям. Проблема в том, что, скорее всего, сообщение об ошибке не будет очень полезным, если вы не предпримете шаги, чтобы убедиться, что это так.
Ответ №5:
Проблема
Если ваши клиенты могут видеть ваши внутренние функции в общедоступных заголовках, и если имена этих внутренних универсальных функций являются «общими», то вы, возможно, подвергаете своих клиентов риску случайного вызова ваших внутренних универсальных функций.
Например:
namespace Database
{
// internal API, not documented
template <class DatabaseItem>
void
store(DatabaseItem);
{
// ...
}
struct SomeDataBaseType {};
} // Database
namespace ClientCode
{
template <class T, class U>
struct base
{
};
// external API, documented
template <class T, class U>
void
store(base<T, U>)
{
// ...
}
template <class T, class U>
struct derived
: public base<T, U>
{
};
} // ClientCode
int main()
{
ClientCode::derived<int, Database::SomeDataBaseType> d;
store(d); // intended ClientCode::store
}
В этом примере автор main
даже не знает, что Database::store существует. Он намеревается вызвать ClientCode::store и становится ленивым, позволяя ADL выбирать функцию вместо указания ClientCode::store
. В конце концов, его аргумент к store
происходит из того же пространства имен, что и store
, поэтому он должен просто работать.
Это не работает. В этом примере вызывается Database::store
. В зависимости от внутренностей Database::store
этот вызов может привести к ошибке во время компиляции или, что еще хуже, к ошибке во время выполнения.
Как исправить
Чем более обобщенно вы называете свои функции, тем больше вероятность, что это произойдет. Дайте вашим внутренним функциям (тем, которые должны отображаться в ваших заголовках) действительно не общие имена. Или поместите их в подпространство имен, например details
. В последнем случае вы должны убедиться, что ваши клиенты никогда не будут иметь details
в качестве связанного пространства имен для целей ADL. Обычно это достигается путем отказа от создания типов, которые клиент будет использовать, прямо или косвенно, в namespace details
.
Если вы хотите стать более параноидальным, начните блокировать вещи с enable_if
.
Если, возможно, вы считаете, что ваши внутренние функции могут быть полезны вашим клиентам, то они больше не являются внутренними.
Приведенный выше пример кода не является надуманным. Это случилось со мной. Это произошло с функциями в namespace std
. В store
этом примере я называю это чрезмерно общим. std::advance
и std::distance
являются классическими примерами чрезмерно универсального кода. Этого следует избегать. И это проблема, которую concepts пытались исправить.
Комментарии:
1. Спасибо, это очень похожий сценарий на то, от чего я пытался защититься, хотя я не был уверен, не трачу ли я впустую свое время.