重载流插入而不违反信息隐藏?

发布于 2024-09-04 03:16:22 字数 2670 浏览 9 评论 0原文

我正在为项目使用 yaml-cpp 。我想重载某些类的 <<>> 运算符,但我遇到了如何“正确”执行此操作的问题。以 Note 类为例。这相当无聊:

class Note {
  public:
    // constructors
    Note( void );
    ~Note( void );

    // public accessor methods
    void            number( const unsigned long& number ) { _number = number; }
    unsigned long   number( void ) const                  { return _number; }
    void            author( const unsigned long& author ) { _author = author; }
    unsigned long   author( void ) const                  { return _author; }
    void            subject( const std::string& subject ) { _subject = subject; }
    std::string     subject( void ) const                 { return _subject; }
    void            body( const std::string& body )       { _body = body; }
    std::string     body( void ) const                    { return _body; }

  private:
    unsigned long   _number;
    unsigned long   _author;
    std::string     _subject;
    std::string     _body;
};

<< 运算符很简单。在 .h 中:

YAML::Emitter& operator << ( YAML::Emitter& out, const Note& v );

.cpp 中:

YAML::Emitter& operator << ( YAML::Emitter& out, const Note& v ) {
  out << v.number() << v.author() << v.subject() << v.body();
  return out;
}

不用担心。然后我去声明 >> 运算符。在 .h 中:

void operator >> ( const YAML::Node& node, Note& note );

但是在 .cpp 中,我得到:

void operator >> ( const YAML::Node& node, Note& note ) {
  node[0] >> ?
  node[1] >> ?
  node[2] >> ?
  node[3] >> ?
  return;
}

如果我写类似 node[0] >> v._number; 那么我需要更改 CV 限定符以使所有 Note 字段 public (这打败了我所教的所有内容(由教授、书籍和经验)))关于数据隐藏。

我喜欢做 node[0] >>温度0; v.number( temp0 ); 到处都是,不仅乏味、容易出错、丑陋,而且相当浪费(额外的副本)。

然后我明白了:我尝试将这两个运算符移至 Note 类本身,并将它们声明为 friend,但编译器(GCC 4.4)不喜欢那:

<块引用>

src/note.h:44: 错误:'YAML::Emitter&注意::operator<<(YAML::Emitter&, const Note&)' 必须仅采用一个参数
src/note.h:45: 错误:'void Note::operator>>(const YAML::Node&, Note&)' 必须只接受一个参数

问题: 我如何“正确地” " 为类重载 >> 运算

  1. 符而不违反信息隐藏原则?
  2. 没有过度复制?

I'm using yaml-cpp for a project. I want to overload the << and >> operators for some classes, but I'm having an issue grappling with how to "properly" do this. Take the Note class, for example. It's fairly boring:

class Note {
  public:
    // constructors
    Note( void );
    ~Note( void );

    // public accessor methods
    void            number( const unsigned long& number ) { _number = number; }
    unsigned long   number( void ) const                  { return _number; }
    void            author( const unsigned long& author ) { _author = author; }
    unsigned long   author( void ) const                  { return _author; }
    void            subject( const std::string& subject ) { _subject = subject; }
    std::string     subject( void ) const                 { return _subject; }
    void            body( const std::string& body )       { _body = body; }
    std::string     body( void ) const                    { return _body; }

  private:
    unsigned long   _number;
    unsigned long   _author;
    std::string     _subject;
    std::string     _body;
};

The << operator is easy sauce. In the .h:

YAML::Emitter& operator << ( YAML::Emitter& out, const Note& v );

And in the .cpp:

YAML::Emitter& operator << ( YAML::Emitter& out, const Note& v ) {
  out << v.number() << v.author() << v.subject() << v.body();
  return out;
}

No sweat. Then I go to declare the >> operator. In the .h:

void operator >> ( const YAML::Node& node, Note& note );

But in the .cpp I get:

void operator >> ( const YAML::Node& node, Note& note ) {
  node[0] >> ?
  node[1] >> ?
  node[2] >> ?
  node[3] >> ?
  return;
}

If I write things like node[0] >> v._number; then I would need to change the CV-qualifier to make all of the Note fields public (which defeats everything I was taught (by professors, books, and experience))) about data hiding.

I feel like doing node[0] >> temp0; v.number( temp0 ); all over the place is not only tedious, error-prone, and ugly, but rather wasteful (what with the extra copies).

