MFC CString 中的可变参数和转换运算符

Posted

技术标签:

【中文标题】MFC CString 中的可变参数和转换运算符【英文标题】:varargs and conversion operators in MFC CString 【发布时间】:2016-08-09 12:34:12 【问题描述】:

在我测试过的所有Visual C++版本下的MFC中,下面的代码都能编译运行;

CString A = "String A", B;
B.Format("The value of A is %s", A);

但会产生警告

C6284, Object passed as _Param_(2) when a string is required in call to 
'ATL::CStringT<char,StrTraitMFC<char,ATL::ChTraitsCRT<char> > >::Format'
 Actual type: 'class ATL::CStringT<char,class StrTraitMFC<char,class ATL::ChTraitsCRT<char> > >'.

查看 CString 的源代码及其 CStringT 的祖先 CSimpleStrT,同时有一个转换运算符可以有效地转换为 char*,如下所示;

operator PCXSTR() const throw()

    return( m_pszData );

数据缓冲区似乎也是类中的第一个数据元素,通过调试器下的执行跟踪表明没有调用转换运算符,代码纯粹基于类布局工作。

虽然原始代码可以很容易地重写以避免上述情况,但它是否有必要并且可能会被未来的 MFC 更新破坏?涉及的代码库部分主要是第 3 方,我宁愿避免在没有充分理由的情况下更改它。

【问题讨论】:

【参考方案1】:

没有您编写的代码已损坏。正式地说,这是未定义的行为。您不应将对象传递给可变参数函数。这些类型的函数没有编译时类型检查(,不是类型安全的),因此编译器不知道它应该调用隐式转换运算符来转换 @ 987654323@ 对象为PCXSTR。您必须显式执行转换,或者使用强制转换或调用返回指向底层 C 样式字符串缓冲区的指针的成员函数。

所有可变参数函数都是如此。甚至像printf 这样简单的东西。以下代码错误:

std::string str = "world";
printf("Hello, %s", str);   // <-- this code is WRONG!

你必须这样写:

std::string str = "world";
printf("Hello, %s", str.c_str());

对于 MFC* 也是一样的:

CString str = TEXT("world");
CString buffer;
buffer.Format(TEXT("Hello, %s"), static_cast<LPCTSTR>(str));
// alternatively:
// buffer.Format(TEXT("Hello, %s"), str.GetString());

这是不在 C++ 中使用可变参数函数的一个很好的理由。更喜欢流,它是类型安全的并且做正确的事情。

您收到的警告试图提醒您注意此问题。尽管可变参数函数不是类型安全的,但这是一个常见的问题,编译器供应商已经付出了很多努力来尝试解析格式字符串,查找插入,并将它们与传递的参数进行匹配。由于我已经描述的原因,它在这种情况下发现了不匹配,并发出警告C6284。


* 实际上,在这种情况下,它恰好对 CString 起作用无论如何。这是因为,正如您在调试器中发现的那样,该类是专门设计的,因此它的第一个成员是指向 C 样式字符串缓冲区的指针。因此,当它以非类型安全的方式按值传递给像printf 这样的可变参数函数时,printf 看到的唯一东西就是指针,所以当它看到%s 说明符时,它会被正确解析。但我不建议依赖这种行为。它仍然是形式上未定义的行为,即使它以特定于实现的方式工作。

Microsoft's documentation specifically tells you to pass CString objects to variadic functions by performing an explicit cast to a char-pointer.

当然,此时 CString 的接口不太可能发生变化,甚至更不可能,因为它会破坏大量现有代码。但你永远不知道,正确编写代码会让你的意图更加清晰。

【讨论】:

谢谢 Cody,正如我所料,我可能会在时间允许的情况下将所有代码从可变参数函数中完全迁移出去。 "[CString's] 第一个成员是指向 C 风格字符串缓冲区的指针" - 事实上,它也是唯一的成员,所以 sizeof(CString)sizeof(TCHAR*) 相同。这很可能是一个在过去也被滥用的属性,并且改变CString 的类布局的机会变得越来越低。所以,是的,“不要依赖它” 是个好建议,但实际上,实现方式永远不会改变。

以上是关于MFC CString 中的可变参数和转换运算符的主要内容,如果未能解决你的问题,请参考以下文章

MFC---典型类和函数

关于MFC中CString的用法

关于MFC中CString的用法

MFC常见问题以及解决方法_Cstring和string互相转换

将 MFC CMap 用于 CString int 对

CString到字节和字节到CByteArray转换-MFC