views:

115

answers:

5

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?
+3  A: 

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.

academicRobot
I agree that all that setters are an imperfection, but he's already got them all, they just take a less-than-useful argument type for this use case -- so, adding the overloads with the actually-useful argument type is hardly adding any problem.
Alex Martelli
@Alex Martelli Perhaps, but then again neither is it any problem to declare a friend function (and its cleaner, IMHO).
academicRobot
I'm not sure what the problem was, but I tried this again today, and it worked; I think you're right, must've been a syntax problem.
Chris
+1  A: 

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.

Alex Martelli
A: 
Noah Roberts
Michael Anderson
I believe the function parameter type disambiguates them. I didn't test any of it though. Even if it doesn't, there are plenty of ways around what you're talking about.
Noah Roberts
I tested this particular aspect of the code and it indeed compiles just fine. Which version of number the address refers to is disambiguated by the type it is being assigned to (the function parameter).
Noah Roberts
+3  A: 

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;
}
R Samuel Klatchko
I would not do this. The class already has a write interface, and adding this method generates a dependency from Note to YAML that is not really needed (you can no longer use Note in a context where YAML is not present).
David Rodríguez - dribeas
+1  A: 

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.

David Rodríguez - dribeas
I totally agree with the 'hardly encapsulated' comment. As it stands, all the data members are effectively public anyway. There's not much "information hiding principle" left to violate.
Charles Bailey
All the data members in the class can be effectively removed/renamed/calculated-on-the-fly later without any change in the user interface. With getters/setters the information on implementation is completely hidden. It is only the way to work with the class that is public.
Alsk
@Alsk, from a theoretical point of view I agree, but in my experience, in all projects I have seen that pattern the only real advantage over plain public attributes has been the possibility of adding invariant checking to the class. Anyway, that is mainly a comment on the encapsulation, the point is that with public setters that allow you to modify the state completely, you do not need anything else, you can build on top of those setters. And building functionality on top of an existing public interface reduces coupling.
David Rodríguez - dribeas