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

时间:2011-01-09 17:43:23

标签: c++

这是this question的后续行动。假设我编写了一个接受或返回const字符串的C ++接口。我可以使用const char *以零结尾的字符串:

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 ++ ish,当后续操作可能需要知道字符串长度时可能效率较低。
  • (2)很糟糕,因为现在f("long very long C string");调用std :: string的构造,它涉及堆分配。如果f使用该字符串只是为了将它传递给一个需要C字符串的低级接口(比如fopen),那么这只是浪费资源。
  • (3)导致代码重复。虽然一个f可以根据最有效的实现来调用另一个void f(boost::iterator_range<const char*> str); // (5) 。但是我们不能基于返回类型重载,比如std :: exception :: what()返回一个const char *。
  • (4)不能单独编译,可能会导致更大的代码膨胀。
  • 根据实现所需的内容在(1)和(2)之间进行选择,将实现细节泄露给接口。

问题是:什么是优先方式?我可以遵循任何单一指南吗?你有什么经历?

修改还有第五个选项:

{{1}}

具有(1)的优点(不需要构造字符串对象)和(2)(字符串的大小显式传递给函数)。

8 个答案:

答案 0 :(得分:7)

如果你正在处理纯C ++代码库,那么我会选择#2,而不用担心函数的调用者不会在出现问题之前将它与std :: string一起使用。一如既往,除非出现问题,否则不要过于担心优化问题。使您的代码干净,易于阅读,并且易于扩展。

答案 1 :(得分:4)

您可以遵循以下指南:使用(2)除非您有充分的理由不这样做。

作为参数的const char* str不明确,允许在str上执行哪些操作。在段错误之前它可以多久递增一次?它是指向charchar s数组还是C字符串(即char的零终止数组)的指针?

答案 2 :(得分:3)

我真的没有一个硬偏好。根据具体情况,我会在大多数示例之间进行替换。

我有时使用的另一个选项类似于Range示例,但使用普通的旧迭代器范围:

template <typename Iter>
void f(Iter first, Iter last);

具有很好的属性,它可以很容易地使用两种C风格的字符串(并允许被调用者在恒定时间内确定字符串的长度)以及std::string

如果模板有问题(可能是因为我不希望在头文件中定义函数),我有时会这样做,但使用char*作为迭代器:

void f(const char* first, const char* last);

同样,它可以简单地与C字符串和C ++ std::string一起使用(我记得,C ++ 03并没有明确要求字符串是连续的,但我知道的每个实现都使用连续分配字符串,我相信C ++ 0x将明确要求它。)

所以这些版本都允许我传递比普通C风格const char*参数更多的信息(它丢失有关字符串长度的信息,并且不处理嵌入的空值),除了支持两者之外

。主要的字符串类型(可能是你能想到的任何其他字符串类)。

缺点当然是你最终得到了一个额外的参数。

不幸的是,字符串处理并不是C ++最强大的一面,因此我认为没有一种“最佳”方法。但迭代器对是我倾向于使用的几种方法之一。

答案 3 :(得分:2)

为了获取参数,我会选择最简单的方法,通常是const char*。这适用于成本为零的字符串文字,并且从const char*中存储的内容中检索std:string通常也是非常低的成本。

就个人而言,我不会为过载而烦恼。除了最简单的情况之外,您将希望合并到两个代码路径,并在某个时刻调用另一个代码路径,或者两者都调用一个公共函数。可以说,过载会隐藏一个是否转换为另一个,哪个路径的成本更高。

只有当我真的想在函数中使用const接口的std::string功能时,我才会在接口本身中使用const std::string&并且我不确定只使用{{ 1}}就足够了。

在许多项目中,无论好坏,经常使用替代字符串类。其中许多内容(例如size())可以廉价访问零终止std::string;转换为const char*需要副本。即使函数的内部不需要指定存储策略,也要求接口中的std::string指示存储策略。我认为这是不合需要的,就像采用const std::string&指示存储策略一样,而如果可能的话,采用const shared_ptr<X>&允许调用者对传递的对象使用任何存储策略。

X&的缺点是,纯粹从接口角度来看,它不会强制执行非归零(尽管偶尔会在某些接口中使用null参数和空字符串之间的区别 - 这无法使用const char*)完成,而std::string可能只是一个字符的地址。但是在实践中,使用const char*传递一个字符串是如此普遍,以至于我认为这是一个负面因素,这是一个相当微不足道的问题。其他问题,例如接口文档中指定的字符编码(适用于const char*std::string)是否更为重要,可能会导致更多工作。

答案 4 :(得分:0)

  

也可以写一个   超载并接受两者:

由于从void f(const string& str)const char*的隐式转换,

std::string已经接受了这两种情况。所以#3比#2没有什么优势。

答案 5 :(得分:0)

答案应该在很大程度上取决于您在f中打算做什么。如果你需要对字符串进行一些复杂的处理,方法2是有意义的,如果你只需要传递给其他一些函数,然后根据其他函数进行选择(让我们说为了论证你打开一个文件 - 什么会最有意义吗?;))

答案 6 :(得分:0)

如果函数体不进行void f(const string& str),我会选择char - 分析;表示它不是指char*的{​​{1}}。

答案 7 :(得分:0)

使用(2)。

第一个陈述的问题不是问题,因为无论如何都必须在某个时刻创建字符串。

在第二点上烦恼过早优化的气味。除非您有特定的情况,即堆分配存在问题,例如使用字符串文字重复调用,并且这些不能更改,否则最好是明确避免这种陷阱。然后,只有这样你才会考虑选项(3)。

(2)清楚地传达了函数接受的内容,并且有正确的限制。

当然,所有5个都是foo(char*)的改进,我遇到的比我想要提到的更多。