Можно ли * безопасно * вернуть TCHAR * из функции? - PullRequest
4 голосов
/ 17 сентября 2010

Я создал функцию, которая преобразует все коды уведомлений о событиях в строки.Довольно простые вещи на самом деле.

У меня есть куча констант типа

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 ]

2 голосов
/ 17 сентября 2010

C ++? станд :: строка . Это не повредит производительности на любом современном компьютере.

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

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

На первом круге я пропустил, что это был вопрос 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.Я не уверен, как правильно с этим справляться, на самом деле я не гуру С ++.

0 голосов
/ 17 сентября 2010

Кажется, простое решение - просто вернуть std::string.Это подразумевает одно динамическое выделение памяти, но вы, вероятно, получите это в любом случае (так как пользователь или ваша функция должны будут выполнить выделение явно)

Альтернативой может быть предоставление пользователю возможности передатьв выходном итераторе, в который вы записываете строку.Затем пользователю предоставляется полный контроль над тем, как и когда выделять и хранить строку.

0 голосов
/ 17 сентября 2010

Вы возвращаете какой-то самораспускающийся умный указатель или свой собственный класс строк. Вы должны следовать интерфейсу, как он определен в std :: string для простоты использования.

class bstr_string {
    _bstr_t contents;
public:
    bool operator==(const bstr_string& eq);
    ...
    ~bstr_string() {
        // free _bstr_t
    }
};

В C ++ вы никогда не имеете дело с необработанными указателями, если у вас нет веской причины, вы всегда используете самоуправляемые классы. Обычно Microsoft использует необработанные указатели, потому что они хотят, чтобы их интерфейсы были C-совместимыми, но если вам все равно, не используйте необработанные указатели.

0 голосов
/ 17 сентября 2010

Вы уже используете _bstr_t, поэтому, если вы можете просто вернуть их напрямую:

_bstr_t GetDirectShowMessageDisplayText(int messageNumber);

Если вам нужно создать другое сообщение во время выполнения, вы можете также упаковать его в _bstr_t.Теперь владение стало ясным, а использование по-прежнему простым благодаря RAII.
Издержки незначительны (_bstr_t использует подсчет ссылок), и вызывающий код все еще может использовать преобразование _bstr_t s вwchar_t* и char*, если необходимо.

0 голосов
/ 17 сентября 2010

Здесь нет хорошего ответа, но этого kludge может быть достаточно.

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

0 голосов
/ 17 сентября 2010

Возможно, есть статический строковый буфер, на который вы возвращаете указатель:

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 ... У вас действительно есть причина для поддержки не -уникод строит?)

0 голосов
/ 17 сентября 2010

Если вы возвращаете точку в строковую константу, вызывающая сторона не должна будет удалять строку - им придется только, если вы new -е памяти, используемой строкой каждый раз. Если вы просто возвращаете указатель на строковую запись в таблице сообщений об ошибках, я бы изменил тип возвращаемого значения на TCHAR const * const, и вы должны быть в порядке.

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

0 голосов
/ 17 сентября 2010

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

TCHAR* GetDirectShowMessageDisplayText( int messageNumber )
{
  switch( messageNumber )
  {
     // ...
     default:
       static TCHAR[] default_value = "This is a default result...";
       return default_value;
  }
}

Вы также можете объявить "default_value" вне функции.

UPDATE:

Если вы хотите вставить номер сообщения в эту строку, то он не будет поточно-ориентированным (если вы используете несколько потоков). Тем не менее, решение этой проблемы заключается в использовании специфичной для потока строки. Вот пример использования 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));
}

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...