为什么没有从 std: : string_view 到 std: : string 的隐式转换?

有一个从 std::stringstd::string_view的隐式转换,它不被认为是不安全的,尽管如果程序员不小心的话,这肯定会导致许多悬空引用。

另一方面,没有使用相同的参数从 std::string_viewstd::string的隐式转换,而是以完全相反的方式: 因为程序员可能不小心

C + + 有了一个原始 const char*指针的替代品,这真是太可爱了,同时也让它变得非常混乱,简直就是一个彻头彻尾的东西:

  • 隐式 const char*-> std::string: 好的
  • 隐式 std::string_view-> std::string: 没有
  • 作业 std::string = const char*: 好的
  • 作业 std::string = std::string_view: 好的
  • 附加 std::string + = const char*: 好的
  • 附加 std::string + = std::string_view: 好的
  • 级联 const char* + std::string: 好的
  • 级联 std::string_view + std::string: 没有
  • 级联 std::string + const char*: 好的
  • 级联 std::string + std::string_view: 没有

我是漏掉了什么,还是这一切都是无稽之谈?

最后,如果没有使其类似于 const char*的所有关键部分,这个字符串视图有多大用处?为什么要把它整合到 Stdlib的生态系统中,而不做最后一步来完成它呢?毕竟,如果我们需要一个表示字符串片段的对象,我们可以编写自己的对象。事实上,很多图书馆在几年前就已经这样做了。制定标准的全部意义就是让它对于最广泛的用例是有用的,不是吗?

他们会在 C + + 23中修复这个吗?

63257 次浏览

The problem is that std::string_view -> std::string makes a copy of the underlying memory, complete with heap allocation, whereas the implicit std::string -> std::string_view does not. If you've bothered to use a std::string_view in the first place then you obviously care about copies, so you don't want one to happen implicitly.

Consider this example:

void foo1(const std::string& x)
{
foo2(x);
}
void foo2(std::string_view x)
{
foo3(x);
}
void foo3(const std::string& x)
{
// Use x...
}

The function foo2 could've used a const std::string& parameter, but used a std::string_view so that it is more efficient if you pass in a string that isn't a std::string; no surprises there. But it's less efficient than if you'd just given it a const std::string& parameter!

  • When foo2 is called with a std::string argument (e.g. by foo1): When foo2 calls foo3, it creates a copy of the string. If it had a const std::string& argument, it could've used the object it already had.
  • When foo2 is called with a const char* argument: A std::string copy has to be made sooner or later; with a const std::string& parameter it gets made earlier, but overall there's exactly one copy either way.

Now imagine foo2 calls multiple functions like foo3, or calls foo3 in a loop; it's making exactly the same std::string object over and over. You'd want the compiler to notify you about this.

Because expensive implicit conversions are undesirable...

You only listed one implicit conversion out of your entire set of examples: const char* -> std::string; and you're asking for another one. In all of the other 'OK'-marked functions - allocations of memory are obvious/explicit:

  • Assignments: When you assign anything to an owning object with variable storage size, it is understood that an allocation may be necessary. (Unless it's a move-assignment, but never mind that.)
  • Append I: when you append to an owning object with variable storage size, it is even more obvious it will need to allocate to accommodate the additional data. (It might have enough reserved space, but nobody guarantees this about strings.)
  • Concatenations: In all three cases, memory is allocated for the result only, not for any intermediate object. Allocation for the result is obviously necessary, since the operands of the concatenation remain intact (and can't be assumed to hold the result anyway).

Implicit conversions in general have benefits and detriments. C++ is actually mostly stingy with these, and inherits most implicit conversions from C. But - const char* to std::string is an exception. As @ArthurTacca notes, it allocates memory. Now, the C++ core guidelines say:

C.164: Avoid implicit conversion operators

Reason:
Implicit conversions can be essential (e.g., double to int) but often cause surprises (e.g., String to C-style string).

and this is doubly the case when the unintended conversion performs expensive operations like allocation, which have side effects, and may make system calls.


PS - std::string_view has quite a few gotchas; see this CppCon 2018 talk by Victor Ciura:

Enough string_view to hang ourselves with

So remember it's not some kind of universal panacea; it's another class that you need to use with care, not carelessness.

... an explicit constructor is sufficient.

There actually does exist an explicit constructor of std::string from std::string_view. Use it - by passing std::string{my_string_view} to a function taking a string.

One of the reasons string views are useful in embedded environment is precisely because they don't do dynamic allocation and we get safety due to the length being passed as part of the view. So for me the lack of an implicit cast to std::string is not a deal breaker because that would require dynamic allocation. A silent conversion would be an error in this environment and have to be found and removed.