是否可以*安全*从函数返回TCHAR *?

时间:2010-09-16 20:58:47

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

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

我有一堆像

这样的竞争对手
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 ++,所以如果它是显而易见的,不要打折,因为某种原因我忽略了它。

9 个答案:

答案 0 :(得分:2)

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

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

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

答案 1 :(得分:0)

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

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

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

答案 2 :(得分:0)

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

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

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

答案 4 :(得分:0)

您已经使用_bstr_t,所以如果您可以直接退回这些内容:

_bstr_t GetDirectShowMessageDisplayText(int messageNumber);

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

答案 5 :(得分:0)

您返回某种自我释放的智能指针或您自己的自定义字符串类。您应该按照std :: string中定义的界面进行操作,以便最简单地使用。

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

在C ++中,除非有重要原因,否则永远不会处理原始指针,而是始终使用自我管理类。通常,Microsoft使用原始指针,因为他们希望它们的接口与C兼容,但如果你不在乎,那么就不要使用原始指针。

答案 6 :(得分:0)

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

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

答案 7 :(得分:0)

在第一轮中,我错过了这是一个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则很容易在{{1}}之上重写上述内容。)

请注意使用boost :: noncopyable - 如果你复制这种类型的对象,你冒着双重释放的风险。不幸的是,这可能会导致从消息漂亮的打印机功能返回它的问题。我不确定解决这个问题的正确方法是什么,我实际上并不是C ++专家。

答案 8 :(得分:-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返回的字符串可能会被后续调用该函数所破坏。显然,它不是线程安全的。但这些可能是您申请的可接受限制。