通过像printf这样的可变参数函数传递一个带有常量字符*转换函数的类

Passing a class with conversion function for const char* through a variadic function like printf

本文关键字:函数 一个 字符 常量 转换 printf 参数 变参      更新时间:2023-10-16

我有一个类SpecialString。它有一个运算符重载/转换功能,它在任何时候作为常量字符*传递时使用。然后它返回一个普通的 c 字符串。

class SpecialString
{
...
operator char* () const { return mCStr; }
...
};

这在很久以前(实际上是 19 年前)曾经有效,当时我将它们直接传递给 printf()。编译器足够聪明,知道参数是 char* 并且它使用了转换函数,但现在的 g++ 抱怨。

SpecialString str1("Hello"), str2("World");
printf("%s %sn", str1, str2);

错误:无法通过可变参数方法传递非 POD 类型"SPECIALSTRING"(又名"SpecialString")的对象;调用将在运行时中止 [-Wnon-pod-varargs]

有没有办法在不更改代码的情况下让它再次工作?我可以添加一个 deref 运算符重载函数,该函数返回 c 字符串并像这样传递 SpecialString 对象。

class SpecialString
{
...
operator CHAR* () const { return mCStr; }
char* operator * () const { return mCStr; }
...
};
SpecialString str1("Hello"), str2("World");
printf("%s %sn", *str1, *str2);

但我宁愿不要这样做,因为这需要手动更改数千行代码。

如果您不想收到通知,可以禁用该警告...但这是个坏主意。

程序的行为是未定义的,您应该修复它,这需要更改代码。您可以将现有转换运算符与static_cast一起使用,或者您可以使用一元*运算符的想法,这将更简洁。

如果您改用不需要引入重载的一元+,则需要更少的更改,因为它将调用隐式转换。不过,这可能会给代码的读者增加一些困惑。

由于您不想修改现有代码,因此可以编写"hack"。更具体地说,一堆重载printf()修补现有代码。

例如:

int printf(const char* f, const SpecialString& a, const SpecialString& b)
{
return printf(f, (const char*)a, (const char*)b);
}

在标头中声明此函数后,每次使用这些特定参数调用printf()都将使用此函数而不是您熟悉的"真实"printf(),并执行所需的转换。

我认为您的代码中有相当多的printf()调用组合涉及SpecialString,因此您可能需要编写一堆不同的重载,这至少可以说是丑陋的af,但它确实符合您的要求。

正如另一条评论中提到的,它一直是未定义的行为,恰好适用于您的情况。

对于MicrosoftCString类,似乎未定义的行为被如此使用(因为它碰巧有效),现在布局以它仍然可以工作的方式定义。请参阅如何将 CString 传递给格式字符串 %s?。

在我们的代码库中,当我修改文件以通过调用GetString()显式执行转换时,我尝试修复代码

您可以执行以下几项操作:

  • 修复收到警告的任何位置的现有代码

    在这种情况下,像c_strGetString这样的命名函数比转换运算符更可取,以避免显式强制转换(例如static_cast甚至最糟糕的 C 型情况(const char *).deref运算符可能是可以接受的折衷方案。

  • 使用一些格式库

    • <iosteam>
    • FMT:https://github.com/fmtlib/fmt
    • 许多其他选择(搜索C++格式库或类似内容)
  • 使用可变参数模板函数,以便可以进行转换。

  • 如果只使用几个类型(整数、双精度、字符串),并且很少超过 2 或 3 个参数,则定义重载也可能是可能的。
  • 不建议:破解您的课程以再次工作。

    • 您是否对类定义进行了任何更改,导致它中断或仅升级编译器版本或更改编译器选项?
    • 这种黑客正在使用未定义的行为,因此您必须弄清楚编译器的工作原理,并且代码将不可移植。
    • 要使其正常工作,类必须具有指针的大小,并且数据本身必须与指针兼容。因此,从本质上讲,数据必须由单个指针组成(没有向量表或其他东西)。

旁注:我认为应该避免定义自己的字符串类。在大多数情况下,应使用标准C++字符串(或字符串视图)。如果您需要其他函数,我建议您在命名空间中编写独立函数,例如StringUtilities。这样,您可以避免在自己的字符串和标准字符串(或某些库字符串,如MFC,Qt或其他内容)之间来回转换。

相关文章: