实现编译时机制检查字符串的唯一性
Implementing compile-time mechanism checking uniqueness of a string
定义我的问题的最简单方法是,我正在尝试实现一种机制,该机制将检查是否已使用相同的字符串(或一对(数字,字符串((。我希望使用 C 预处理器以智能方式实现此机制。我还希望这种机制在调试模式下发生冲突或运行时错误时(通过检查断言(时为我提供编译错误。我们不希望开发人员在添加消息时出错,因为每条消息都应该是唯一的。我知道这可以通过计算哈希或例如 crc/md5 来完成,但这种机制很容易发生冲突,我需要避免。至关重要的是,每条消息只能使用一次。
此机制的示例行为:
addMessage(1, "Message1") //OK
addMessage(2, "Message2") //OK
.
.
.
addMessage(N, "MessageN") //OK
addMessage(2, "Message2") //Compile error, Message2 has already been used
替代行为(调试代码时(:
addMessage(1, "Message1") //OK
addMessage(2, "Message2") //OK
.
.
.
addMessage(N, "MessageN") //OK
addMessage(2, "Message2") //Assertion failed, because Message2 has already been used
这样做的首选方法是巧妙地使用 #define 和 #undef 指令。一般来说,预处理器应该以聪明的方式使用(我不确定这是否可能(,也许可以通过适当的宏组合来实现?任何可以帮助我解决这个问题的 C 预处理器黑客?
编辑:我需要这些消息在全球范围内是唯一的,而不仅仅是在一个代码块中(如 if 语句的功能(。
//EDIT2:对这个问题的最佳描述是我有 100 个不同的源文件,我想使用预处理器(或者可能使用预处理器以外的其他机制,而不是每次在编译开始时使用脚本解析源文件,这将非常耗时,并且会为足够复杂的项目添加另一个阶段(多次使用字符串(或预处理器定义(。我仍然不知道该怎么做(我知道这可能根本不可能,但我希望它真的是(。
这将在重复字符串上给出错误:
constexpr bool isequal(char const *one, char const *two) {
return (*one && *two) ? (*one == *two && isequal(one + 1, two + 1))
: (!*one && !*two);
}
constexpr bool isunique(const char *test, const char* const* list)
{
return *list == 0 || !isequal(test, *list) && isunique(test, list + 1);
}
constexpr int no_duplicates(const char* const* list, int idx)
{
return *list == 0 ? -1 : (isunique(*list, list + 1) ? no_duplicates(list + 1, idx + 1) : idx);
}
template <int V1, int V2> struct assert_equality
{
static const char not_equal_warning = V1 + V2 + 1000;
};
template <int V> struct assert_equality<V, V>
{
static const bool not_equal_warning = 0;
};
constexpr const char* l[] = {"aa", "bb", "aa", 0};
static_assert(assert_equality<no_duplicates(l, 0), -1>::not_equal_warning == 0, "duplicates found");
g++ 的输出:
g++ -std=c++11 unique.cpp
unique.cpp: In instantiation of ‘const char assert_equality<0, -1>::not_equal_warning’:
unique.cpp:29:57: required from here
unique.cpp:20:53: warning: overflow in implicit constant conversion [-Woverflow]
unique.cpp:29:1: error: static assertion failed: duplicates found
第一个模板参数(在本例中为 0(到 'assert_equality' 告诉您重复字符串的第一个位置。
我不确定使用标准C++预处理器是否可以轻松实现(我猜不是(。您可以使用其他一些预处理器(例如 GPP(
你可以用另一种方式:从其他来源生成一些X宏"头"文件(例如使用一个小awk
脚本,这将验证单性(。然后自定义您的构建(例如,向Makefile
添加一些规则(以运行该生成脚本以生成头文件。
或者,如果您坚持在编译器内部完成处理,并且您的编译器是最近的 GCC,请考虑使用 MELT 自定义 GCC(例如,通过添加适当的内置或编译指示来完成这项工作(。
在上个世纪,我破解了一个小的Emacs函数,在emacs
编辑器中做了类似的工作(唯一编号错误消息((在保存C文件之前重新编号一些#define
-s(。
我将假设这样的事情会起作用:
addMessage(1, "Message1")
addMessage(2, "Message1")
或:
addMessage(1, "Message") /* transforms into "Message_1" */
addMessage(2, "Message_1") /* transforms into "Message_1_2" */
由于 C 预处理器延迟扩展标记并禁止从另一个宏中定义宏,因此不可能保存执行一个宏的结果,以便另一个宏可以使用它。
另一方面,绝对可以强制符号的唯一性:
#define addMessage(N, MSG) const char *_error_message_##N (void) { return MSG; }
或:
#define addMessage(N, MSG) const char *_error_message_##N (void) { return MSG "_" #N; }
因为在链接步骤中,名称为_error_message_NUMBER
的重复符号将触发错误。 而且因为它是一个函数,所以它不能在不触发错误的情况下在另一个函数内部使用。
假设您的编译器仍然不符合 C++11,因为您没有适当地标记。我还假设您对错误消息并不特别,只是您希望它工作。在这种情况下,以下基于宏的解决方案可能适合您
#include <iostream>
#include <string>
#define ADD_MESSAGE(N, MSG)
char * MSG;
addMessage(N, #MSG);
void addMessage(int n, std::string msg)
{
std::cout << msg << std::endl;
}
int main() {
ADD_MESSAGE(1, Message1); //OK
ADD_MESSAGE(2, Message2); //OK
ADD_MESSAGE(3, MessageN); //OK
ADD_MESSAGE(4, Message2); //Compile error, Message2 has already been used
};
编译输出
prog.cpp: In function ‘int main()’:
prog.cpp:17:17: error: redeclaration of ‘char* Message2’
ADD_MESSAGE(4, Message2); //Compile error, Message2 has already been used
^
prog.cpp:4:8: note: in definition of macro ‘ADD_MESSAGE’
char * MSG;
^
prog.cpp:15:17: error: ‘char* Message2’ previously declared here
ADD_MESSAGE(2, Message2); //OK
^
prog.cpp:4:8: note: in definition of macro ‘ADD_MESSAGE’
char * MSG;
^
如果您不关心大量无用的样板,那么这里完全是预处理器,因此不必担心范围,然后在程序启动时检查它们是否唯一。
在文件中:
#ifndef ERROR1
#define ERROR1 "1"
#endif
#ifndef ERROR2
#define ERROR2 "2"
#endif
...
#ifndef ERROR255
#define ERROR255 "255"
#endif
#include <assert.h>
#include <set>
#include <string>
class CheckUnique {
CheckUnique() {
std::set<std::string> s;
static const char *messages = {
#if HAVE_BOOST
# include <boost/preprocessor.hpp>
# define BOOST_PP_LOCAL_LIMITS (1, 254)
# define BOOST_PP_LOCAL_MACRO(N) ERROR ## N,
# include BOOST_PP_LOCAL_ITERATE()
#else // HAVE_BOOST
ERROR1,
ERROR2,
...
#endif // HAVE_BOOST
ERROR255
};
for (int i = 0; i < sizeof messages / sizeof *messages; i++) {
if (s.count(messages[i]))
assert(! "I found two error messages that were the same");
else
s.insert(messages[i]);
}
}
};
static CheckUnique check;
然后,可以在每个源文件的末尾#include
此文件,也可以将其放入自己的文件中,并包含包含具有#define ERROR
行的每个文件。 这样,一旦操作系统加载程序,用于检查的构造函数就会运行并引发异常。
这也要求你能够访问 Boost.Preprocessor 库(它只是标头,所以很容易设置(。 虽然如果你不能使用它,那么你可以硬编码错误宏,如我在#if HAVE_BOOST
块中所示。
这里的大多数样板都非常简单,所以如果你用一个程序(比如某种可移植的脚本(生成它,那么它会让你的生活更轻松,但它仍然可以一次性完成。