我正在构建的应用程序中有三个线程,在应用程序的整个生命周期中,所有这些线程都保持打开状态。只能从特定线程访问几个变量和函数。在我的调试编译中,如果要从非法线程访问这些函数或变量之一,则希望运行检查并抛出错误,但是我不希望这成为最终编译时的开销。我真的只是想要这样做,所以程序员不会犯愚蠢的错误,也不能保护我的执行程序不会犯错误。
最初,我有一个“线程保护”类模板,该模板将包装函数的返回类型,并在隐式转换为预期的返回类型之前对构造进行检查,但这似乎不适用于void返回类型没有禁用重要警告,并且无法解决有关受保护变量的问题。
有没有这样做的方法,还是超出了语言的范围? “如果您需要此解决方案,您就在做错事情”的评论不被赞赏,我使用这种方法设法将程序的执行时间减少了近一半,但是我很可能会犯一个错误,从而导致无声竞赛条件和最终不确定的行为。
答案 0 :(得分:0)
您所描述的正是assert
宏的用途。
assert(condition)
在调试版本condition
中已检查。如果为假,程序将在该行引发异常。在发行版中,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
是老式的。实际上,我们被告知要停止使用它,因为这会导致资源泄漏(异常被堆积在堆栈中)。由于调试行为与发布行为不同,因此有可能引起调试麻烦。在很多情况下,如果断言的条件为假,则实际上并没有很好的选择。您通常不想继续运行该函数,但也不知道要返回什么值。 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中编写受保护的访问器函数,则可以使编译器内联它们。
更新以解决该问题:
尽管,真正的问题是我应该在哪里放置一个断言宏?是一个 要求我为所有线程编写setter和getters 受保护的变量,然后将它们声明为内联并希望它们得到 在最终版本中进行了优化?
您说有一些变量在访问时应检查(仅在调试版本中),以确保正确的线程正在访问它们。因此,从理论上讲,您希望在每个 这样的访问之前使用一个断言宏。如果只有几个地方,这很容易(如果是这种情况,您可以忽略我要说的一切)。但是,如果有太多地方开始违反DRY Principal,我建议编写getter / setter方法并将assert放入其中(这是我上面给出的示例)。但是,虽然断言不会在发布模式下增加开销(因为它是有条件地编译的),但是使用额外的功能(可能)会增加函数调用的开销。但是,如果在.h中编写它们,则很有可能将它们内联。
您对我的要求是想出一种无需释放开销的方法。现在,我已经提到了内联,我不得不说编译器最了解。通常存在特定于编译器的强制内联的方法(因为允许编译器忽略inline
关键字)。在尝试内联代码之前,应该先对代码进行概要分析。请参阅此问题的答案。 Is it good practice to make getters and setters inline?。通过查看程序集,您可以轻松查看编译器是否内联函数。不用担心,您不必精于组装。只需找到调用函数,然后为获取器/设置器寻找一个call
。如果函数是内联的,则不会看到call
,而可能会看到mov
。