C++重载I/O运算符:克服歧义

C++ Overloading the I/0 operators: Getting Past Ambiguity

本文关键字:克服 歧义 运算符 重载 C++      更新时间:2023-10-16

我正在处理一个类,我的类中有3个数组,它们都表示相同的数据,但格式不同。我重载了在类外部声明的<<运算符,该运算符接受对它的const引用,而不是作为该类的朋友。

SomeClass {
public:
    // Nameless Union - All 3 Arrays Are of The Same Exact Data Type
    // And All 3 Arrays Have The Same Exact Size. This Nameless Union
    // Uses The Same Memory Address For All 3 Arrays And Their Elements. 
    // So An Element Is Changed By One Array Type, It Is Expected And 
    // Accepted For It To Change The Others. This Is Not 3 Different 
    // Arrays, This Is Still 1 Array Of Size 256, Just Different 
    // Representations Or Different Ways To Access Them.
    union {
        int m_256[256];
        int m_16[16][16];
        int m_4[4][4][4][4];
    };
    SomeClass() { std::fill( std::begin( m_256 ), std::end( m_256 ), 0 ); }
}; // SomeClass
std::ostream& operator<<( std::ostream& out, const SomeClass& c ) {
    out << std::endl;
    for ( unsigned box = 0; box < 4; box++ ) {
        for ( unsigned slice = 0; slice < 4; slice++ ) {
            for ( unsigned row = 0; row < 4; row++ ) {
                for ( unsigned col = 0; col < 4; col++ ) {
                    out << "(" << box << "," << slice << "," << row << "," << col << ") = "
                         << c.m_4[box][slice][row][col] << std::endl;
                }
            }
        }
    } 
    return out;
} // operator<<

这就是我目前拥有的。我希望能够将operator<<也用于此类,但能够区分以不同格式显示相同数据的方法。

我知道你不能这样做:通过添加第二个

std::ostream& operator<<( std::ostream& out, const SomeClass& c ) {
    out << std::endl;
    for ( unsigned i = 0; i < 16; i++ ) {
        for ( unsigned j = 0; j < 16; j++ ) {
            out << "(" << i << "," << j << ") = " << c.m_16[i][j] << std::endl;
        }
    }
    return out;
} // operator<<

和第三个

std::ostream& operator<<( std::ostream& out, const SomeClass& c ) {
    out << std::endl;
    for ( unsigned u = 0; u < 256; u++ ) {
        out << u << " = " << m_256[u] << std::endl;
    }
    return out;
} // operator<<

由于这个事实是模棱两可的。然而,我希望拥有以3种不同格式中的任何一种显示它的功能。

这个问题有什么解决办法吗?我希望能够将类对象发送到流运算符,而这些类型的运算符不能接受额外的参数,因为它们是二进制运算符,而不是函数。

您可以使用适配器类来编写输出。可以将格式说明符传递到构造函数中,也可以按类型进行区分。例如(按类型区分):

struct SomeClassAs256 {
  SomeClass const& x_;
  explicit(SomeClass const& x) : x_(x) {}
};

然后有一个运算符<lt;实现:

ostream& operator<<(ostream& os, SomeClassAs256 const& x) {
  ...
  return os;
}

然后你使用它:

SomeClass x;
...
cout << SomeClassAs256(x) << endl;

你真的不能这么做,至少不能这么简单。

这给您留下了两个选择:使用两个创建字符串并返回字符串的函数,或者创建流操纵器结构。

使用std::ostringstream:创建一组返回字符串用于输出的格式化函数非常简单

std::string format1(SomeClass const& c)
{
    std::ostringstream os;
    os << whatever you want ...
    return os.str();
}

创建操纵器结构稍微复杂一点,但也可以更灵活、更强大:

class format1
{
public:
    format1(SomeClass const& c)
        : c_(c)
    {}
    friend std::ostream& operator<<(std::ostream& os,
                                    format1 const& fmt)
    {
        os << some formated output here using `fmt.c_`...;
        return os;
    }
private:
    SomeClass const& c_;
};

在这两种情况下,您都可以使用相同的方式:

SomeClass c(...);
std::cout << format1(c) << 'n';

您可以通过使用make函数从类中返回代理(以提供自适应)并使用:运算符<lt;(ostream,SomeClass::Proxy)作为输出运算符。我将编写一些示例代码。

这样,您就不需要公开类的内部。使运算符<lt;朋友们。。。

示例:

