4

我创建了一个将所有事件通知代码转换为字符串的函数。真的很简单的东西。

我有一堆像

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 ...

我真的不想那样做。一定会有更好的办法。

在那儿?

在中断了 10 年之后我又回到了 C++,所以如果这很明显,请不要小看我忽略了它是有原因的。

4

9 回答 9

2

C++?标准::字符串。它不会破坏任何现代计算机的性能。

但是,如果您需要对此进行过度优化,您有以下三种选择:

  1. 使用您的示例所具有的缓冲区。
  2. 之后让用户删除字符串。许多类似这样的 API 都提供了自己的删除函数,用于删除各种动态分配的返回数据。
  3. 返回指向静态缓冲区的指针,您在每次调用时用返回字符串填充该缓冲区。但是,这确实有一些缺点,因为它不是线程安全的,并且可能会令人困惑,因为返回的指针的值会在下次有人调用该函数时发生变化。如果非线程安全是可以接受的并且您记录了这些限制,那应该没问题。
于 2010-09-16T21:01:22.810 回答
0

The simple solution does seem to be to just return a std::string. It does imply one dynamic memory allocation, but you'd probably get that in any case (as either the user or your function would have to make the allocation explicitly)

An alternative might be to allow the user to pass in an output iterator which you write the string into. Then the user is given complete control over how and when to allocate and store the string.

于 2010-09-16T21:57:45.343 回答
0

只需声明使用静态字符串作为默认结果:

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));
}

此解决方案的唯一限制是客户端无法将返回的字符串传递给其他线程。

于 2010-09-16T21:03:09.537 回答
0

如果您要返回一个指向字符串常量的点,则调用者将不必删除该字符串 - 只有在您new每次都使用该字符串使用的内存时,他们才需要这样做。如果您只是在错误消息表中返回指向字符串条目的指针,我会将返回类型更改为TCHAR const * const,您应该没问题。

当然,这不会阻止您的代码的用户尝试删除指针引用的内存,但是您可以做的只有这么多来防止滥用。

于 2010-09-16T21:03:27.270 回答
0

也许有一个静态字符串缓冲区,您返回一个指向:

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所以两个指针现在都指向同一个消息。解决方案?从函数返回 a std::string- 这是现代 C++ 风格,尽量避免使用 C 风格的指针和缓冲区。(看起来你可能想发明一个tstringstd::stringANSI 和std::wstringunicode 中的,虽然我建议只使用 unicode...你真的有任何理由支持非 unicode 构建吗?)

于 2010-09-16T21:08:35.163 回答
0

您返回某种自释放智能指针或您自己的自定义字符串类。您应该按照 std::string 中定义的接口进行操作,以便于使用。

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

在 C++ 中,除非有重要原因,否则您永远不会处理原始指针,您总是使用自管理类。通常,Microsoft 使用原始指针是因为他们希望其接口与 C 兼容,但如果您不在乎,请不要使用原始指针。

于 2010-09-16T21:31:50.960 回答
0

你已经使用了_bstr_t,所以如果你可以直接返回它们:

_bstr_t GetDirectShowMessageDisplayText(int messageNumber);

如果您需要在运行时构建不同的消息,您也可以将其打包成一个_bstr_t。现在所有权清晰,使用仍然很简单,这要归功于 RAII。
开销可以忽略不计(_bstr_t使用引用计数),调用代码仍然可以使用_bstr_ts 转换为wchar_t*char*如果需要。

于 2010-09-16T21:21:54.800 回答
0

On the first go-round I missed that this was a C++ question rather than a plain C question. Having C++ to hand opens up another possibility: a self-managing pointer class that can be told whether or not to delete.

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);
    }
  }
}

(I don't remember if Windows CRT has asprintf, but it's easy enough to rewrite the above on top of std::string if it doesn't.)

Note the use of boost::noncopyable, though - if you copy this kind of object you risk double frees. Unfortunately, that may cause problems with returning it from your message-pretty-printer function. I'm not sure what the right way to deal with that is, I'm not actually much of a C++ guru.

于 2010-09-16T22:17:36.930 回答
-1

这里没有很好的答案,但这个 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 返回的字符串可能会被随后对该函数的调用破坏。显然,它不是线程安全的。但这些可能是您的应用程序可以接受的限制。

于 2010-09-16T21:12:47.747 回答