Как избежать использования global в C для обнаружения установки переменной?
У меня есть функция foo(), которая вызывается из двух разных потоков кода. Допустим, эти два потока кода имеют две разные функции, вызывающие foo ()
bar () и tar ()
Я хочу принять какое-то решение, исходя из того, какая функция (bar () или tar ()) вызвала foo (). В настоящее время я устанавливаю глобальную переменную IN_BAR = 1; в случае bar () и IN_BAR = 0; в случае смолы. Затем я проверяю значение IN_BAR в foo () и что-то делаю.
int IN_BAR = 0; // global
void bar () {
...
IN_BAR = 1;
foo();
IN_BAR = 0;
..
}
void tar() {
...
foo();
...
}
void foo() {
...
if (IN_BAR)
do_this();
else
do_that();
}
В настоящее время есть много мест (в кодовой базе), которые выглядят так. Я хочу избежать использования глобальных переменных и установки и сброса глобальных переменных. Есть ли способ справиться с вышеупомянутой ситуацией? Или здесь есть недостаток дизайна?
РЕДАКТИРОВАТЬ:
Есть случаи, когда bar () и tar () не вызывают напрямую foo(), т.е. между ними много вызовов, но решение должно быть принято в bar () и tar ().
bar () ->filler1 () -> filler2 ()-> foo ()
tar () ->func1 () -> func2 ()-> foo ()
test.c
#include <stdio.h>
// globals
int data = 0;
int IN_BAR = 0;
int calculate()
{
// Some algorithm.
return 10;
}
void foo()
{
if (!IN_BAR)
data = calculate();
// Use data in this method.
printf("Data: %d\n", data);
}
// This function is a place holder for multiple functions.
void func_bar()
{
foo();
}
void bar()
{
IN_BAR = 1;
data = calculate();
func_bar();
IN_BAR = 0;
}
// This function is a place holder for multiple functions.
void func_tar()
{
foo();
}
void tar()
{
func_tar();
}
int main()
{
int c = 1;
if (c == 1)
bar();
else
tar();
return 1;
}
Я вычисляю что-то внутри бара и хочу использовать повторно в foo(), но в случае tar я не вычисляю и хочу вычислить снова, так как вычисление не выполняется.
Как я могу справиться с этим делом?
2 ответа
Есть ли способ справиться с вышеупомянутой ситуацией?
Это звучит как foo()
нужна какая-то информация о контексте, в котором он работает, чтобы сделать правильные вещи. Если вы думаете об этом таким образом, то этот контекст, каким бы он ни был, явно является вкладом в foo()
и так должно быть передано в качестве параметра. Другими словами, вы должны назвать это что-то вроде foo(context)
,
Или здесь есть недостаток дизайна?
Это зависит от обстоятельств. Функция на самом деле не должна знать или беспокоиться о том, откуда она вызывается, но, возможно, есть некоторая информация, относящаяся к вызывающей стороне, которая имеет смысл в качестве ввода. Например, если вы знаете, что bar()
вызывается только при наличии некоторых данных и tar()
вызывается только тогда, когда данные недоступны, тогда вы можете передать параметр, который указывает, доступны ли данные. Или вы можете просто передать сами данные, если они доступны или nil
если это не так.
Причина, по которой функция не должна заботиться о том, кто ее вызывает, заключается в том, что такие знания делают функцию хрупкой, трудной для тестирования и трудной в использовании. Это хрупко, потому что функция может сломаться, если обстоятельства вызывающего абонента изменятся. Это сложно протестировать, потому что тестирование обычно включает вызов функции, и ожидается, что поведение функции будет одинаковым независимо от того, откуда она вызывается. И это трудно использовать, потому что вам нужно знать о зависимости функции от вызывающей стороны: если вы когда-нибудь захотите вызвать ее откуда-то еще, вы должны обновить функцию.
Есть случаи, когда bar() и tar() не вызывают напрямую foo(), т.е. между ними много вызовов, но решение должно быть принято в bar() и tar().
Ответ по-прежнему в значительной степени тот же: вызывающая сторона имеет некоторую информацию, которая является вводом для вызываемой стороны, поэтому она должна передать ее в качестве параметра. Давайте рассмотрим вашу цепочку вызовов:
деготь ()->func1()->func2()-> Foo ()
поскольку tar()
звонки func1()
вместо foo()
не должно делать никаких предположений о том, как func1()
работает - он просто должен передать информацию, которая func1()
должен сделать свою работу и получить результат, который func1()
возвращается. "Решение", которое tar()
а также bar()
сделать, что бы это ни было на самом деле, является для вас необходимым вкладом в foo()
и, следовательно, это необходимый вклад в func2()
и, следовательно, это необходимый вклад в func1()
, даже если func1()
а также func2()
не используйте эту информацию, кроме как для вызова какой-либо другой функции. Таким образом, один из вариантов заключается в том, чтобы каждая промежуточная функция принимала новый параметр, который они просто передают следующей функции в цепочке.
Другой вариант, который может работать лучше, если может быть более одного "решения", которое влияет foo()
, состоит в том, чтобы создать какую-то структуру, которая предоставляет информацию "контекста" или "среды", и имеет промежуточные функции, которые передаются каждый раз вместе. Вы часто видите этот стиль в графических системах, где есть контекст рисования с большим количеством параметров, которые могут быть изменены, и этот контекст является параметром, передаваемым большинству функций в системе.
Проблема, с которой вы сталкиваетесь, - это процедурная версия проблемы "как получить нужные мне данные", которая часто возникает в объектно-ориентированном программировании. Возможны следующие варианты: 1) сделать так, чтобы объект предположил, где взять необходимые ему данные; или 2) сообщить объекту, где взять необходимые ему данные. Вариант 1 обычно включает одноэлементную или другую глобально доступную порцию данных. Вариант 2 известен как внедрение зависимости и часто обобщается как "говори, не спрашивай".
Другое решение, которое вы, возможно, не рассмотрели, это сломать foo()
до двух или более функций, так что tar()
а также bar()
в конечном итоге вызовите две разные функции, каждая из которых будет делать то, что нужно. Это может сработать, если цепочки вызовов для tar()
а также bar()
действительно различны. В этом случае вы по-прежнему передаете ту же самую информацию - вы просто делаете это неявно, используя имя функции.
Давайте попробуем сделать этот совет более конкретным, взглянув на код, который вы предоставили:
void foo()
{
if (!IN_BAR)
data = calculate();
// Use data in this method.
printf("Data: %d\n", data);
}
Причина того, что foo()
здесь зависит от IN_BAR
в том, что bar()
звонки calculate()
и сохраняет результат в data
, который является еще одной глобальной переменной. Если foo()
вызывается как часть tar()
, затем calculate()
не был вызван и data
предположительно не будет иметь полезного значения, поэтому вам нужно вызвать его внутри foo()
в таком случае.
Ваш конкретный вопрос, кажется, о том, как избежать необходимости IN_BAR
Итак, давайте посмотрим на это в первую очередь. Одним из решений здесь является просто сделать действительный data
требование для звонка foo()
, Если вы это сделаете, то foo()
не нужно IN_BAR
- можно просто предположить, что data
всегда допустимо, потому что вызывать его без этого является ошибкой. Это перекладывает ответственность за вызов calculate()
снаружи foo()
царство, и это позволяет вам позвонить calculate()
где-нибудь выше по цепочке - вы можете сделать это в tar()
или в func_tar()
или в другом месте, если вы делаете это до звонка foo()
, а также foo()
становится проще:
void foo()
{
// Use data in this method.
printf("Data: %d\n", data);
}
Если есть какая-то причина, по которой вы не можете этого сделать, то вы можете, по крайней мере, связать data
к этой одной переменной. Вы можете сделать это либо путем определения некоторого значения для data
это означает "недействительный", так что вы можете просто посмотреть на данные и узнать, действительно ли они действительны. Например, вы можете изменить его тип на int*
и инициализировать его nil
, а затем иметь calculate()
вернуть указатель на int
что он рассчитывает:
void foo()
{
if (data == nil)
data = calculate();
// Use data in this method.
printf("Data: %d\n", data);
}
Было бы намного лучше, однако, переопределить foo()
чтобы data
передается явно:
void foo(int data)
{
// Use data in this method.
printf("Data: %d\n", data);
}
Теперь вам не нужно ни data
или же IN_BAR
как глобалы, и foo()
нужно значение для data
явный Если это нормально для foo()
звонить calculate()
сам, когда нет data
значение, безусловно, должно быть в порядке для func_tar()
звонить calculate()
непосредственно перед звонком foo()
:
void func_tar()
{
foo( calculate() );
}
В случае bar()
, который вызывает calculate()
сама, у вас уже есть data
значение, и это должно быть передано его вспомогательным функциям:
void func_bar(int data)
{
foo(data);
}
Это правда, даже если func_bar()
является заменой для нескольких функций. Суть в том, что использование глобальной переменной, чтобы избежать передачи data
информация в параметрах - плохая идея. возможно data
также заменяет несколько различных значений; в этом случае объедините их все в одну структуру, как я описал в обсуждении выше.
Если вы хотите избежать этого, то не имеют bar
вызов foo
, Поскольку очевидно bar
знает о foo
Он может вызвать соответствующую версию и оставить это решение вызывающей стороне.
Создайте отдельную функцию для случая bar
звонки foo
и для tar
призвание foo
:
void bar () {
...
foobar();
..
}
void tar() {
...
footar();
...
}
void foobar() {
...
do_this();
}
void footar() {
...
do_that();
}
Редактировать:
Чтобы избежать необходимости вносить много изменений в код, вы можете изменять только там, где в данный момент используется глобальная переменная (и добавлено предложение Джонатана Леффлера):
void bar () {
...
foobar(); // change only here
..
}
void tar() {
...
foo();
...
}
void foobar() {
foocommon();
do_this();
}
void foo() {
foocommon();
do_that();
}
void foocommon()
{
...
}