Подтвердить что ты не робот

Безопасность аргумента функции С++

В функции, которая принимает несколько аргументов одного и того же типа, как мы можем гарантировать, что вызывающий абонент не испортил порядок?

Например

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,...
4b9b3361

Ответ 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, скорее всего, вызовет все правильные дискуссии об использовании памяти, документации, тестировании производительности и т.д.