【问题标题】:Is it possible to *safely* return a TCHAR* from a function?是否可以*安全地*从函数中返回 TCHAR*?
【发布时间】:2010-09-16 20:58:47
【问题描述】:

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

我有一堆像

这样的常量
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++,所以如果这是显而易见的事情,请不要小看我忽略了它是有原因的。

【问题讨论】:

  • 我不太清楚您要返回的消息是静态常量还是动态生成的。如果是前者,似乎没有问题,你可以直接返回指向它们的指针,因为它们永远不需要被释放。

标签: c++ function thread-safety return-type thread-specific-storage


【解决方案1】:

C++? std::string。它不会破坏任何现代计算机的性能。

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

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

【讨论】:

  • 如果他只想返回一个指向字符串常量的指针,那就太过分了。
  • std::string 在这里?即使这是 C++,这听起来也不好。在这里返回一个动态分配的字符串是没有意义的。
  • 我更喜欢 std::string 而不是不安全地使用指针 - 慢速和正确比快速和错误更好。无论哪种方式,它都可能足够快
  • @Vlad:但这个问题是关于 HFT 系统的吗?
  • @jalf:我没有说 std::string 不是线程安全的。这是矫枉过正。至于延迟,内存分配可能是个问题。
【解决方案2】:

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

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

【讨论】:

  • 谢谢。问题是,虽然我可以为所有已知的 messageNumber 返回 const TCHAR*,但我需要生成的默认值需要以某种方式分配。这就是我遇到问题的原因。
  • 默认是固定字符串吗?如果是这样的话,那么最简单的方法可能是 Zack 的建议。
【解决方案3】:

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

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

您也可以在函数之外声明“default_value”。

更新:

如果您想在该字符串中插入消息编号,那么它将不是线程安全的(如果您使用多个线程)。但是,该问题的解决方案是使用thread-specific 字符串。以下是使用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));
}

此方案的唯一限制是返回的字符串不能由客户端传递给其他线程。

【讨论】:

  • 谢谢,我考虑过,但我无法将 messageNumber 插入字符串中。
  • @John:抱歉,我没有注意到您想在该字符串中输入一个数字。我已经相应地更新了我的答案。
【解决方案4】:

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

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,这将是 ANSI 中的 std::string 和 unicode 中的 std::wstring,尽管我建议只使用 unicode ......你真的有任何理由支持非-unicode 构建?)

【讨论】:

  • 这可能是一个可接受的限制,具体取决于较大的代码试图做什么。
  • @Vlad 我在提到“如果您从多个线程调用”时暗示了这一点 - 已编辑以澄清。
【解决方案5】:

您返回某种自释放智能指针或您自己的自定义字符串类。您应该遵循 std::string 中定义的接口以便于使用。

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

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

【讨论】:

  • 正如我在回答中指出的那样,_bstr_t 已经完成了这项工作 - 请参阅 MSDN
【解决方案6】:

简单的解决方案似乎只是返回一个std::string。它确实意味着一个动态内存分配,但在任何情况下您都可能会得到它(因为用户或您的函数必须明确地进行分配)

另一种方法可能是允许用户传入您将字符串写入其中的输出迭代器。然后用户可以完全控制如何以及何时分配和存储字符串。

【讨论】:

    【解决方案7】:

    在第一轮我错过了这是一个 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++ 大师。

    【讨论】:

    • 这里相同:正如我在回答中指出的那样,_bstr_t 已经完成了这项工作 - 请参阅 MSDN
    【解决方案8】:

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

    _bstr_t GetDirectShowMessageDisplayText(int messageNumber);
    

    如果您需要在运行时构建不同的消息,您也可以将其打包到 _bstr_t 中。有了 RAII,现在所有权已经明确,使用仍然很简单。
    开销可以忽略不计(_bstr_t 使用引用计数),调用代码仍然可以使用 _bstr_ts 转换为 wchar_t*char*(如果需要)。

    【讨论】:

      【解决方案9】:

      这里没有好的答案,但这个杂牌可能就足够了。

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

      【讨论】:

      • 我认为这是一个完全有效的解决方案,并且我自己一直都在使用它。 (不过,说真的,每次只需让缓冲区 100 个字符和 snprintf 整个事情 - 更容易!)如果你愿意,你可以使用 TLS 来获得一些线程安全和/或一组缓冲区(使用循环)做一些事情,比如在另一个 printf 中多次调用函数等等。
      • 是的,我一直在努力避免这种打击。
      • @Vlad:你有没有错过我说的“这不是线程安全的”部分?线程安全并非始终是一项要求。
      猜你喜欢
      • 2011-01-23
      • 1970-01-01
      • 2018-02-25
      • 1970-01-01
      • 1970-01-01
      • 2020-10-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多