5

在我的 C++ 上阅读了一下,发现这篇关于 RTTI(运行时类型识别)的文章:http: //msdn.microsoft.com/en-us/library/70ky2y6k (VS.80).aspx 。好吧,那是另一个主题 :) - 然而,我在 -class 中偶然发现了一个奇怪的说法type_info,即关于::name-method。它说:“type_info::name成员函数返回一个const char*以空字符结尾的字符串,表示该类型的人类可读名称。指向的内存被缓存,永远不应该直接释放。”

你怎么能自己实现这样的事情!?我以前经常为这个确切的问题苦苦挣扎,因为我不想char为调用者创建一个新的 -array 来删除,所以我一直坚持std::string至今。

所以,为了简单起见,假设我想做一个返回的方法"Hello World!",我们称之为

const char *getHelloString() const;

就个人而言,我会以某种方式做到这一点(伪):

const char *getHelloString() const
{
  char *returnVal = new char[13];
  strcpy("HelloWorld!", returnVal);

  return returnVal
}

.. 但这意味着调用者应该delete[]对我的返回指针执行 a :(

提前谢谢

4

12 回答 12

23

这个怎么样:

const char *getHelloString() const
{
    return "HelloWorld!";
}

直接返回文字意味着字符串的空间由编译器在静态存储中分配,并且在整个程序期间都可用。

于 2008-10-14T12:03:22.890 回答
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()第一次调用给定类型时,它会取消修饰名称,将其存储在堆分配的缓冲区中,缓存缓冲区指针并返回它。

链表也可用于在程序退出期间释放缓存的字符串(但是,我没有验证是否是这种情况)。这将确保在您运行内存调试工具时它们不会显示为内存泄漏。

于 2008-10-15T05:55:09.153 回答
2

哎呀,如果我们只是在谈论一个函数,那么您总是希望返回相同的值。这很简单。

const char * foo() 
{
   static char[] return_val= "HelloWorld!";
   return return_val;
}

棘手的一点是,当您开始缓存结果时,您必须考虑线程,或者当您的缓存失效时,并尝试将内容存储在线程本地存储中。但是,如果它只是立即复制的一次性输出,那么这应该可以解决问题。
或者,如果您没有固定大小,则必须做一些事情,您必须使用任意大小的静态缓冲区。在其中您最终可能会有太大的东西,或者转向托管类说std::string

const char * foo() 
{
   static std::string output;
   DoCalculation(output);
   return output.c_str();
}

也是函数签名

const char *getHelloString() const;

仅适用于成员函数。此时您不需要处理静态函数局部变量,只需使用成员变量即可。

于 2008-10-14T12:10:03.233 回答
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 都使用它们自己的堆,而且它们不是同一个堆,所以你从一个堆分配并从另一个堆释放。

于 2008-10-14T14:20:00.117 回答
1

我认为,既然他们知道它们的数量是有限的,他们就会永远保留它们。在某些情况下您可能会这样做,但作为一般规则,std::string 会更好。

他们还可以查找新调用以查看他们是否已经创建了该字符串并返回相同的指针。同样,根据您在做什么,这也可能对您有用。

于 2008-10-14T12:05:11.967 回答
0

Why does the return type need to be const? Don't think of the method as a get method, think of it as a create method. I've seen plenty of API that requires you to delete something a creation operator/method returns. Just make sure you note that in the documentation.

/* create a hello string
 * must be deleted after use
 */
char *createHelloString() const
{
  char *returnVal = new char[13];
  strcpy("HelloWorld!", returnVal);

  return returnVal
}
于 2008-10-14T13:27:05.310 回答
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;
    }

};
于 2008-10-14T13:35:51.987 回答
0

The advice given that warns about the lifetime of the returned string is sound advise. You should always be careful about recognising your responsibilities when it comes to managing the lifetime of returned pointers. The practise is quite safe, however, provided the variable pointed to will outlast the call to the function that returned it. Consider, for instance, the pointer to const char returned by c_str() as a method of class std::string. This is returning a pointer to the memory managed by the string object which is guaranteed to be valid as long as the string object is not deleted or made to reallocate its internal memory.

In the case of the std::type_info class, it is a part of the C++ standard as its namespace implies. The memory returned from name() is actually pointed to static memory created by the compiler and linker when the class was compiled and is a part of the run time type identification (RTTI) system. Because it refers to a symbol in code space, you should not attempt to delete it.

于 2008-10-14T17:54:20.823 回答
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 。这样,调用者可以随心所欲地保留它,而不必担心显式删除它。一个公平的妥协国际海事组织。

于 2008-10-14T13:51:45.837 回答
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 ..

于 2010-01-16T22:15:42.223 回答
-3

它可能是使用静态缓冲区完成的:

const char* GetHelloString()
{
    static char buffer[256] = { 0 };
    strcpy( buffer, "Hello World!" );
    return buffer;
}

这个缓冲区就像一个全局变量,只能从这个函数访问。

于 2008-10-14T12:07:08.670 回答
-5

你不能依赖 GC;这是 C++。这意味着您必须保持内存可用,直到程序终止。你根本不知道什么时候可以安全地删除[]它。所以,如果你想构造并返回一个 const char*,简单的 new[] 并返回它。接受不可避免的泄漏。

于 2008-10-14T12:10:19.070 回答