【问题标题】:Passing a class with conversion function for const char* through a variadic function like printf通过像 printf 这样的可变参数函数传递一个具有 const char* 转换函数的类
【发布时间】:2019-10-23 02:14:37
【问题描述】:

我有一个类 SpecialString。它有一个运算符重载/转换函数,它在任何时候都使用它作为 const char* 传递。然后它返回一个正常的 c 字符串。

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

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

SpecialString str1("Hello"), str2("World");
printf("%s %s\n", 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 %s\n", *str1, *str2);

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

【问题讨论】:

  • 我几乎可以向您保证,“编译器足够聪明,知道参数是 char* 并且它使用了转换函数”是对编译器所做工作的误解。跨度>
  • 您是否尝试使用选项-Wnon-pod-varargs 编译它?
  • 这从未正式生效。你逃脱了一些未定义的行为。有时你会这样做。有时你不会。
  • @Havenard,将错误的类型传递给printf 是UB。
  • @Havenard 他们正在传递SpecialString 类型的左值。那是错误的类型。

标签: c++ function methods variadic


【解决方案1】:

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

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

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

【讨论】:

    【解决方案2】:

    由于您不想修改现有代码,因此您可以编写“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,因此您可能必须编写一堆不同的重载,这至少可以说是丑陋的,但它确实适合您要求。

    【讨论】:

      【解决方案3】:

      正如另一条评论中提到的,在您的情况下发生的总是未定义的行为。

      对于 Microsoft CString 类,似乎未定义的行为被如此使用(因为它恰好起作用),现在布局以一种仍然可以工作的方式定义。见How can CString be passed to format string %s?

      在我们的代码库中,当我修改文件以通过调用 GetString()

      显式进行转换时,我尝试修复代码

      你可以做一些事情:

      • 在收到警告的任何地方修复现有代码

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

      • 使用一些格式库

      • 使用可变参数模板函数,以便进行转换。

      • 如果您只使用几种类型(int、double、string)并且很少使用超过 2 或 3 个参数,也可以定义重载。
      • 不推荐:让你的班级重新开始工作。

        • 您是否对类定义进行了任何更改,导致其中断或仅升级编译器版本或更改编译器选项?
        • 此类 hack 正在处理未定义的行为,因此您必须弄清楚您的编译器是如何工作的,并且代码将无法移植。
        • 要使其工作,类必须具有指针的大小,并且数据本身必须与指针兼容。因此,本质上,数据必须由单个指针组成(没有 v-table 或其他东西)。

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

      【讨论】:

        猜你喜欢
        • 2014-01-27
        • 1970-01-01
        • 1970-01-01
        • 2019-01-31
        • 1970-01-01
        • 2011-10-25
        • 1970-01-01
        • 2020-01-13
        • 1970-01-01
        相关资源
        最近更新 更多