2016-08-08 2 views
57

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

Например

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

Компилятора может помочь вы в большинстве случаев. В противном случае это ваши обязанности (программист). –

+7

Не так ли легко в C++, используя определенные типы? – alk

+0

Если параметры организованы логически (a, b.c, d, e ...), их легко запомнить все в правильном порядке. –

ответ

69

Типичным решением является размещение параметров в структуре с именованными полями.

AllocateParams p; 
p.num_buffers = 1; 
p.pages_per_buffer = 10; 
p.default_value = 93; 
allocate_things(p); 

Вам необязательно использовать поля, конечно. Вы можете использовать функции-члены или что угодно.

+0

Вы могли бы сделать это, но все равно 'allocate_things' является плохим именем, а' AllocateParams' не лучше. Создание структур более или менее несвязанных переменных, вероятно, еще больше затушевывает ситуацию. –

+10

@FrankPuffer: Да, согласен, но это не замена стека кода. Если у вас есть комментарии об исходном авторском коде, они принадлежат как комментарии к вопросу, а не ответы. Этот образец кода предназначен для иллюстрации конкретной техники и ничего другого. –

+11

@FrankPuffer: Я думаю, ясно, что это просто имена заполнителей. –

7

Вы не можете. Поэтому рекомендуется иметь как можно меньше аргументов функции.

В вашем примере вы могли иметь отдельные функции, такие как set_num_buffers(int num_buffers), set_pages_per_buffer(int pages_per_buffer) и т.д.

Вы, наверное, заметили, что сами allocate_things не доброе имя, потому что оно не выражает то, что функция на самом деле делает. Особенно я не ожидал, что он установит значение по умолчанию.

+3

И разделять обязанности. –

+3

и не делать используйте магические числа, параметры жесткого кодирования, как обычно, приводят к болью больше, чем его ценность. – Cody

+0

lmao, почему это было приостановлено? Я думал, что было сочтено полезным выразить, что люди задают вопросы X/Y? действительный для представления альтернатив, которые имеют свои преимущества. –

30

До сих пор два хороших ответа: еще один подход - попытаться использовать систему типов везде, где это возможно, и создать сильные 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 для эпохи времени в наносекундах и т. Д.

+2

Для целых чисел, в частности, облачное перечисление является достойным выбором. –

+0

Вы можете пойти еще дальше с этим подходом, используя пользовательские литералы, чтобы уменьшить синтаксические накладные расходы при использовании таможенных типов при совершении звонков. – nate

+2

вы можете получить вызов, который выглядит как 'allocate_things (40_buffers, 22_pages, 80 ...', и если вы не поместите значения в нужные места, это даст вам ошибку компилятора. – nate

9

(Примечание: пост был первоначально тегами «C`)

С99 и далее позволяет расширение к идее @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})); 
+1

Но это о C++. Который не импортирует сложные литералы из C. –

+1

@underscore_d Сообщение _was_ о C до тех пор, пока не будет отредактировано (сообщение все еще имеет смысл в C-контекст - непонятно, что OP/πάντα ῥεῖ изменилось - теперь посмотрим, как это соотносится с заголовком) – chux

+1

Yup, только что увидел это. Яркая игра в соответствии с оригинальными тегами. Хотя название всегда не соглашалось. Если бы люди отмечали то, что они на самом деле mean ... sigh –

30

Если у вас есть C++ 11 компилятора, вы могли бы использовать user-defined literals в сочетании с пользовательскими типами. Вот наивный подход:

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

... * oh wow *. +1; это ** очень интересно. Но я не знаю, могу ли я или не хочу найти сценарий, в котором мне это понадобится ... ;-) –

+1

Похоже, что это может быть макроопределение. – rubenvb

+0

Что, если бы 40 были переменной вместо буквального? – Barry

7

Просто для полноты картины, можно использовать именованные аргументы, когда ваш вызов становится.

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); 

Тем не менее, с текущей C++ это требует совсем немного кода, который будет реализован (в файле заголовка объявляющего allocate_things(), который также должен объявить соответствующие внешние объекты num_buffers и т.д. обеспечивая operator=, который возвращает уникальный подходящий объект).

---------- Рабочий пример (для Sergej)

#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); 
} 
+2

Похож на C++ 35, или так ... +1. Хотелось бы увидеть минимальный рабочий пример. – sergej

+0

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4172.htm – sergej

+0

@sergej см. Отредактированный ответ – Walter

6

Вы действительно собираетесь попробовать QA все комбинации произвольных целых чисел? И бросить все проверки на отрицательные/нулевые значения и т. Д.?

Просто создайте два типа перечислений для минимального, среднего и максимального количества буферов и небольших средних и больших размеров буфера. Тогда пусть компилятор сделать работу, и пусть ваши QA люди принимают во второй половине дня от:

allocate_things(MINIMUM_BUFFER_CONFIGURATION, LARGE_BUFFER_SIZE, 42); 

Тогда у вас есть только проверить ограниченное число комбинаций, и вы будете иметь покрытие 100%. Люди, работающие над вашим кодом через 5 лет, должны знать только то, чего они хотят достичь, и не должны угадывать номера, которые могут им понадобиться, или какие значения действительно были протестированы в поле.

Это делает код немного сложнее, но он звучит так, как параметры для низкоуровневой настройки производительности, поэтому слияние значений не должно восприниматься как дешевое/тривиальное/не нуждающееся в тщательном тестировании. Обзор кода изменения от allocate_something (25, 25, 25);

... чтобы

allocate_something (30, 80, 42);

... скорее всего, получите лишь пожимает плечами/сдувается, но обзор кода нового EXTRA_LARGE_BUFFERS значения перечислимого типа, скорее всего, вызовет все необходимые дискуссии об использовании памяти, документировании, тестирование производительности и т.д.

Смежные вопросы