#include <iostream>
class SomeClass {
    union {
        int m_256[256];
        int m_16[16][16];
        int m_4[4][4][4][4];
    };
public:
    SomeClass() { std::fill( std::begin( m_256 ), std::end( m_256 ), 0 ); }
    struct x256
    {
      const SomeClass& c_;
      explicit x256(const SomeClass& c): c_(c)
      {
      }
    };
    struct x16
    {
      const SomeClass& c_;
      explicit x16(const SomeClass& c): c_(c)
      {
      }
    };
    struct x4
    {
      const SomeClass& c_;
      explicit x4(const SomeClass& c): c_(c)
      {
      }
    };
    x256 output265() const
    {
      return x256(*this);
    }
    x16 output16() const
    {
      return x16(*this);
    }
    x4 output4() const
    {
      return x4(*this);
    }
    friend std::ostream& operator<<( std::ostream& out, const SomeClass::x256& c ) {
        out << std::endl;
        for ( unsigned u = 0; u < 256; u++ ) {
            out << u << " = " << c.c_.m_256[u] << std::endl;
        }
        return out;
    } // operator<<
    friend std::ostream& operator<<( std::ostream& out, const SomeClass::x16& c ) {
        //...
        return out;
    } // operator<<
    friend std::ostream& operator<<( std::ostream& out, const SomeClass::x4& c ) {
        //...
        return out;
    } // operator<<
}; // SomeClass
void testSomeClass()
{
  SomeClass someClass;
  std::cout << someClass.output265() << someClass.output16() << someClass.output4() << std::endl;
}

在看到一些很好的答案后,考虑到ostream对象和operator<<不知道使用哪种类型,并考虑到将由用户决定根据他们的需求显示信息,我走了另一条路;然而,我提出的适合我当前需求的解决方案得到了所有为这个问题留下好答案的人的帮助和启发。

我的方向是这样的;我用这三种类型直接在课堂上添加了一个enum。我添加了一个公共函数,它输出一个字符串,并将enum类型作为参数。我将ostream operator<<添加到了我的类中,它所使用的参数是typename到我的类enum中。我使用out函数来分支我的3种不同方式来显示信息。因此,现在在使用该对象的另一段代码中,我可以通过传入所需的类型来传递调用out函数的实例,该函数返回字符串。我的课现在看起来是这样的:

class SomeClass {
public:
    enum OutputType { x256, x16, x4 };
    union {
        int m_256[256];
        int m_16[16][16];
        int m_4[4][4][4][4];
    };
    std::string out( OutputType type ) const;
    std::ostream& operator<<( typename SomeClass::OutputType type );
}; // SomeClass

std::ostream& SomeClass::operator<<( typename SomeClass::OutputType type ) {
    return std::ostream << out(type );
} // operator<<
std::string SomeClass::out( OutputType type ) const {
    std::ostringstream out;
    out << std::endl;
    switch( type ) {
        case: x256: {
            // Print Format Here
            break; 
        }
        case x16: {
            // Print Format Here
            break;
        }
        case x4: {
            // Print Format Here
            break;
        }
        default: {
            // Error Message
            return out.str();
        }
    }
    return out.str();
 } // out

我不知道这是否与我的项目中的类是templateoperator<<的实现方式有关,但我必须在函数声明/定义中使用typename才能使其工作,所以这就是我的代码中除了类名之外的样子。

template< class T>
std::ostringstream& SomeClass<T>::operator<<( typename SomeClass<T>::Type type ) { 
    // Code Here
}

我感谢你们所有人提供的帮助和建议,我将这些建议铭记在心,谢谢大家。

编辑

现在,如果我想让这对用户来说更容易一点:我可以把我的out函数移到私有部分;编写3个不带任何参数的包装器输出或打印函数,但它们将变量设置为私有方法。

在类中添加一些方法,例如成员来决定输出格式:

public:
    enum OutputStyle 
    {
        M_256,
        M_16,
        M_4,
    };
    OutputStyle style() const {return style_;}
 private:  
     mutable OutputStyle style_ = M_256;

添加一些方法,例如函数调用运算符来设置:

public:
    SomeClass const& operator()(OutputStyle s) const
    {
        style_ = s;
        return *this;
    }

使<lt;运营商考虑:

std::ostream& operator<<( std::ostream& out, const SomeClass& c ) 
{
    switch( c.style() )
    {
    default:
        assert(!"defective operator <<");
    case SomeClass::M_256:
        // ... output like M_256 here 
        break; 
    case SomeClass::M_16: 
        // ... output like M_16 here
        break; 
    case SomeClass::M_4:
        // ... output like M_4 here
        break;
    }
} 

然后你可以在输出之前或输出过程中更改它:

    SomeClass x; // <- has style M_256
    x(SomeClass::M_16);
    std::cout << "current:" << x << std::endl
              << "with M_4:" << x(SomeClass::M_4) << std::endl;