我将从榜样开始。在boost中有一个很好的“tokenizer”类。它需要将一个字符串标记为构造函数中的参数:
std::string string_to_tokenize("a bb ccc ddd 0");
boost::tokenizer<boost::char_separator<char> > my_tok(string_to_tokenize);
/* do something with my_tok */
该字符串未在tokenizer中修改,因此它由const对象引用传递。因此我可以在那里传递一个临时对象:
boost::tokenizer<boost::char_separator<char> > my_tok(std::string("a bb ccc ddd 0"));
/* do something with my_tok */
一切看起来都不错,但如果我尝试使用标记器,就会发生灾难。经过简短的调查,我意识到,tokenizer类存储了我给它的引用,并在进一步使用时使用。当然,它不能很好地引用临时对象。
文档没有明确说明在构造函数中传递的对象将在以后使用,但是好的,也没有说明,它不会是:)所以我不能假设这个,我的错误。
然而,这有点令人困惑。一般情况下,当一个对象通过const引用获取另一个对象时,它表明可以在那里给出临时对象。你怎么看?这是一个糟糕的习俗吗?在这种情况下,是否应该使用指向对象(而不是引用)的指针?或者甚至更进一步 - 对于允许/禁止给临时对象作为参数的参数有一些特殊的关键字是不是很有用?编辑:文档(版本1.49)相当简约,唯一可能表明存在此类问题的部分是:
注意:在构造时实际上没有进行解析。解析是按需完成的,因为通过begin提供的迭代器访问令牌。
但它没有明确说明,将使用给定的相同对象。
然而,这个问题的重点是在这种情况下讨论编码风格,这只是启发我的一个例子。
答案 0 :(得分:11)
如果某些函数(例如构造函数)将参数作为引用引用,那么它应该
或
在这种特殊情况下(boost::tokenizer
类)我假设后者不是出于性能原因和/或使类可用于第一个甚至不可复制的容器类型地点。出于这个原因,我认为这是一个文档错误。
答案 1 :(得分:8)
就我个人而言,我认为这是一个坏主意,最好是编写构造函数来复制字符串,或者改为采用const std::string*
。调用者只需输入一个额外字符,但该字符会意外地使用临时字符来阻止它们。
作为一项规则:不要在没有明显表明自己有责任的情况下为人们创造维护对象的责任。
我认为一个特殊的关键字不足以证明改变语言的合理性。实际上并不是临时性的问题,它的任何对象的生存时间都比构造的对象少。在某些情况下,临时会很好(例如,如果tokenizer
对象本身也是同一个完整表达式中的临时对象)。我真的不想为了半个修复而弄乱语言,并且有更全面的修复(例如,使用shared_ptr
,虽然这有其自身的问题)。
“所以我不能假设这个,我的错误”
我认为这不是你的错误,我同意Frerich的意见,并且反对我的个人风格指南完全执行此操作,如果你这样做并且不记录那么这是任何文档错误合理的风格指南。
如果除了“至少与函数调用一样长”之外的任何其他内容,则必须记录所引用的参考函数参数的所需生命周期。这是文档经常松懈的事情,需要妥善完成以避免错误。
即使在垃圾收集语言中,生命周期本身也会自动处理,因此往往会被忽略,但是在不改变传递给对象的其他对象的行为的情况下,是否可以更改或重新使用对象很重要。过去一段时间的方法。因此,函数应该记录它们是否在任何语言中保留了参数的别名,这些语言缺少参照透明度。特别是在C ++中,对象生存期是调用者的问题。
不幸的是,实际确保您的函数无法保留引用的唯一机制是传递值,这会带来性能成本。如果你可以发明一种通常允许别名的语言,但是也有一个C风格的restrict
属性,它在编译时强制执行,const-style,以防止函数甩掉对它们参数的引用,那么祝你好运并报名参加。
答案 2 :(得分:3)
正如其他人所说,boost::tokenizer
示例是tokenizer
中的错误或文档中缺少警告的结果。
为了回答这个问题,我发现以下优先级列表很有用。如果由于某种原因无法选择选项,请转到下一个项目。
此外,如果您从列表中选择下一个项目的理由是“性能”,那么请坐下来测量差异。根据我的经验,大多数人(特别是Java或C#背景)往往高估了按值传递对象的成本(并低估了解除引用的成本)。通过值传递是最安全的选择(它不会在对象或函数之外引起任何意外,甚至在另一个线程中也不会引起任何意外),不要轻易放弃这个巨大的优势。
答案 3 :(得分:1)
很多时候它取决于上下文,例如,如果它是一个将在for_each或类似函数中调用的仿函数,那么你经常会在你的仿函数中将一个引用或一个指针存储到你期望的对象中。超越你的仿函数的生命。
如果它是一般用途类,那么你必须考虑人们将如何使用它。
如果你正在编写一个标记化器,你需要考虑复制你正在标记的东西可能是昂贵的,但你还需要考虑如果你正在编写一个boost库,你正在为将要使用的普通公众编写它它以多用途的方式。
在此处存储const char *
会优于std::string const&
。如果用户有std::string
,那么const char *
只要不修改字符串就会保持有效,但他们可能不会。如果他们有一个const char *或者包含一系列字符的东西并将其传入,那么无论如何它都会复制它以创建std::string const &
并且你很有可能它不会活过你的构造
当然,使用const char *,您无法在实现中使用所有可爱的std::basic_string
函数。
有一个选项可以作为参数,一个std::string&
(非const引用),它应该保证(使用兼容的编译器)没有人会传递一个临时的,但你将能够记录你实际上并没有改变它,以及你看似不正确的代码背后的基本原理。注意,我在代码中也使用了一次这个技巧。你可以愉快地使用字符串的查找功能。 (以及,如果您愿意,可以使用basic_string而不是字符串,这样您也可以对宽字符串进行标记)。