Then I got wise: I attempted to move these two operators into the Note class itself, and declare them as friends, but the compiler (GCC 4.4) didn't like that:

src/note.h:44: error: ‘YAML::Emitter& Note::operator<<(YAML::Emitter&, const Note&)’ must take exactly one argument
src/note.h:45: error: ‘void Note::operator>>(const YAML::Node&, Note&)’ must take exactly one argument

Question: How do I "properly" overload the >> operator for a class

  1. Without violating the information hiding principle?
  2. Without excessive copying?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

孤星 2024-09-11 03:16:22

在不违反封装的情况下执行此操作的典型方法是使运算符>>好友功能。您对友元运算符的声明一定存在语法问题(从错误消息中不清楚到底是什么)。我不使用 YAML,但从您的问题来看,以下是其要点:

class Note{
    ...
    friend void operator >> ( const YAML::Node& node, Note& note );
    ....
 };
 void operator >> ( const YAML::Node& node, Note& note ){
    node[0] >> note._number;
    node[1] >> note._author;
    node[2] >> note._subject;
    node[3] >> note._body;
 }

友元函数对私有成员具有与成员函数相同的访问权限。

或者,您可以为所有成员数据声明 setter,但友元函数方法更清晰。

The typical way to do this without violating encapsulation is to make the operator>> a friend function. There must have been a syntax problem with your declaration of a friend operator (not clear what exactly from the error message). I don't use YAML, but from your question the following is the jist of it:

class Note{
    ...
    friend void operator >> ( const YAML::Node& node, Note& note );
    ....
 };
 void operator >> ( const YAML::Node& node, Note& note ){
    node[0] >> note._number;
    node[1] >> note._author;
    node[2] >> note._subject;
    node[3] >> note._body;
 }

A friend function has the same access rights to private members as a member function.

Alternatively, you can declare setters for all member data, but the friend function method is cleaner.

把昨日还给我 2024-09-11 03:16:22

我喜欢使用辅助方法。由于该方法是类的一部分,因此它将具有对所有私有字段的完全访问权限:

class Note {
public:
    void read(const YAML::Node& node)
    {
        node >> ...;
    }
};

然后让 operator>> 转发调用:

const YAML::Node &operator >> ( const YAML::Node& node, Note& note ) {
    note.read(node);
    return node;
}

I like to use a helper method. Since the method is part of the class, it will have full access to all private fields:

class Note {
public:
    void read(const YAML::Node& node)
    {
        node >> ...;
    }
};

and then have operator>> just forward the call:

const YAML::Node &operator >> ( const YAML::Node& node, Note& note ) {
    note.read(node);
    return node;
}
心作怪 2024-09-11 03:16:22

您可以在 Note 中定义更多 setter 方法,例如

void number(YAML::Immitter& e) { e>>_number; }

等,然后定义语法糖 >> 因为

void operator >> ( YAML::Immitter& e, Note& note ) {
  note.number(e);
  note.author(e);
  note.subject(e);
  note.body(e);
}

我不熟悉您的 YAML 命名空间正在使用(我知道 yaml 但我从未在 C++ 中处理过它),但这大致就是您使用普通流的方式(除了 void 返回类型; -),我相信它可以轻松适应您的具体需求。

You define further setter methods in Note, such as

void number(YAML::Immitter& e) { e>>_number; }

etc, and you then define the syntax-sugar >> as

void operator >> ( YAML::Immitter& e, Note& note ) {
  note.number(e);
  note.author(e);
  note.subject(e);
  note.body(e);
}

