字符串接受界面应该如何?

问题描述:

这是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)(所述的尺寸的优点字符串显式传递给函数)。

+0

)。字符串将在堆栈上构建 – 2011-01-09 17:58:52

+0

@nice:正确的,std :: string本身被分配到堆栈上。但是如果你的字符串足够长或者你的实现没有使用短字符串优化,那么std :: string将在堆上分配它的存储空间。 – ybungalobill 2011-01-09 18:02:12

用于拍摄参数我会用什么是最简单的,往往是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::stringconst char*)是否更重要,可能会导致更多工作。

如果你正在处理一个纯粹的C++代码库,那么我会去#2,而不用担心函数的调用者不会在std :: string中使用它,直到出现问题。与往常一样,除非出现问题,否则不要过多担心优化。让你的代码干净,易于阅读,并且易于扩展。

+0

但是,为什么你比较喜欢2 1?它不会更干净,更容易阅读或更容易扩展! – ybungalobill 2011-01-09 17:48:48

+1

@ybungalobill:因为如果我正在编写C++,我宁愿处理C++构造,除非遇到需要开始处理的性能问题。 – 2011-01-09 17:49:47

+3

@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几乎没有什么优势。

+1

它避免了转换。它允许实现决定哪个版本更好。 – ybungalobill 2011-01-09 17:53:11

答案应该在很大程度上依赖于您打算在f做什么。如果你需要对字符串进行一些复杂的处理,那么方法2是有意义的,如果你只需要传递给其他函数,那么就根据其他函数进行选择(比方说,为了参数,你打开一个文件 - 会怎样最有意义的;))

+0

这泄漏了一个实现细节。这正是我想要避免的。 – ybungalobill 2011-01-09 17:57:03

我会选择void f(const string& str)如果函数体没有做char -analysis;意味着它不是指strchar*

使用(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)在没有堆分配的情况下(