在我的C ++上读了一下,发现这篇关于RTTI(运行时类型识别)的文章:
http://msdn.microsoft.com/en-us/library/70ky2y6k(VS.80).aspx。好吧,这是另一个主题:) - 但是,我在type_info
- 类中偶然发现了一个奇怪的说法,即::name
- 方法。它说:“type_info::name
成员函数返回一个const char*
到一个以空字符结尾的字符串,表示该类型的人类可读名称。指向的内存是缓存的,永远不应该直接释放。”< / p>
你怎么能自己实现这样的东西!?我以前经常在这个问题上苦苦挣扎,因为我不想为调用者删除一个新的char
- 数组,所以我坚持std::string
因此远。
所以,为了简单起见,假设我想创建一个返回"Hello World!"
的方法,让我们称之为
const char *getHelloString() const;
就个人而言,我会以某种方式(Pseudo):
const char *getHelloString() const
{
char *returnVal = new char[13];
strcpy("HelloWorld!", returnVal);
return returnVal
}
..但这意味着调用者应该在我的返回指针上执行delete[]
:(
提前谢谢
答案 0 :(得分:23)
这个怎么样:
const char *getHelloString() const
{
return "HelloWorld!";
}
直接返回文字意味着字符串的空间由编译器在静态存储中分配,并且在程序的整个过程中都可用。
答案 1 :(得分:3)
我喜欢关于如何静态分配字符串的所有答案,但对于所有实现来说并不一定如此,尤其是原始海报链接到其文档的那些实现。在这种情况下,看起来装饰类型名称是静态存储的,以节省空间,未修饰的类型名称按需计算并缓存在链表中。
如果您对Visual C ++ type_info::name()
实现如何分配和缓存其内存感到好奇,那么就不难发现。首先,创建一个微小的测试程序:
#include <cstdio>
#include <typeinfo>
#include <vector>
int main(int argc, char* argv[]) {
std::vector<int> v;
const type_info& ti = typeid(v);
const char* n = ti.name();
printf("%s\n", n);
return 0;
}
构建它并在调试器下运行它(我使用WinDbg)并查看type_info::name()
返回的指针。它是否指向全球结构?如果是这样,WinDbg的ln
命令将告诉最接近的符号的名称:
0:000> ?? n
char * 0x00000000`00857290
"class std::vector<int,class std::allocator<int> >"
0:000> ln 0x00000000`00857290
0:000>
ln
没有打印任何内容,这表明该字符串不在任何特定模块所拥有的地址范围内。如果它在数据或只读数据段中,它将在该范围内。让我们看一下它是否在堆上分配,通过搜索所有堆来查找type_info::name()
返回的地址:
0:000> !heap -x 0x00000000`00857290
Entry User Heap Segment Size PrevSize Unused Flags
-------------------------------------------------------------------------------------------------------------
0000000000857280 0000000000857290 0000000000850000 0000000000850000 70 40 3e busy extra fill
是的,它是在堆上分配的。在malloc()
的开头放置断点并重新启动程序会确认它。
查看<typeinfo>
中的声明,可以了解堆指针缓存的位置:
struct __type_info_node {
void *memPtr;
__type_info_node* next;
};
extern __type_info_node __type_info_root_node;
...
_CRTIMP_PURE const char* __CLR_OR_THIS_CALL name(__type_info_node* __ptype_info_node = &__type_info_root_node) const;
如果找到__type_info_root_node
的地址并在调试器中的列表中向下走,则会很快找到包含type_info::name()
返回的相同地址的节点。该列表似乎与缓存方案有关。
原始问题中链接的MSDN页面似乎填写了空白:名称以其装饰形式存储以节省空间,此表单可通过type_info::raw_name()
访问。当您在给定类型上第一次调用type_info::name()
时,它会取消设计名称,将其存储在堆分配的缓冲区中,缓存缓冲区指针并返回它。
链接列表也可用于在程序退出期间解除分配缓存的字符串(但是,我没有验证是否是这种情况)。这将确保在运行内存调试工具时它们不会显示为内存泄漏。
答案 2 :(得分:2)
嗯,如果我们只谈论一个函数,那么你总是希望返回相同的值。这很简单。
const char * foo()
{
static char[] return_val= "HelloWorld!";
return return_val;
}
棘手的一点是当你开始做缓存结果的事情,然后你必须考虑线程,或者你的缓存失效,并试图将东西存储在线程本地存储中。但如果它只是立即复制的一次性输出,这应该可以解决问题 或者,如果你没有固定的大小,你必须做一些事情,你必须使用任意大小的静态缓冲区...你可能最终有一个太大的东西,或转向托管类说{{1} }。
std::string
也是功能签名
const char * foo()
{
static std::string output;
DoCalculation(output);
return output.c_str();
}
仅适用于会员功能。 此时您不需要处理静态函数局部变量,只能使用成员变量。
答案 3 :(得分:1)
我认为,因为他们知道这些数量有限,所以他们只是永远保持它们。在某些情况下,您可能适合这样做,但作为一般规则,std :: string会更好。
他们也可以查找新的调用,看看他们是否已经创建了该字符串并返回相同的指针。同样,根据您的工作情况,这对您也很有用。
答案 4 :(得分:1)
在实现分配一块内存然后期望调用者释放它的函数时要小心,就像在OP中那样:
const char *getHelloString() const
{
char *returnVal = new char[13];
strcpy("HelloWorld!", returnVal);
return returnVal
}
通过这样做,您将内存的所有权转移给调用者。如果从其他函数调用此代码:
int main()
{
char * str = getHelloString();
delete str;
return 0;
}
...转移内存所有权的语义不明确,造成了更有可能出现错误和内存泄漏的情况。
此外,至少在Windows下,如果这两个函数位于2个不同的模块中,则可能会损坏堆。特别是,如果main()在hello.exe中,在VC9中编译,并且getHelloString()在utility.dll中,在VC6中编译,则在删除内存时会破坏堆。这是因为VC6和VC9都使用自己的堆,并且它们不是同一个堆,因此您从一个堆分配并从另一个堆中释放。
答案 5 :(得分:0)
为什么返回类型需要为const
?不要将该方法视为 get 方法,将其视为 create 方法。我见过很多API,要求你删除创建操作符/方法返回的内容。请确保在文档中注明。
/* create a hello string
* must be deleted after use
*/
char *createHelloString() const
{
char *returnVal = new char[13];
strcpy("HelloWorld!", returnVal);
return returnVal
}
答案 6 :(得分:0)
当我需要这种功能时,我经常做的是在类中有一个char *指针 - 初始化为null - 并在需要时分配。
即:
class CacheNameString
{
private:
char *name;
public:
CacheNameString():name(NULL) { }
const char *make_name(const char *v)
{
if (name != NULL)
free(name);
name = strdup(v);
return name;
}
};
答案 7 :(得分:0)
这样的事情可以做到:
const char *myfunction() {
static char *str = NULL; /* this only happens once */
delete [] str; /* delete previous cached version */
str = new char[strlen("whatever") + 1]; /* allocate space for the string and it's NUL terminator */
strcpy(str, "whatever");
return str;
}
编辑:我发生的事情是,对此的一个很好的替代可能是返回一个boost :: shared_pointer。这样,调用者只要他们想要就可以抓住它,并且他们不必担心明确地删除它。公平妥协IMO。
答案 8 :(得分:0)
给出的建议是警告返回字符串的生命周期。在管理返回指针的生命周期时,您应该始终小心识别自己的责任。但是,如果指向的变量将比返回它的函数的调用更久,那么这种做法是非常安全的。例如,考虑c_str()
作为类std::string
的方法返回的const char指针。这将返回一个指向由字符串对象管理的内存的指针,只要字符串对象未被删除或重新分配其内部存储器,该指针就保证有效。
在std::type_info
类的情况下,它是C ++标准的一部分,因为它的命名空间意味着。从name()
返回的内存实际上是指编译器和链接器在编译类时创建的静态内存,它是运行时类型标识(RTTI)系统的一部分。因为它引用代码空间中的符号,所以不应尝试删除它。
答案 9 :(得分:0)
我认为这样的事情只能使用对象和RAII成语“干净地”实现。
当调用对象析构函数(obj超出范围)时,我们可以安全地假设const char*
指针不再被使用。
示例代码:
class ICanReturnConstChars
{
std::stack<char*> cached_strings
public:
const char* yeahGiveItToMe(){
char* newmem = new char[something];
//write something to newmem
cached_strings.push_back(newmem);
return newmem;
}
~ICanReturnConstChars(){
while(!cached_strings.empty()){
delete [] cached_strings.back()
cached_strings.pop_back()
}
}
};
我所知道的另一种可能性是传递一个smart_ptr ..
答案 10 :(得分:-3)
可能是使用静态缓冲区完成的:
const char* GetHelloString()
{
static char buffer[256] = { 0 };
strcpy( buffer, "Hello World!" );
return buffer;
}
此缓冲区就像一个只能从此函数访问的全局变量。
答案 11 :(得分:-5)
你不能依赖GC;这是C ++。这意味着您必须保持内存可用,直到程序终止。您根本不知道何时删除[]它是安全的。所以,如果你想构造并返回一个const char *,简单的new []它并返回它。接受不可避免的泄漏。