Можно ли * безопасно * вернуть TCHAR* из функции?
Я создал функцию, которая преобразует все коды уведомлений о событиях в строки. Довольно простые вещи на самом деле.
У меня есть куча таких как
const _bstr_t DIRECTSHOW_MSG_EC_ACTIVATE("A video window is being activated or deactivated.");
const _bstr_t DIRECTSHOW_MSG_EC_BUFFERING_DATA("The graph is buffering data, or has stopped buffering data.");
const _bstr_t DIRECTSHOW_MSG_EC_BUILT("Send by the Video Control when a graph has been built. Not forwarded to applications.");
.... etc....
и моя функция
TCHAR* GetDirectShowMessageDisplayText( int messageNumber )
{
switch( messageNumber )
{
case EC_ACTIVATE: return DIRECTSHOW_MSG_EC_ACTIVATE;
case EC_BUFFERING_DATA: return DIRECTSHOW_MSG_EC_BUFFERING_DATA;
case EC_BUILT: return DIRECTSHOW_MSG_EC_BUILT;
... etc ...
Ничего страшного. У меня ушло 5 минут, чтобы бросить вместе.
... но я просто не верю, что у меня есть все возможные значения, поэтому я хочу, чтобы по умолчанию было возвращено что-то вроде "Неожиданный код уведомления (7410)", если совпадений не найдено.
К сожалению, я не могу думать о том, чтобы в любом случае вернуть действительный указатель, не заставляя вызывающую программу удалить память строки... что не только неприятно, но и конфликтует с простотой других возвращаемых значений.
Поэтому я не могу придумать способ сделать это без изменения возвращаемого значения на параметр, в котором пользователь передает буфер и длину строки. Что сделало бы мою функцию похожей
BOOL GetDirectShowMessageDisplayText( int messageNumber, TCHAR* outBuffer, int bufferLength )
{
... etc ...
Я действительно не хочу этого делать. Должен быть лучший способ.
Есть?
Я возвращаюсь в C++ после 10-летнего перерыва, поэтому, если это что-то очевидно, не стоит сбрасывать со счетов то, что я пропустил это по какой-то причине.
9 ответов
C++? std:: string. Это не собирается разрушать производительность на любом современном компьютере.
Однако, если у вас есть необходимость чрезмерной оптимизации, у вас есть три варианта:
- Перейдите к буферу, который есть в вашем примере.
- Попросите пользователей удалить строку впоследствии. Многие подобные API предоставляют свою собственную функцию удаления для удаления каждого типа динамически размещаемых возвращаемых данных.
- Вернуть указатель на статический буфер, который вы заполняете строкой возврата при каждом вызове. Однако у этого есть некоторые недостатки: он не является потокобезопасным и может сбивать с толку, поскольку значение возвращаемого указателя изменится при следующем вызове функции. Если не-потокобезопасность приемлема, и вы документируете ограничения, все должно быть хорошо.
Просто объявите использование статической строки в качестве результата по умолчанию:
TCHAR* GetDirectShowMessageDisplayText( int messageNumber )
{
switch( messageNumber )
{
// ...
default:
static TCHAR[] default_value = "This is a default result...";
return default_value;
}
}
Вы также можете объявить "default_value" вне функции.
ОБНОВИТЬ:
Если вы хотите вставить номер сообщения в эту строку, то он не будет потокобезопасным (если вы используете несколько потоков). Тем не менее, решение этой проблемы заключается в использовании специфичной для потока строки. Вот пример использования Boost.Thread:
#include <cstdio>
#include <boost/thread/tss.hpp>
#define TCHAR char // This is just because I don't have TCHAR...
static void errorMessageCleanup (TCHAR *msg)
{
delete []msg;
}
static boost::thread_specific_ptr<TCHAR> errorMsg (errorMessageCleanup);
static TCHAR *
formatErrorMessage (int number)
{
static const size_t MSG_MAX_SIZE = 256;
if (errorMsg.get () == NULL)
errorMsg.reset (new TCHAR [MSG_MAX_SIZE]);
snprintf (errorMsg.get (), MSG_MAX_SIZE, "Unexpected notification code (%d)", number);
return errorMsg.get ();
}
int
main ()
{
printf ("Message: %s\n", formatErrorMessage (1));
}
Единственным ограничением этого решения является то, что возвращаемая строка не может быть передана клиентом в другой поток.
Вы уже используете _bstr_t
так что если вы можете просто вернуть их напрямую:
_bstr_t GetDirectShowMessageDisplayText(int messageNumber);
Если вам нужно создать другое сообщение во время выполнения, вы можете упаковать его в _bstr_t
тоже. Теперь право собственности ясно, и использование все еще просто благодаря RAII.
Накладные расходы незначительны (_bstr_t
использует подсчет ссылок) и вызывающий код все еще можно использовать _bstr_t
с преобразованием в wchar_t*
а также char*
если нужно.
Возможно, у вас есть статический строковый буфер, на который вы возвращаете указатель:
std::ostringstream ss;
ss << "Unexpected notification code (" << messageNumber << ")";
static string temp = ss.str(); // static string always has a buffer
return temp.c_str(); // return pointer to buffer
Это не потокобезопасно, и если вы постоянно удерживаете возвращенный указатель и вызываете его дважды с другим messageNumbers
все они указывают на один и тот же буфер в temp
- поэтому оба указателя теперь указывают на одно и то же сообщение. Решение? Вернуть std::string
из функции - это современный стиль C++, старайтесь избегать указателей и буферов в стиле C. (Похоже, вы можете изобрести tstring
который был бы std::string
в ANSI и std::wstring
в Unicode, хотя я бы порекомендовал просто перейти только на Unicode... У вас действительно есть причина поддерживать сборки не-Unicode?)
На первом раунде я пропустил, что это был вопрос C++, а не простой вопрос C. Наличие C++ под рукой открывает еще одну возможность: самоуправляемый класс указателей, который может сказать, удалять или нет.
class MsgText : public boost::noncopyable
{
const char* msg;
bool shouldDelete;
public:
MsgText(const char *msg, bool shouldDelete = false)
: msg(msg), shouldDelete(shouldDelete)
{}
~MsgText()
{
if (shouldDelete)
free(msg);
}
operator const char*() const
{
return msg;
}
};
const MsgText GetDirectShowMessageDisplayText(int messageNumber)
{
switch(messageNumber)
{
case EC_ACTIVATE:
return MsgText("A video window is being activated or deactivated.");
// etc
default: {
char *msg = asprintf("Undocumented message (%u)", messageNumber);
return MsgText(msg, true);
}
}
}
(Я не помню, если Windows CRT имеет asprintf
, но это довольно легко переписать выше на вершине std::string
если это не так.)
Обратите внимание на использование boost::noncopyable, однако - если вы копируете объект такого типа, вы рискуете получить двойное освобождение. К сожалению, это может вызвать проблемы с возвратом из вашей функции message-pretty-printer. Я не уверен, что правильный способ справиться с этим, я на самом деле не большой гуру C++.
Если вы возвращаете точку в строковую константу, вызывающая сторона не должна будет удалять строку - это будет необходимо только в том случае, если вы new
-ing памяти, используемой строкой каждый раз. Если вы просто возвращаете указатель на строковую запись в таблице сообщений об ошибках, я бы изменил тип возвращаемого значения на TCHAR const * const
и ты должен быть в порядке.
Конечно, это не помешает пользователям вашего кода попытаться удалить память, на которую ссылается указатель, но вы можете сделать так много, чтобы предотвратить злоупотребления.
Вы возвращаете какой-то самораспускающийся умный указатель или свой собственный класс строк. Вы должны следовать интерфейсу, как он определен в std::string для простоты использования.
class bstr_string {
_bstr_t contents;
public:
bool operator==(const bstr_string& eq);
...
~bstr_string() {
// free _bstr_t
}
};
В C++ вы никогда не имеете дело с необработанными указателями, если у вас нет веской причины, вы всегда используете самоуправляемые классы. Обычно Microsoft использует необработанные указатели, потому что они хотят, чтобы их интерфейсы были C-совместимыми, но если вам все равно, то не используйте необработанные указатели.
Кажется, простое решение - просто вернуть std::string
, Это подразумевает одно динамическое распределение памяти, но вы, вероятно, получите это в любом случае (так как пользователь или ваша функция должны были бы сделать это явно)
Альтернативой может быть предоставление пользователю возможности передавать итератор вывода, в который вы записываете строку. Затем пользователю предоставляется полный контроль над тем, как и когда распределять и хранить строку.
Здесь нет хорошего ответа, но этого клуджа может быть достаточно.
const char *GetDirectShowMessageDisplayText(int messageNumber)
{
switch(messageNumber)
{
// ...
default: {
static char defaultMessage[] = "Unexpected notification code #4294967296";
char *pos = defaultMessage + sizeof "Unexpected notification code #" - 1;
snprintf(pos, sizeof "4294967296" - 1, "%u", messageNumber);
return defaultMessage;
}
}
}
Если вы сделаете это, вызывающие должны знать, что строка, которую они возвращают из GetDirectShowMessageText, может быть засорена последующим вызовом функции. И это не потокобезопасно, очевидно. Но это могут быть приемлемые ограничения для вашего приложения.