其他运算符都不能改变参数数量和类型,括号运算符 (operator()) 是一个特别有趣的运算符,因为它允许您同时改变其参数的类型和数量。
问题及实践:
编写一个名为 MyString 的类,该类包含一个std::string。重载operator<<该类以输出字符串。重载operator()该类以返回从第一个参数的索引处开始的子字符串(作为MyString)。子字符串的长度应由第二个参数定义。
以下代码应该运行:
int main()
{
MyString s { "Hello, world!" };
std::cout << s(7, 5) << '\n'; // start at index 7 and return 5 characters
return 0;
}
提示:使用std::string::substr来获取 std::string 的子字符串。
所遇到的2个问题:
[-Wsign-conversion]符号转换警告
MyString operator()(int start, int length)
{
return MyString{ m_string.substr(start , length) };
}
warning: implicit conversion changes signedness: 'int' to 'size_type' (aka 'unsigned long') [-Wsign-conversion]
|| 16 | return MyString{ m_string.substr(start , length) };
经查, std::string::size_type 是 std::string 类中定义的一个成员类型,用于表示字符串的大小、索引或位置。它是一个无符号整数类型。在大多数现代系统上,它通常是 std::size_t 的别名,而 std::size_t 在 64 位系统上通常就是 unsigned long。
所以在我这个电脑上,size_type(别名size_t),它的类型是unsigned long.
编译器怕我们输入的参数int类型为负数时会造成崩溃,因此发出警告. 通常是可以通过的,但我把错误等级提高了,设置了-Wsign-conversion把符号转换警告视为错误,所以此处会报出来.
解决办法是使用更为安全的类型转换static_cast
MyString operator()(int start, int length)
{
return MyString { m_string.substr(
static_cast<std::string::size_type>(start),
static_cast<std::string::size_type>(length)
)};
}
为了更为安全, 可以先用防御性编程断言, 确保在输入负数时,程序会立即中止, 也可帮助我们快速定位问题
#include <cassert> // for assert()
assert(start >= 0 && length >= 0);
assert(start + length <= static_cast<int>(m_string.length()) && "MyString::operator(int, int):Substring is out of range");
error:
non-const lvalue reference to type ‘MyString’ cannot bind to a temporary of type ‘MyString’
MyString& operator()(int start, int length)
{
return MyString { m_string.substr(
static_cast<std::string::size_type>(start),
static_cast<std::string::size_type>(length)
)};
}
在代码 return MyString { … }; 中,创建了一个临时对象(右值)。这个对象在函数返回后就会被销毁。
返回类型是 MyString&,这是一个非const的左值引用。
C++语言规则禁止将一个非const的左值引用绑定到一个临时对象上。因为这样做是极其危险的:你将会得到一个指向已被销毁对象的"悬空引用",后续使用这个引用会导致未定义行为(程序崩溃或更糟)。
所以返回类型不要用MyString&类型,应该使用MyString.
为什么使用std::string_view?
在我们的operator()内部,std::string::substr返回一个std::string,这意味着当我们调用它时,我们会复制源字符串的一部分。我们的重载函数operator()使用它来构造一个新的MyString,它包含一个std::string成员,该成员会生成另一个副本。然后,我们将此返回MyString给调用者,调用者会生成第三个副本。编译器可能会优化掉其中一些副本,使其不存在,但至少std::string必须保留一个(包含生成的子字符串)。
std::string_view能够查看现有字符串的子字符串,而无需复制。如果我们operator()返回一个std::string_view,那么调用者可以在std::string_view满足条件时使用 ,或者在需要修改或保留子字符串时将其转换为std::string或MyString。
源码
#include <iostream>
#include <string>
#include <cassert> // for assert()
class MyString
{
private:
std::string m_string {};
public:
MyString(std::string_view string = {})
:m_string{ string }
{
}
friend std::ostream& operator<<(std::ostream& out, const MyString& s)
{
out << s.m_string;
return out;
}
MyString operator()(int start, int length)
{
assert(start >= 0 && length >= 0);
assert(start + length <= static_cast<int>(m_string.length()) && "MyString::operator(int, int):Substring is out of range");
return MyString { m_string.substr(
static_cast<std::string::size_type>(start),
static_cast<std::string::size_type>(length)
)};
}
};
int main()
{
MyString s { "Hello, world!" };
std::cout << s(7, 5) << '\n'; // start at index 7 and return 5 characters
std::cout << s(-7, 5) << '\n';
// MyString MyString::operator()(int, int): Assertion `start >= 0 && length >= 0' failed.
return 0;
}

330

被折叠的 条评论
为什么被折叠?



