mfccstring中的变量和转换操作符

varargs and conversion operators in MFC CString

本文关键字:转换 操作符 变量 mfccstring      更新时间:2023-10-16

在我测试过的所有版本的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更新破坏吗?所涉及的代码库部分主要是第三方的,我宁愿避免在没有充分理由的情况下更改它。

不,你写的代码是坏的。官方说法是,这是未定义行为。不应该将对象传递给可变函数。这些类型的函数没有编译时类型检查(),即:,不是类型安全的),因此编译器不知道它应该调用隐式转换操作符将CString对象转换为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说明符时,它得到了正确的解析。但我不建议依赖这种行为。它仍然是形式上未定义的行为,即使它以特定于实现的方式工作。

微软的文档特别是告诉你通过对char指针执行显式强制转换将CString对象传递给可变函数。

当然,CString的接口不太可能在此时改变,甚至更不可能,因为它会破坏这么多现有的代码。但是你永远不会知道,当你正确地编写代码时,它会使你的意图更清晰。