实现编译时机制检查字符串的唯一性

Implementing compile-time mechanism checking uniqueness of a string

本文关键字:字符串 唯一性 检查 机制 编译 时机 实现      更新时间:2023-10-16

定义我的问题的最简单方法是,我正在尝试实现一种机制,该机制将检查是否已使用相同的字符串(或一对(数字,字符串((。我希望使用 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块中所示。

这里的大多数样板都非常简单,所以如果你用一个程序(比如某种可移植的脚本(生成它,那么它会让你的生活更轻松,但它仍然可以一次性完成。