为std::string重载运算符+是个坏主意吗

overloading operator+ for std::strings - a bad idea?

本文关键字:std string 重载 运算符      更新时间:2023-10-16

现在通常应该稀疏地使用运算符重载,尤其是在涉及stdlib时。

尽管我很好奇,除了读者可能看不清楚代码中发生了什么之外,还会有什么陷阱(如果有的话(——是否有任何技术理由避免这种特定的过载?

std::string operator+(const std::string& lhs, const std::wstring& rhs) {
return lhs + to_utf8(rhs);
}

(还有做逆变换的双重过载(

我发现这可以让一些操作更容易写出来,例如:

std::wstring{L"hel"} + "lo " + getName();

利弊是什么,尤其是你认为有任何可能"适得其反"的场景(技术场景(吗?

性能不是问题。

您不应该这样做,因为它会破坏参数依赖查找(ADL(。

考虑一下这个无辜的测试代码:

namespace myNamespace
{
struct MyType {};
MyType operator+(MyType, MyType);
template<class T>
auto doSomething(T t)
{
return t + std::wstring{};
}
}

看起来没有问题,是吗?

嗯,它是:

void test()
{
std::string s;
myNamespace::doSomething(s);
}
error: invalid operands to binary expression ('std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >' and 'std::wstring' (aka 'basic_string<wchar_t>'))
return t + std::wstring{};
~ ^ ~~~~~~~~~~~~~~
<source>:25:18: note: in instantiation of function template specialization 'myNamespace::doSomething<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >' requested here
myNamespace::doSomething(s);
^
<source>:12:12: note: candidate function not viable: no known conversion from 'std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >' to 'myNamespace::MyType' for 1st argument
MyType operator+(MyType, MyType);
^

https://godbolt.org/z/bLBssp

问题是找不到模板的operator+定义。模板中的operator+通过非限定名称查找进行解析。这基本上做了两件事:

  1. 在每个封闭作用域中递归查找任何operator+,直到找到第一个为止。如果在全局范围内或在不同的命名空间中为std::stringstd::wstring定义自己的operator+,则当有任何operator+"更接近"模板时,就无法以这种方式找到它。

  2. 查找与运算符(ADL(的参数类型相关联的命名空间。由于这两种类型都来自namespace std,因此我们在其中查找并没有发现任何有效的operator+(请参阅godbolt上的其他错误注释(。你不能把你自己的运算符放在那里,因为这是未定义的行为。

因此,经验法则是:只重载涉及您的类型的运算符,因为该运算符必须与您的类型放在同一命名空间中,ADL才能工作。


即使没有模板,问题也是一样的,但在这种情况下,手动引入操作员可能是合理的。这显然是不合理的要求通用代码(甚至可能不是你的(

我担心您的用户可能没有意识到他们正在使用此功能。尽量避免像这样的隐式转换。

在需要的时候只写to_utf8很容易。

如果您有stringwstring的大量混合,请在源代码处修复它:当您最初接收到宽字符串时,在string中转换为UTF-8,那么所有"内部"字符串都很好且一致。