好的,好的。我一直在研究一个类项目(我们还没有涵盖std :: string和std :: vector,但显然我知道它们)来构建一个时钟。程序的主要部分需要时间和日期值作为格式化的c字符串(例如“12:45:45”,“12/12/12”等),我可能通过存储它们来保持简单我的基础课的方式。但是,我没有。
相反,我这样做了:
class UsageEntry {
public:
....
typedef time_t TimeType;
typedef int IDType;
...
// none of these getters are thread safe
// furthermore, the char* the getters return should be used immediately
// and then discarded: its contents will be modified on the next call
// to any of these functions.
const char* getUserID();
const char* getDate();
const char* getTimeIn();
const char* getTimeOut();
private:
IDType m_id;
TimeType m_timeIn;
TimeType m_timeOut;
char m_buf[LEN_MAX];
};
其中一个吸气剂(它们基本上都是一样的):
const char* UsageEntry::getDate()
{
strftime(m_buf, LEN_OF_DATE, "%D", localtime(&m_timeIn));
return m_buf;
}
这是一个使用这个指针的函数:
// ==== TDataSet::writeOut ====================================================
// writes an entry to the output file
void TDataSet::writeOut(int index, FILE* outFile)
{
// because of the m_buf kludge, this cannot be a single
// call to fprintf
fprintf(outFile, "%s,", m_data[index].getUserID());
fprintf(outFile, "%s,", m_data[index].getDate());
fprintf(outFile, "%s,", m_data[index].getTimeIn());
fprintf(outFile, "%s\n", m_data[index].getTimeOut());
fflush(outFile);
} // end of TDataSet::writeOut
这会造成多少麻烦?或者从另一个角度来看它,还有什么其他有趣的东西!!有趣!这会导致什么行为?最后,可以做些什么来解决它(除了使用字符串/向量的明显解决方案)?
有点相关:做类似事情的C ++库函数如何处理这个问题?例如localtime()返回一个指向struct tm对象的指针,该对象以某种方式在该函数调用结束时存活至少足够长,以供strftime使用。
答案 0 :(得分:2)
没有足够的信息来确定它是否会引起麻烦,因为您没有说明如何使用它。只要您记录警告并在使用课程时牢记这些警告,就不会有问题。
需要注意一些常见问题,但希望这些是常识:
删除UsageEntry
将使getter返回的指针无效,因为这些缓冲区也将被删除。 (如果在MadScienceDream's example中使用本地声明的UsageEntry
,则特别容易遇到这种情况。)如果存在风险,则调用者应创建自己的字符串副本。记下这一点。
m_timeIn
似乎不是const
,因此可能会发生变化。调用getter将修改内部缓冲区,并且具有该指针的任何内容都可以看到这些更改。如果这是一个风险,调用者应该创建自己的字符串副本。记下这一点。
您的获取者既不是可重入的也不是线程安全的。记下这一点。
让调用者提供目标缓冲区和长度作为参数会更安全。为方便起见,该函数可以返回指向该缓冲区的指针。这就是例如read
有效。
强大的API可以避免问题。如果做不到这一点,良好的文档和常识也可以减少问题的发生。行为只有在没有人期望的情况下才会出乎意料,这就是有关行为的文档很重要的原因:它通常会消除意外行为。
将其视为烤箱顶部的“小心:热表面”警告。您可以在顶部设计带有绝缘层的烤箱,这样就不会发生意外。如果做不到这一点,你至少可以做一个警告标签,可能不会出现意外。如果既没有绝缘也没有警告,最终有人将自行燃烧。
既然您已经编辑了问题以在标题中显示一些文档,那么许多初始风险已经降低。这是一个很好的改变。
以下是如果使用用户提供的缓冲区(以及返回指向该缓冲区的指针)如何更改使用情况的示例:
// ==== TDataSet::writeOut ====================================================
// writes an entry to the output file
void TDataSet::writeOut(int index, FILE* outFile)
{
char userId[LEN_MAX], date[LEN_MAX], timeIn[LEN_MAX], timeOut[LEN_MAX];
fprintf(outFile, "%s,%s,%s,%s\n",
m_data[index].getUserID(userId, sizeof(userId)),
m_data[index].getDate(date, sizeof(date)),
m_data[index].getTimeIn(timeIn, sizeof(timeIn)),
m_data[index].getTimeOut(timeOut, sizeof(timeOut))
);
fflush(outFile);
} // end of TDataSet::writeOut
答案 1 :(得分:2)
这里的主要问题是大多数基于指针的代码的主要问题是所有权问题。问题如下:
const char* val;
{
UsageEntry ue;
val = ue.getDate();
}//ue goes out of scope
std::cout << val << std::endl;//SEGFAULT (maybe, really nasal demons)
因为val
实际上归ue
所有,所以如果它们存在于不同的范围内,你就可以自己动手。你可以记录这个,但是将缓冲区作为参数传递是非常简单的(就像strftime
函数那样)。
(感谢下面的odedsh指出这个)
另一个问题是随后的通话会打消所获得的信息。使用的示例odesh是
fprintf(outFile, "%s\n%s",ue.getUserID(), ue.getDate());
但问题更为普遍:
const char* id = ue.getUserID();
const char* date = ue.getDate();//Changes id!
这违反了“最小惊讶的原则”,因为它很奇怪。
这种设计也打破了每个班级应该完成一件事的经验法则。在这种情况下,UsageEntry
都提供访问器以将格式化的时间作为字符串,并管理该字符串缓冲区。
答案 2 :(得分:2)
这会造成多少麻烦?或者从另一个角度来看, 还有什么其他的有趣和!!有趣!这会导致什么行为? 最后,可以做些什么来解决它(除了明显的解决方案 使用字符串/向量代替)?
这里没有什么特别好玩的,只是意味着你的getter的结果不能比UsageEntry
的相应实例更长,或者你有一个悬空指针。
做类似事情的C ++库函数如何处理这个? 例如localtime()返回一个指向struct tm对象的指针 不知怎的,这个函数调用的结束至少存活了很长时间 由strftime使用。
The documentation of localtime says:
返回值
成功时指向静态内部std :: tm对象的指针,否则为NULL。结构可以在之间共享 std :: gmtime,std :: localtime和std :: ctime,可能会被覆盖 每次调用。