Рекомендации по программированию на общих / шаблонных языках: ограничивать типы или не ограничивать типы

#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. Спасибо, это очень похожий сценарий на то, от чего я пытался защититься, хотя я не был уверен, не трачу ли я впустую свое время.