C++进阶:重载括号()运算符

其他运算符都不能改变参数数量和类型,括号运算符 (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;
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值