I'm not familiar with the YAML namespace you're using (I know yaml but I've never handled it in C++), but that's roughly how you'd doit with normal streams (apart from the void return types;-), and I'm sure it can be easily adapted to your exact needs.

仅此而已 2024-09-11 03:16:22

您的类已经有 setter 方法。只需使用临时变量来读取值并使用 setter 方法来配置对象即可:

void operator >> ( const YAML::Emitter& node, Note& note ) {
  unsigned long number;
  unsigned long author;
  // ...
  node[0] >> number;
  node[1] >> author;
  // ... everything properly read, edit the node:
  node.number(number);
  node.author(author);
  // ...
  return;

}

其他一些注释: 具有所有属性的 setter/getter 方法的类很难被封装。您为用户提供了与您的字段实际上是公共字段相同的访问级别(唯一的优点是您可以稍后添加检查,但封装仍然很弱)。

在建议添加采用 YAML 节点的成员方法的解决方案中,这将为类的所有用户添加额外的依赖项。虽然您可以使用前向声明来避免强制它们包含 YAML 标头,但您将无法使用 Note 提取库以在不轻易使用 YAML 的其他项目中使用。

资源的潜在浪费可能会非常有限。再说一次,一如既往,首先测量,然后尝试解决问题(如果有问题)。

Your class already has setter methods. Just use temporaries to read the values and use the setter methods to configure the object:

void operator >> ( const YAML::Emitter& node, Note& note ) {
  unsigned long number;
  unsigned long author;
  // ...
  node[0] >> number;
  node[1] >> author;
  // ... everything properly read, edit the node:
  node.number(number);
  node.author(author);
  // ...
  return;

}

Some other comments: A class that has setters/getters for all the attributes is hardly encapsulated. You are giving users the same access level as if your fields were actually public (with the only advantage that you can add checking at a later time, but still, the encapsulation is weak).

On the solutions that suggest adding a member method that takes the YAML node, that will add an extra dependency to all users of your class. While you can use forward declarations to avoid forcing them to include the YAML headers, you will not be able to pull a library with your Note to use in a different project that does not use YAML easily.

The potential wasteful usage of resources is probably going to be very limited. Then again, as always, first measure and then try to solve problems if you have them.

拥抱没勇气 2024-09-11 03:16:22

嗯,这是您可能会考虑的一个想法。您所说的与非朋友、非会员之间的问题 <<功能的一个特点是它涉及到很多tmp声明。您是否考虑过封装这个概念并围绕它构建一个可重用的组件?使用可能如下所示:


inputter& operator >> (inputter& in, my_type & obj)
{
  input_helper<my_type> helper(obj);

  in >> helper.setter(&my_type::number);
  in >> helper.setter(&my_type::subject);
  // etc
}

input_helper 的职责只是提供模板函数 setter(),该函数返回一个对象,该对象仅读取值并用它调用 setter ,创建必要的临时变量。像这样的代码需要对模板有一定的熟悉,但不会特别困难。现在无法完全清晰地思考——可能是感冒了——或者我可能只能把它打出来。也许有点像这样:


template < typename T >
struct input_helper
{
  input_helper(T & t) : obj(t) {}

  template < typename V >
  struct streamer
  {
    streamer(T & t, void (T::*f)(V const&)) : obj(t), fun(f) {}

    template < typename Stream >
    Stream& read_from(Stream & str) const // yeah, that's right...const; you'll be using a temporary.
    {
      V v;
      str >> v;
      obj.(*fun)(v);
      return str;
    }

  private: // you know the drill...
  }

  template < typename V >
  streamer setter(void (T::*fun)(V const&))
  {
    return streamer(obj, fun);
  }
private:
  T & obj;
};
// etc...  operator >> (blah blah) { return setter.read_from(stream); }

其中肯定有各种各样的错误,但它应该给你一个想法。还需要更多的工作来概括。

Well, here's an idea that you might consider. The problem you say you have with the non-friend, non-member << function is that it involves a lot of tmp declarations. Have you considered encapsulating the concept and building a reusable component around it? Use might look something like so:


inputter& operator >> (inputter& in, my_type & obj)
{
  input_helper<my_type> helper(obj);

  in >> helper.setter(&my_type::number);
  in >> helper.setter(&my_type::subject);
  // etc
}

The responsibility of input_helper is simply to supply the template function setter() that returns an object that simply reads the value and calls the setter with it, creating the necessary temporary variable. Code like this would require some intimate familiarity with templates but wouldn't be particularly difficult. Can't think completely straight right now--might be getting a cold--or I'd probably be able to just type it out. Maybe something sort of like so:


template < typename T >
struct input_helper
{
  input_helper(T & t) : obj(t) {}

  template < typename V >
  struct streamer
  {
    streamer(T & t, void (T::*f)(V const&)) : obj(t), fun(f) {}

    template < typename Stream >
    Stream& read_from(Stream & str) const // yeah, that's right...const; you'll be using a temporary.
    {
      V v;
      str >> v;
      obj.(*fun)(v);
      return str;
    }

  private: // you know the drill...
  }

  template < typename V >
  streamer setter(void (T::*fun)(V const&))
  {
    return streamer(obj, fun);
  }
private:
  T & obj;
};
// etc...  operator >> (blah blah) { return setter.read_from(stream); }

There's surely all kinds of errors in that but it should give you the idea. Would also require more work to generalize.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文