字符串接受界面应该如何?
这是this question的后续行动。假设我编写了一个接受或返回一个常量字符串的C++接口。我可以用一个const char * 0结尾的字符串:字符串接受界面应该如何?
void f(const char* str); // (1)
另一种方法是使用一个std :: string的:
void f(const string& str); // (2)
它也可以编写过载,同时接受:
void f(const char* str); // (3)
void f(const string& str);
或与升压字符串算法相结合,甚至一个模板:
template<class Range> void f(const Range& str); // (4)
我的想法是:
- (1)不是C++杂交,并且可以是效率较低时随后的操作可能需要知道字符串长度。
- (2)不好,因为现在
f("long very long C string");
调用std :: string的构造,涉及堆分配。如果f
使用该字符串只是将其传递给一个需要C字符串的低级接口(如fopen),那么这只是浪费资源。 - (3)导致代码重复。尽管一个
f
可以根据最有效的实现来调用另一个。然而,我们不能根据返回类型来重载,就像std :: exception :: what()那样返回一个const char *。 - (4)不适用于单独的编译,并可能导致更大的代码膨胀。
- 根据实现需要什么来选择(1)和(2),好吧,将实现细节泄漏到接口。
问题是:什么是优先的方式?是否有任何单一的指导方针可以遵循?你有什么经验?
编辑:还有一个第五个选项:
void f(boost::iterator_range<const char*> str); // (5)
其具有(1)(不需要构造一个字符串对象)和(2)(所述的尺寸的优点字符串显式传递给函数)。
用于拍摄参数我会用什么是最简单的,往往是const char*
去。这与零成本的字符串文字一起工作,并且从存储在std:string
中的东西检索const char*
通常也是非常低的成本。
就个人而言,我不会过载打扰。除了最简单的情况外,您都希望合并到两个代码路径中,并且在某个时刻有一个调用另一个代码路径,或者都调用一个常用函数。有人可能会认为,过载会隐藏一个人是否转换为另一个,哪个路径的成本较高。
只有当我真正想用函数内部的std::string
接口const
功能我会在界面本身const std::string&
,我不知道,只是用size()
就足够的理由的。
在许多项目中,是好还是坏,替代字符串类经常被使用。其中很多,如std::string
可以便宜地访问零终止的const char*
;转换为std::string
需要一份副本。在接口中需要const std::string&
即使在函数的内部不需要指定时也会规定存储策略。我认为这是不可取的,就像采取const shared_ptr<X>&
指示存储策略,而采用X&
(如果可能),允许调用者为传递的对象使用任何存储策略。
const char*
一的缺点是,单纯从界面的角度来看,它不执行非NULL的含量(尽管很偶然betweem null参数和一个空字符串的差在一些接口使用 - 这不能可以用std::string
来完成),而const char*
可能只是单个字符的地址。然而,在实践中,使用const char*
来传递一个字符串非常普遍,所以我认为这是一个相当微不足道的担忧。其他问题,例如接口文档中指定的字符编码(适用于std::string
和const char*
)是否更重要,可能会导致更多工作。
如果你正在处理一个纯粹的C++代码库,那么我会去#2,而不用担心函数的调用者不会在std :: string中使用它,直到出现问题。与往常一样,除非出现问题,否则不要过多担心优化。让你的代码干净,易于阅读,并且易于扩展。
但是,为什么你比较喜欢2 1?它不会更干净,更容易阅读或更容易扩展! – ybungalobill 2011-01-09 17:48:48
@ybungalobill:因为如果我正在编写C++,我宁愿处理C++构造,除非遇到需要开始处理的性能问题。 – 2011-01-09 17:49:47
@ybungalobill`const char * str`是根据定义指向char *的*指针。这是一个*字符串*只是按照惯例。这就是为什么在C++中,2更干净。 – Oswald 2011-01-09 17:54:23
您可以遵循一条指导原则:使用(2)除非您有很好的理由不要。
A const char* str
作为参数没有明确说明,允许在str
上执行哪些操作。在它发生段错误之前,它多久可以增加一次?它是指向一个char
,一个char
的数组还是一个C字符串(即一个零终止数组char
)?
它也可以写一个 超载,同时接受:
void f(const string& str)
已经接受这既是因为从const char*
到std::string
的隐式转换。所以#3比#2几乎没有什么优势。
它避免了转换。它允许实现决定哪个版本更好。 – ybungalobill 2011-01-09 17:53:11
答案应该在很大程度上依赖于您打算在f
做什么。如果你需要对字符串进行一些复杂的处理,那么方法2是有意义的,如果你只需要传递给其他函数,那么就根据其他函数进行选择(比方说,为了参数,你打开一个文件 - 会怎样最有意义的;))
这泄漏了一个实现细节。这正是我想要避免的。 – ybungalobill 2011-01-09 17:57:03
我会选择void f(const string& str)
如果函数体没有做char
-analysis;意味着它不是指str
的char*
。
使用(2)。
首先指出问题,它不是一个问题,因为字符串有在某些时候要创建不管。
烦恼于第二点气味过早的优化。除非你有堆分配有问题的特定情况,例如重复的字符串文字调用,而且这些情况不能改变,那么最好避免使用这种缺陷。然后才可以考虑选项(3)。 (2)明确传达该功能所接受的内容,并且具有正确的限制。
当然,所有5个都比foo(char*)
有所改进,我遇到的不止是我会提及的。
我真的没有一个硬性偏好。根据具体情况,我会在大部分示例中进行交替。
我有时用另一种方法是类似于您Range
例子,但使用普通的旧迭代器范围:
template <typename Iter>
void f(Iter first, Iter last);
它有它用C风格的字符串工作轻松(和允许被叫很好的特性以恒定时间确定字符串的长度)以及std::string
。
如果模板是有问题的(也许是因为我不想在头要定义的函数),我有时会做同样的,但使用char*
迭代器:
void f(const char* first, const char* last);
同样,也可以是(我记得,C++ 03没有明确要求字符串是连续的,但我知道的每个实现都使用连续分配的字符串,并且我相信C++ 0x将明确要求它)。
因此,除了支持两种主要字符串类型之外,这些版本都允许传递比普通C风格const char*
参数(丢失关于字符串长度的信息,并且不处理嵌入式空值)更多的信息(也可能是其他任何你能想到的字符串类)。
不利的一面是你最终得到一个额外的参数。
不幸的是,字符串处理并不是真正的C++最强大的一面,所以我不认为有一个“最好”的方法。但迭代器对是我倾向于使用的几种方法之一。 (2)在没有堆分配的情况下(
)。字符串将在堆栈上构建 – 2011-01-09 17:58:52
@nice:正确的,std :: string本身被分配到堆栈上。但是如果你的字符串足够长或者你的实现没有使用短字符串优化,那么std :: string将在堆上分配它的存储空间。 – ybungalobill 2011-01-09 18:02:12