重载运算符<<

4

我正在制作一个简单的类,使用operator<<。它将存储两个平行的数据数组,每个数组都有不同(但已知)的数据类型。 思路是最终的接口看起来像这样:

MyInstance << "First text" << 1 << "Second text" << 2 << "Third text" << 3;

这将使数组看起来像这样:

StringArray: | "First text" | "Second text" | "Third text" |
IntArray:    | 1            | 2             | 3            |

我可以处理检查输入逻辑以确保一切匹配的问题,但我对 "operator<< " 的技术细节感到困惑。
我查看的教程说要将其重载为友元函数,并使用 std::ostream& 返回类型,但我的类与流没有任何关系。我尝试使用 void 作为返回类型,但出现编译错误。最终,我返回了一个类的引用,但我不确定为什么这样可以工作。
以下是我的代码:
class MyClass
{
public:

MyClass& operator<<(std::string StringData)
{
    std::cout << "In string operator<< with " << StringData << "." << std::endl;

    return *this; // Why am I returning a reference to the class...?
}

MyClass& operator<<(int IntData)
{
    std::cout << "In int operator<< with " << IntData << "." << std::endl;

    return *this;
}
};

int main()
{   
MyClass MyInstance;
MyInstance << "First text" << 1 << "Second text" << 2 << "Third text" << 3;

return 0;
}

此外,我的类的用户可能会执行以下不需要的操作:
MyInstance << "First text" << 1 << 2 << "Second text" << "Third text" << 3;

我该如何强制输入的交替性呢?

关于您的评论,您需要返回对您的类的引用,以便可以将<<操作链接在一起。 - GWW
3个回答

7
ostream运算符返回对ostream的引用,而且返回MyClass的引用有助于你的情况,原因是类似A << B << C这样的表达式总是被解释为(A << B) << C。也就是说,第一个重载运算符返回的内容将成为下一个运算符调用的左侧。
如果你想让MyInstance << "First text" << 1 << 2这样的表达式产生编译错误,你需要确保在前两个<<运算符之后返回的类型不能再与另一个整数一起调用。我认为像这样做可能会达到你想要的效果(感谢@Pete Kirkham提供了一个很好的改进建议):
struct MyClass_ExpectInt;
class MyClass {
private:
    friend MyClass& operator<<(const MyClass_ExpectInt&, int);
    void insert_data(const std::string& StringData, int IntData);
    // ...
};
struct MyClass_ExpectInt {
    MyClass& obj_ref;
    std::string str_data;
    explicit MyClass_ExpectInt( MyClass& obj, const std::string& str )
      : obj_ref( obj ), str_data( str ) {}
};
MyClass_ExpectInt operator<<( MyClass& obj, const std::string& StringData )
{
    // Do nothing until we have both a string and an int...
    return MyClass_ExpectInt( obj, StringData );
}
MyClass& operator<<( const MyClass_ExpectInt& helper, int IntData )
{
    helper.obj_ref.insert_data( helper.str_data, IntData );
    return helper.obj_ref;
}

这将是你定义与 MyClass 相关的唯二重载的 operator<< 函数。这样,每次调用 operator<< 时,编译器会将返回类型从 MyClass& 切换到 MyClass_ExpectInt 或反之,并且不允许传递“错误”类型的数据给 operator<<


2
我会将字符串数据放入临时的_ExpectInt中,这样只有在两个参数都给出后才会添加到数组中。 - Pete Kirkham
@Pete 不错的观点,如果 MyInstance << "First text"; 缺少整数应该什么也不做。这可能更有意义。 - aschepler
我会将MyClass_ExpectInt的构造函数设为私有。这样只有MyClass才能创建对象。我这么做只是为了尽量防止对象被误用。 - Martin York
有没有一种方法可以在它们各自的类内定义运算符?也就是说,将其定义为MyClass::operator<<而不是友元函数? - Maxpm
1
@Maxpm:是的,成员运算符几乎同样有效。但我通常更喜欢非成员二元运算符,因为它们允许左操作数上进行隐式用户定义转换。 - aschepler

2
MyInstance << "First text" << 1;

这行代码调用了 operator<<(operator<<(MyInstance, "First text"), 1)。如果 operator<< 没有返回 MyClass 的引用,那么“外部”的调用将失败,因为你会传递一个 void 而不是期望的 MyClass &
为了在编译时强制实现交替类型,需要创建一个辅助类,例如 MyClassHelper。然后,需要创建这两个运算符:
MyClassHelper & operator<<(MyClass &, std::string const &);
MyClass & operator<<(MyClassHelper &, int);

每个运算符都应该返回到涉及的“另一个”对象的引用。这样可以确保在 << "string" 之后,返回的引用是 MyClassHelper,它只有一个 int 类型的 operator<<。而在 << int 之后,返回的引用是 MyClass,它只有一个 string 类型的 operator<<。请注意保留 html 标签。

0
除了schepler所说的内容外:语法
x << "one" << 1 << "two" << 2;

不表明“one”属于1。今天看起来很好,但明天向别人解释会非常困难,两年后的维护者更难逆向工程化。这是因为它类似于“常规”插入运算符,而后者支持更多类型。

如果您有自由选择API外观的权利,最好现在做些什么,以使其更清晰地表明您正在插入两个关联值。

例如,可以通过仅允许插入std::pair<string,int>来实现此目的:

x << make_pair( "one", 1 ) 
  << make_pair( "two", 2 )
  ;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接