Безопасность аргумента функции С++
В функции, которая принимает несколько аргументов одного и того же типа, как мы можем гарантировать, что вызывающий абонент не испортил порядок?
Например
void allocate_things(int num_buffers, int pages_per_buffer, int default_value ...
и позже
// uhmm.. lets see which was which uhh..
allocate_things(40,22,80,...
Ответы
Ответ 1
Типичное решение состоит в том, чтобы поместить параметры в структуру с именованными полями.
AllocateParams p;
p.num_buffers = 1;
p.pages_per_buffer = 10;
p.default_value = 93;
allocate_things(p);
Конечно, вам не обязательно использовать поля. Вы можете использовать функции-члены или что угодно.
Ответ 2
Два хороших ответа до сих пор, еще один: другой подход - попытаться использовать систему типов везде, где это возможно, и создать сильные typedef. Например, используя boost strong typedef (http://www.boost.org/doc/libs/1_61_0/libs/serialization/doc/strong_typedef.html).
BOOST_STRONG_TYPEDEF(int , num_buffers);
BOOST_STRONG_TYPEDEF(int , num_pages);
void func(num_buffers b, num_pages p);
Вызов func с аргументами в неправильном порядке теперь будет ошибкой компиляции.
Несколько заметок об этом. Во-первых, усиление сильного typedef довольно устарело в своем подходе; вы можете делать гораздо более приятные вещи с помощью вариабельного CRTP и полностью избегать макросов. Во-вторых, очевидно, что это приводит к некоторым накладным расходам, поскольку вам часто приходится явно конвертировать. Поэтому обычно вы не хотите злоупотреблять им. Это очень приятно для вещей, которые появляются снова и снова в вашей библиотеке. Не так хорошо для вещей, которые появляются как один. Так, например, если вы пишете библиотеку GPS, у вас должен быть сильный двойной typedef для расстояний в метрах, сильный int64 typedef для временной эпохи в наносекундах и т.д.
Ответ 3
Если у вас есть компилятор С++ 11, вы можете использовать пользовательские литералы в сочетании с пользовательскими типами. Вот наивный подход:
struct num_buffers_t {
constexpr num_buffers_t(int n) : n(n) {} // constexpr constructor requires C++14
int n;
};
struct pages_per_buffer_t {
constexpr pages_per_buffer_t(int n) : n(n) {}
int n;
};
constexpr num_buffers_t operator"" _buffers(unsigned long long int n) {
return num_buffers_t(n);
}
constexpr pages_per_buffer_t operator"" _pages_per_buffer(unsigned long long int n) {
return pages_per_buffer_t(n);
}
void allocate_things(num_buffers_t num_buffers, pages_per_buffer_t pages_per_buffer) {
// do stuff...
}
template <typename S, typename T>
void allocate_things(S, T) = delete; // forbid calling with other types, eg. integer literals
int main() {
// now we see which is which ...
allocate_things(40_buffers, 22_pages_per_buffer);
// the following does not compile (see the 'deleted' function):
// allocate_things(40, 22);
// allocate_things(40, 22_pages_per_buffer);
// allocate_things(22_pages_per_buffer, 40_buffers);
}
Ответ 4
(Примечание: сообщение было первоначально помечено знаком "C" )
C99 и далее позволяет расширение @Dietrich Epp: составной литерал
struct things {
int num_buffers;
int pages_per_buffer;
int default_value
};
allocate_things(struct things);
// Use a compound literal
allocate_things((struct things){.default_value=80, .num_buffers=40, .pages_per_buffer=22});
Может даже передать адрес структуры.
allocate_things(struct things *);
// Use a compound literal
allocate_things(&((struct things){.default_value=80,.num_buffers=40,.pages_per_buffer=22}));
Ответ 5
Вы не можете. Поэтому рекомендуется иметь как можно меньше аргументов функции.
В вашем примере у вас могут быть отдельные функции, такие как set_num_buffers(int num_buffers)
, set_pages_per_buffer(int pages_per_buffer)
и т.д.
Вы, наверное, заметили, что allocate_things
не является хорошим именем, потому что оно не выражает, что функция фактически делает. Особенно я не ожидал, что он установит значение по умолчанию.
Ответ 6
Просто для полноты вы можете использовать именованные аргументы, когда ваш вызов станет.
void allocate_things(num_buffers=20, pages_per_buffer=40, default_value=20);
// or equivalently
void allocate_things(pages_per_buffer=40, default_value=20, num_buffers=20);
Однако, с текущим С++ это требует довольно много кода для реализации (в объявлении файла заголовка allocate_things()
, который должен также объявлять соответствующие внешние объекты num_buffers
и т.д., предоставляя operator=
, которые возвращают уникальный подходящий объект).
---------- рабочий пример (для сергей)
#include <iostream>
struct a_t { int x=0; a_t(int i): x(i){} };
struct b_t { int x=0; b_t(int i): x(i){} };
struct c_t { int x=0; c_t(int i): x(i){} };
// implement using all possible permutations of the arguments.
// for many more argumentes better use a varidadic template.
void func(a_t a, b_t b, c_t c)
{ std::cout<<"a="<<a.x<<" b="<<b.x<<" c="<<c.x<<std::endl; }
inline void func(b_t b, c_t c, a_t a) { func(a,b,c); }
inline void func(c_t c, a_t a, b_t b) { func(a,b,c); }
inline void func(a_t a, c_t c, b_t b) { func(a,b,c); }
inline void func(c_t c, b_t b, a_t a) { func(a,b,c); }
inline void func(b_t b, a_t a, c_t c) { func(a,b,c); }
struct make_a { a_t operator=(int i) { return {i}; } } a;
struct make_b { b_t operator=(int i) { return {i}; } } b;
struct make_c { c_t operator=(int i) { return {i}; } } c;
int main()
{
func(b=2, c=10, a=42);
}
Ответ 7
Вы действительно собираетесь попробовать QA все комбинации произвольных целых чисел? И бросить все проверки на отрицательные/нулевые значения и т.д.?
Просто создайте два типа перечислений для минимального, среднего и максимального количества буферов и небольших средних и больших размеров буфера. Затем пусть компилятор выполнит эту работу и пусть ваши люди QA уйдут днем:
allocate_things(MINIMUM_BUFFER_CONFIGURATION, LARGE_BUFFER_SIZE, 42);
Затем вам нужно только проверить ограниченное количество комбинаций, и у вас будет 100% охват. Люди, работающие над вашим кодом через 5 лет, должны знать только то, чего они хотят достичь, и не должны угадывать числа, которые могут им понадобиться, или какие значения действительно были протестированы в этой области.
Это делает код немного сложнее продлить, но похоже, что параметры предназначены для низкоуровневой настройки производительности, поэтому не следует воспринимать ценности как дешевые/тривиальные/не нуждающиеся в тщательном тестировании. Обзор кода изменения с allocate_something (25, 25, 25);
... to
allocate_something (30, 80, 42);
... скорее всего, просто пожимает плечами/сбрасывается, но обзор кода нового значения enum EXTRA_LARGE_BUFFERS, скорее всего, вызовет все правильные дискуссии об использовании памяти, документации, тестировании производительности и т.д.