varargs и операторы преобразования в MFC CString

В MFC во всех версиях Visual C++, которые я тестировал, следующий код компилируется и работает;

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? Часть задействованной кодовой базы в основном принадлежит третьей стороне, и я бы предпочел не менять ее без веской причины.


person SmacL    schedule 09.08.2016    source источник


Ответы (1)


Нет, код, который вы написали, не работает. Официально говоря, это undefined-behavior. Вы не должны передавать объекты функциям с переменным числом аргументов. Эти типы функций не имеют проверки типов во время компиляции (т.е., не безопасны для типов), поэтому компилятор не знает, что он должен вызвать неявный оператор преобразования для преобразования этого 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. Но я бы не рекомендовал полагаться на такое поведение. Это по-прежнему формально неопределенное поведение, даже если оно работает в зависимости от реализации.

документация Microsoft конкретно говорит вам передавать объекты CString в вариативные функции, выполняя явное приведение к указателю на символ.

Конечно, маловероятно, что интерфейс CString изменится на этом этапе, и еще менее вероятно, потому что это нарушит так много существующего кода. Но вы никогда не знаете, и это делает ваши намерения более ясными, когда вы пишете код правильно.

person Cody Gray    schedule 09.08.2016
comment
Спасибо, Коди, как я и подозревал, я, вероятно, полностью перенесу весь код из функций с переменным числом аргументов, как только позволит время. - person SmacL; 09.08.2016
comment
Первый элемент [CString's] является указателем на строковый буфер в стиле C. Фактически, это также единственный элемент, поэтому sizeof(CString) совпадает с sizeof(TCHAR*). Вероятно, этим свойством злоупотребляли и в прошлом, и шансы когда-либо изменить макет класса для CString становятся все более низкими. Так что да, не полагайтесь на это — это хороший совет, но на самом деле реализация никогда не изменится. - person IInspectable; 10.08.2016