Можно ли * безопасно * вернуть 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. Это не собирается разрушать производительность на любом современном компьютере.

Однако, если у вас есть необходимость чрезмерной оптимизации, у вас есть три варианта:

  1. Перейдите к буферу, который есть в вашем примере.
  2. Попросите пользователей удалить строку впоследствии. Многие подобные API предоставляют свою собственную функцию удаления для удаления каждого типа динамически размещаемых возвращаемых данных.
  3. Вернуть указатель на статический буфер, который вы заполняете строкой возврата при каждом вызове. Однако у этого есть некоторые недостатки: он не является потокобезопасным и может сбивать с толку, поскольку значение возвращаемого указателя изменится при следующем вызове функции. Если не-потокобезопасность приемлема, и вы документируете ограничения, все должно быть хорошо.

Просто объявите использование статической строки в качестве результата по умолчанию:

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, может быть засорена последующим вызовом функции. И это не потокобезопасно, очевидно. Но это могут быть приемлемые ограничения для вашего приложения.

Другие вопросы по тегам