2

Я не эксперт C. Я не делал никаких C с моего CS1 & 2 дня в колледже. Я сделал немного C++ здесь и там, но это было давно. Так что я спрашиваю о правильном шаблоне, чтобы решить проблему, которую я воспринимаю.Абстракции уровня экземпляра в C

Я решил заняться довольно амбициозной задачей, и я делаю это в C. Одна из вещей, которые я изучаю (пере), - это разные методы абстракции в C по сравнению с теми, которые я привык иметь в высших уровня. Проблема, с которой я сталкиваюсь, и спрашиваю, в частности, имеет отношение к тестированию и функциям.

Например, у меня есть две функции: f и g и f будут звонить по номеру g, если выполняются определенные условия.

void f(object* obj) { 
    // ... 
    if(obj->state) 
    g(obj); 
    // ... 
} 

Мне нужно, чтобы проверить, что на самом деле g называют, но я также не хочу, чтобы на самом деле выполнить его, так как он может делать все виды других вещей. Обычно это будет решаться на языке более высокого уровня посредством интерфейсов или абстрактных классов или наследования. Но в C у меня нет этих методов.

Фактически единственным решением этой проблемы, о котором я могу думать, является использование указателей функций. Вместо того, чтобы напрямую звонить g, я должен добавить указатель на object так, чтобы он имел указатель на g, который я могу заменить указателем на другую функцию в тестах.

typedef struct object { 
    int state; 
    void (*g)(object* obj); 
} object; 

void f(object* obj) { 
    // ... 
    if(obj->state) 
    obj->g(obj); 
    // ... 
} 

Тогда обычно я просто указать на g при выделении экземпляра g но в моем тесте я указываю obj-> г на другую функцию, тот, который просто проверяет входящие значения, и отмечает, что это было на самом деле называется.

Это идея, которую я имею прямо сейчас, но я не уверен, что это вполне правильный C-ish способ решить эту проблему. Надеюсь, мое описание иллюстрирует проблему, с которой я сталкиваюсь, но то, что мне действительно интересно, - это то, как лучше справиться с этим.

Каковы рекомендуемые шаблоны проектирования для решения этой проблемы в C?

+0

Что вы имеете в виду, когда говорите, что вам нужно проверить, что g вызывается, но вы не хотите его выполнять? Вы имеете в виду, что иногда вы собираетесь работать в каком-то тестовом режиме, а затем вы хотите запустить альтернативную версию g? – David

+0

да точно. Реальный g может быть довольно сложным, и то, что он делает, не имеет особого значения для целей тестирования f. Я не хочу запускать все, что есть в g, только для проверки f. Вы бы использовали интерфейсы и stubbing, если вы собираетесь решить это на C#, например. –

+0

В итоге я фактически не делал ни одного из приведенных ниже ответов. Вместо этого я создал пользовательский malloc, который затем вставляю небольшой заголовок в каждое распределение памяти (только в отладочном), которое включает в себя «нужный» номер перечисления. Затем я создал новую структуру, которая имеет ту же структуру плюс желаемое значение хэш-функции. Когда в режиме отладки я смотрю на заголовок, обнаруживаю, какая структура входит, и либо делает хеш, как обычно, либо просто использует предоставленный хеш, если он существует. Работает как шарм и не имеет эффектов исполнения в выпуске! –

ответ

1

Если вы хотите, чтобы изменить функцию g вызывается в различных вызовах f, может быть, вы должны пройти в g в качестве параметра f:

typedef void (*gFunc)(object* obj); 

void f(object *obj, gFunc g) { 
    ... 
    if (obj->state) { 
     g(obj); 
    } 
    ... 
} 

Если вы не хотите испортить функцию подписи f, вы можете определить версию f для вызова выше реализации:

void gNormal(object* obj);   //normal implementation of g 
void gTest(object* obj);    //test version of g 

void fGeneral(object* obj, gFunc g); //general implementation of f as above 

void fNormal(object* obj)    
{ 
    fGeneral(obj, &gNormal);   //use the default version of g 
} 

Независимо от того, выбрали ли вы этот метод или указатель функции g, член объекта, вероятно, примерно совпадает с точкой зрения языка - вам нужно учитывать, что имеет больше смысла для вашей структуры объекта.

Опять же, если вы делаете много таких вещей, возможно, вы должны использовать объектно-ориентированный язык!

+0

Да, я вижу. Это определенно сработает. Вы сказали: «... подумайте, что имеет смысл для вашей структуры объекта». Я думаю, что в любом случае это будет работать. Мне просто интересно, есть ли какая-то стандартная практика, которая рекомендуется. Обычный способ справиться с этим так сказать. И в отношении использования объектно-ориентированного языка ... вот что я пытаюсь сделать :) Вы должны где-то начать. –

0

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

#ifdef TESTING 

    int g() { // implement test version here } 

#else 

    int g() { // implement real version here } 

#endif 

Если вы знаете только во время компиляции, вы могли бы дать использовать глобальную переменную таким же образом, но с обеих реализаций в одном определении функции:

int g() { 
    if (gTesting) { 
     // implement test version here 
    } 
    else { 
     // implement real version here 
    } 
} 
+0

Я не уверен, что макро-решение будет работать, так как у меня также есть другие тесты для g в другом месте. Я действительно нуждаюсь в реальном g, чтобы существовать, я просто не хочу всегда его называть. Другое решение будет работать, но такая вещь может стать действительно уродливой очень быстрой. –

+0

Я не знаю, будет ли он работать лучше для вашего кода, но вы также можете перенести макросы внутрь f, так что внутри f вы вызываете реальный g, если TESTING не определен и тестовая версия g, если ИСПЫТАНИЕ. – David

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