是否可以*安全地*从函数中返回 TCHAR*?
Posted
技术标签:
【中文标题】是否可以*安全地*从函数中返回 TCHAR*?【英文标题】:Is it possible to *safely* return a TCHAR* from a function? 【发布时间】: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++,所以如果这是显而易见的事情,请不要小看我忽略了它是有原因的。
【问题讨论】:
我不太清楚您要返回的消息是静态常量还是动态生成的。如果是前者,似乎没有问题,你可以直接返回指向它们的指针,因为它们永远不需要被释放。 【参考方案1】:C++? std::string。它不会破坏任何现代计算机的性能。
但是,如果您需要对此进行过度优化,您有以下三种选择:
-
使用示例中的缓冲区。
之后让用户删除该字符串。很多类似这样的 API 都提供了自己的删除函数,用于删除各种动态分配的返回数据。
返回一个指向静态缓冲区的指针,您在每次调用时用返回字符串填充该缓冲区。但是,这确实有一些缺点,因为它不是线程安全的,并且可能会令人困惑,因为返回的指针的值会在下次有人调用该函数时发生变化。如果非线程安全是可以接受的,并且您记录了这些限制,那应该没问题。
【讨论】:
如果他只想返回一个指向字符串常量的指针,那就太过分了。 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_t
s 转换为 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:你有没有错过我说的“这不是线程安全的”部分?线程安全并非始终是一项要求。以上是关于是否可以*安全地*从函数中返回 TCHAR*?的主要内容,如果未能解决你的问题,请参考以下文章