C ++, как мне создать переменные и функции с ограниченным потоком / защищенные? - PullRequest
0 голосов
/ 19 июня 2019

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

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

Есть ли способ сделать это или это выходит за рамки языка? «Если вам нужно это решение, вы делаете это неправильно», комментарии не оценены, мне удалось почти вдвое сократить время выполнения моей программы с помощью этой методологии, но слишком вероятно, что я совершу ошибку, которая приведет к молчаливой гонке состояние и в конечном итоге неопределенное поведение.

1 Ответ

0 голосов
/ 22 июня 2019

То, что вы описали, это именно то, для чего предназначен макрос 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.

...