这是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)
我的想法是:
f("long very long C string");
调用std :: string的构造,它涉及堆分配。如果f
使用该字符串只是为了将它传递给一个需要C字符串的低级接口(比如fopen),那么这只是浪费资源。f
可以根据最有效的实现来调用另一个void f(boost::iterator_range<const char*> str); // (5)
。但是我们不能基于返回类型重载,比如std :: exception :: what()返回一个const char *。问题是:什么是优先方式?我可以遵循任何单一指南吗?你有什么经历?
修改还有第五个选项:
{{1}}
具有(1)的优点(不需要构造字符串对象)和(2)(字符串的大小显式传递给函数)。
答案 0 :(得分:7)
如果你正在处理纯C ++代码库,那么我会选择#2,而不用担心函数的调用者不会在出现问题之前将它与std :: string一起使用。一如既往,除非出现问题,否则不要过于担心优化问题。使您的代码干净,易于阅读,并且易于扩展。
答案 1 :(得分:4)
您可以遵循以下指南:使用(2)除非您有充分的理由不这样做。
作为参数的const char* str
不明确,允许在str
上执行哪些操作。在段错误之前它可以多久递增一次?它是指向char
,char
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*)
的改进,我遇到的比我想要提到的更多。