2

我想为 C 风格的函数strftime编写一个方便的包装器。我已经找到了一些将字符数组转换为字符串的选项,反之亦然。这是我的代码:

std::string Time::getAsFormattedString ( const std::string& format , const size_t& maxStringSize = 999 )
{

    char* timeArray = 0;
    std::string timeString;

    //  [OPTION_0]
    timeArray = reinterpret_cast <char*> (malloc(sizeof(char)*maxStringSize)));

    //  [OPTION_1]
    timeArray = const_cast <char*> (timeString.c_str());

    //  [OPTION_2]
    timeArray = &(*(timeString.begin()));

    strftime(timeArray,maxStringSize,format.c_str(),&this->time);
    timeString = timeArray;

    //  [OPTION_0]
    free(timeArray);

    return timeString;

}

№0 选项看起来很安全,因为在释放内存之前不能抛出异常(编辑:timeString = timeArray可以抛出一个,在该行周围需要 try-catch)

№1 const-casting 总是看起来像一个 hack

№2 似乎是最好的,我不知道它是否会出现一些问题

你能告诉我,哪一个是最安全、正确、最优的,也许是一种最佳实践。

谢谢你。

4

5 回答 5

3

您提出的任何选项都不是真正可以接受的;第二个和第三个甚至都行不通。在全球范围内,有两种“可接受的”解决方案。

最简单的是:

char buffer[ 1000 ];
size_t n = strftime( buffer, sizeof( buffer ), format.c_str(), &time );
if ( n == 0 ) {
    throw SomeError;    //  or you might just abort...
}
return std::string( buffer );

这具有简单的优点,但您必须将最大尺寸记录为界面中的约束。(对我来说,这似乎是一个合理的限制。)

或者,您可以删除约束:

std::vector<char> buffer( 100 );
size_t n = strftime( &buffer[0], buffer.size(), format.c_str(), &time );
while ( n == 0 ) {
    buffer.resize( 2 * buffer.size() );
    n = strftime( &buffer[0], buffer.size(), format.c_str(), &time );
}
return std::string( buffer.begin(), buffer.begin() + n );

(在 C++11 中,实际上在 C++03 中,您可以 std::string直接使用而不是std::vector. 来执行此操作。在这种情况下,您需要resize( n )在返回结果字符串之前调用它。)

于 2013-06-20T08:49:38.127 回答
2

首先,唯一不会崩溃的选项是此处的选项 0。当你说你已经分配了 999 个字节时,其他的会崩溃,但实际上内部字符串可能只有 1 个字节分配给它,并且会发生悲伤的事情。

但是,我可能会通过在此处的堆栈上分配大量字符来做到这一点。

char timeArray[2048];
strftime(timeArray,2048,format.c_str(),&this->time);
return string(timeArray);

这样您就不必进行任何强制转换或动态分配,并且几乎可以肯定会更整洁、更快。

于 2013-06-20T08:23:26.860 回答
1

唯一可能的解决方案是选项 0(尽管可以进行一些小的调整)。正如其他人指出的那样,该标准表示该c_str()方法返回的空间并不用于写入目的,实际上它是一种允许std::string' 被 C 标准库(它是 C++ 的一部分)读取的方法标准库)。

标准内容如下:

返回: 指向长度为 size() + 1 的数组的初始元素的指针,其第一个 size() 元素等于由 *this 控制的字符串的相应元素,其最后一个元素是由 charT() 指定的空字符。

要求:程序不得更改存储在数组中的任何值。在任何后续调用指定与 this 相同的对象的类 basic_string 的非常量成员函数之后,程序也不会将返回值视为有效指针值。

所以,我只是对你的代码做一个快速修复:

const char * Time::getAsFormattedString(const std::string& format)
{
    static char timeArray[256];

    std::strftime( timeArray, 256, format.c_str(), &this->time );

    return timeArray;
}

这使得在程序启动时为您的方法创建缓冲区,并连续重用,因此不会出现内存错误(因为堆未被触及)。

唯一的问题是堆栈中有空间来创建将存储函数结果的字符串,但无论如何这将在调用函数后发生,函数本身不会触及堆,并且只有堆栈的最小值。

实际上,该函数的用处并未受到影响,因为它会自动转换为const char *to std::string,所以您可以安全地以通常的方式调用它:

std::string strTime = time.getAsFormattedString( "%F %T" );

希望这可以帮助。

于 2013-06-20T08:58:11.803 回答
1

选项 2(或者更好的是,&timeString[0])应该是首选。

关于选项 1 中的错误,您是对的const_cast,在选项 0 中,您至少可以通过使用new而不是malloc(并避免强制转换)来清理代码

但更喜欢选项2。

(哦,正如评论者指出的那样,如果您正在写入字符串本身,您显然必须首先将其调整为足够大,以免您写出越界。鉴于反对票,我可能应该是明确的)

于 2013-06-20T08:14:54.673 回答
1

选项 1 和 2 不好,因为您不打算更改从中获得的字符串std::string::c_str()(c 代表constant)。选项 2 需要对字符串进行“调整大小”才能使用它。但我不确定字符串是否能保证从它们的同一个缓冲区复制......

我的解决方案是:

char timeArray[1000];     

(虽然这太过分了。除非您多次重复相同的格式说明符,否则不太可能达到 100 个以上的字符,而且这非常冗长 - 所以“合理”的组合不会达到接近 1000 个字符的任何地方。)

请注意,这timeString = timeArray可能会引发异常bad_alloc,因此如果您不想在这种情况下泄漏内存,则需要使用基于堆栈的存储(如我的建议)、智能指针或围绕部分代码的 try/catch 块.

于 2013-06-20T08:23:47.977 回答