То, что вы описали, это именно то, для чего предназначен макрос assert
.
assert(condition)
В отладочной сборке проверено condition
.Если значение равно false, программа выдаст исключение в этой строке.В сборке релиза assert
и все, что находится внутри скобок, не компилируется.
Не будь грубым, было бы более полезно, если бы вы объяснили переменные, которые вы пытаетесь защитить.Какого они типа?Откуда они?Какова их жизнь?Они глобальные?Зачем вам нужно возвращать возвращаемый тип, если он пуст?Как вы попали в ситуацию, когда один поток может случайно получить доступ к чему-либо.Я вроде должен догадаться, но я выброшу некоторые идеи здесь:
#include <thread>
#include <cassert>
void protectedFunction()
{
assert(std::this_thread::get_id() == g_thread1.get_id());
}
// protect a global singleton (full program lifetime)
std::string& protectedGlobalString()
{
static std::string inst;
assert(std::this_thread::get_id() == g_thread1.get_id());
return inst;
}
// protect a class member
int SomeClass::protectedInt()
{
assert(std::this_thread::get_id() == g_thread1.get_id());
return this->m_theVar;
}
// thread protected wrapper
template <typename T>
class ThreadProtected
{
std::thread::id m_expected;
T m_val;
public:
ThreadProtected(T init, std::thread::id expected)
: m_val(init), m_expected(expected)
{ }
T Get()
{
assert(std::this_thread::get_id() == m_expected);
return m_val;
}
};
// specialization for void
template <>
class ThreadProtected<void>
{
public:
ThreadProtected(std::thread::id expected)
{
assert(std::this_thread::get_id() == expected);
}
};
assert
- oldschool.Нам фактически сказали прекратить использовать это на работе, потому что это вызывало утечку ресурсов (исключение находилось высоко в стеке).Это может привести к головной боли отладки, потому что поведение отладки отличается от поведения выпуска.В большинстве случаев, если заявленное условие ложно, на самом деле нет хорошего выбора, что делать;Вы обычно не хотите продолжать запускать функцию, но также не знаете, какое значение вернуть.assert
все еще очень полезен при разработке кода.Я лично все время использую assert
.
static_assert
здесь не поможет, потому что условие, которое вы проверяете (например, «Какой поток выполняет этот код?»), Является условием времени выполнения.
Еще одно примечание:
Не помещайте вещи, которые вы хотите скомпилировать, в assert
.Сейчас это кажется очевидным, но легко сделать что-то глупое, например
int* p;
assert(p = new(nothrow) int); // check that `new` returns a value -- BAD!!
Хорошо проверить распределение new
, но распределение не произойдет в сборке релиза, и вы не будетедаже заметьте, пока вы не начнете тестирование релиза!
int* p;
p = new(nothrow) int;
assert(p); // check that `new` returns a value -- BETTER...
Наконец, если вы пишете защищенные функции доступа в теле класса или в .h, вы можете побудить компилятор включить их.
Обновление для решения вопроса:
Реальный вопрос, однако, где я помещаю макрос утверждения?Требуется ли, чтобы я записывал сеттеры и геттеры для всех моих переменных, защищенных потоками, затем объявлял их как встроенные и надеялся, что они будут оптимизированы в окончательном выпуске?
Вы сказали, что есть переменные, которые следует проверить (только в отладочной сборке) при обращении к ним, чтобы убедиться, что правильный поток обращается к ним.Таким образом, теоретически, вы хотите макрос assert перед каждым таким доступом.Это легко, если есть только несколько мест (если это так, вы можете игнорировать все, что я собираюсь сказать).Однако, если есть так много мест, где он начинает нарушать принцип DRY, я предлагаю написать геттеры / сеттеры и поместить утверждение внутрь (это то, что я случайно привел в примерах выше).Но хотя assert не добавляет накладных расходов в режиме выпуска (поскольку он условно компилируется), использование дополнительных функций (возможно) добавляет накладные расходы при вызове функции.Однако, если вы напишите их в .h, есть большая вероятность, что они будут встроены.
Ваше требование для меня состояло в том, чтобы придумать способ сделать это без лишних затрат.Теперь, когда я упомянул встраивание, я обязан сказать, что компилятор знает лучше.Обычно существуют специфичные для компилятора способы принудительного встраивания (поскольку компилятору разрешено игнорировать ключевое слово inline
).Вы должны профилировать код, прежде чем пытаться что-то встроить.Смотрите ответ на этот вопрос. Является ли хорошей практикой включение геттеров и сеттеров в линию? .Вы можете легко увидеть, встроен ли компилятор в функцию, посмотрев на сборку.Не волнуйтесь, вам не нужно быть хорошим в сборке.Просто найдите вызывающую функцию и найдите call
для метода получения / установки.Если функция была встроенной, вы не увидите call
и, скорее всего, увидите mov
.