Thread: Friends design

  1. #1
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654

    Friends design

    I was wondering the best approach to this design...
    Say that we have two classes: X and X_iter.
    Now X would be a container, and X_iter would be an iterator for that class.
    Now, X would have certain functions that allows its iterators to access the data it needs. These members are naturally protected/private, since the outside has no business with these. But X_iter needs access to these functions. But making the X_iter a friend, it gets access to all internals, even the ones it should never touch.

    Also, though I'd ask this, too:
    Similarly, X_iter contains constructors that allows it to get access to the information it needs, and it is likely some constructors therefore are protected/private, because only the container class should be able to construct an iterator using these constructors.

    What is the best approach to these? Friends? Public?

    Code:
    class X
    {
    protected:
        // Some iterator methods
        // X_iter needs access to these
    public:
        X_iter begin() { return X_iter(some_arguments_here); }
    };
    
    class X_iter
    {
    protected:
        X_iter(some_type_here); // The container class X needs access to this
    };
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  2. #2
    The larch
    Join Date
    May 2006
    Posts
    3,573
    But making the X_iter a friend, it gets access to all internals, even the ones it should never touch.
    I think you worry too much. Similarly, if a class has members a and b and methods foo and bar, and foo happens to need only a, would you go out of your way to hide b from it?

    It seems to me that it is the user's perspective that matters and when you write the classes themselves, you need to show up some discipline.
    I might be wrong.

    Thank you, anon. You sure know how to recognize different types of trees from quite a long way away.
    Quoted more than 1000 times (I hope).

  3. #3
    (?<!re)tired Mario F.'s Avatar
    Join Date
    May 2006
    Location
    Ireland
    Posts
    8,446
    You'll want to use the iterator pattern, Elysia.
    Nice little pattern with the added benefit your iterator class isn't dependent on the aggregate type. So you can easily change your aggregate class without any need to change the iterator class. The only change you'll ever need to do to your aggregate class is adding a createiterator() function.

    Check your GoF, or a quick implementation here (didn't check the link for code correctness).
    Originally Posted by brewbuck:
    Reimplementing a large system in another language to get a 25% performance boost is nonsense. It would be cheaper to just get a computer which is 25% faster.

  4. #4
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Quote Originally Posted by anon View Post
    I think you worry too much. Similarly, if a class has members a and b and methods foo and bar, and foo happens to need only a, would you go out of your way to hide b from it?

    It seems to me that it is the user's perspective that matters and when you write the classes themselves, you need to show up some discipline.
    Ah, I see. Thought it best to ask the recommended way of going about it anyway.
    MarioF: I'll look into that too, thanks.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  5. #5
    The larch
    Join Date
    May 2006
    Posts
    3,573
    I'm confused, wasn't Elysia already using the iterator pattern?

    Nice little pattern with the added benefit your iterator class isn't dependent on the aggregate type.
    At least in the link provided this isn't visible. (Didn't you mean, user code using iterator isn't dependent on aggregate type?)

    The code also seems to be faulty: the operator== seems to allow accessing the right-hand stack out of bounds. (Only the left-hand iterator is tested to see whether it has reached the end.)

    (And in C++ it is sort of important that user-defined iterators follow the same interface as STL ones because of how templates work.)
    I might be wrong.

    Thank you, anon. You sure know how to recognize different types of trees from quite a long way away.
    Quoted more than 1000 times (I hope).

  6. #6
    (?<!re)tired Mario F.'s Avatar
    Join Date
    May 2006
    Location
    Ireland
    Posts
    8,446
    Quote Originally Posted by anon View Post
    I'm confused, wasn't Elysia already using the iterator pattern?
    Hmm... nope. I don't see the pattern anywhere. I see a class wanting to be an iterator but iteration functionality being implemented on the aggregate.

    Iterator Pattern - Gil Fink on .Net
    Originally Posted by brewbuck:
    Reimplementing a large system in another language to get a 25% performance boost is nonsense. It would be cheaper to just get a computer which is 25% faster.

  7. #7
    The larch
    Join Date
    May 2006
    Posts
    3,573
    But this link too shows that the iterator is using knowledge of the aggregate (the [] operator and Count property)? In this case these are public, but you don't mean to say it is always possible to provide an iterator, using the public interface only?
    I might be wrong.

    Thank you, anon. You sure know how to recognize different types of trees from quite a long way away.
    Quoted more than 1000 times (I hope).

  8. #8
    (?<!re)tired Mario F.'s Avatar
    Join Date
    May 2006
    Location
    Ireland
    Posts
    8,446
    No. Of course not. I think it's implicit that if there is a need to create an iterator class, there must be a minimum exposure on behalf of the aggregate. Otherwise it's not an aggregate. As such, the following quote ceases to make sense:
    Now X would be a container,
    I base the statement "but iteration functionality being implemented on the aggregate" based on the following quote from the original post:

    class X
    {
    protected:
    // Some iterator methods
    // X_iter needs access to these
    [...]
    If indeed there is no desire to expose [] and count(), then the aggregate is not the encapsulating class anymore, but one of its members. In that case, the iterator (if there is indeed a need to define one) should be implemented inside the class and its methods made public. But I wouldn't probably call that an iterator anymore...
    Last edited by Mario F.; 07-28-2009 at 08:28 AM.
    Originally Posted by brewbuck:
    Reimplementing a large system in another language to get a 25% performance boost is nonsense. It would be cheaper to just get a computer which is 25% faster.

  9. #9
    The larch
    Join Date
    May 2006
    Posts
    3,573
    Ok. I just interpreted it as: it is not a simple thing to make X iteratable, hence X provides some methods to be used only by X_iter to achieve its task. Those methods might not be implemented, but this only means X_iter would need to implement those itself and poke around even deeper in the private members of X.
    I might be wrong.

    Thank you, anon. You sure know how to recognize different types of trees from quite a long way away.
    Quoted more than 1000 times (I hope).

  10. #10
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,708
    You can also encapsulate a lot of the iteration logic in a generic base class. Here's a rough example:

    Code:
    template < typename Derived >
    class iterator_interface
    {
    	public:
    	
    	inline Derived& operator += ( int n )
    	{
    		self( ).advance( n );
    		return self( );
    	}
    	
    	inline friend Derived operator + ( Derived const& lhs, int n )
    	{
    		return Derived( lhs.self( ) ) += n;
    	}	
    	
    	inline Derived& operator ++ ( void )
    	{
    		return *this += 1;
    	}
    	
    	inline Derived operator ++ ( int )
    	{
    		Derived
    			temp = self( );
    		++*this;
    		return temp;
    	}
    
    	inline Derived& operator -= ( int n )
    	{
    		self( ).advance( -n );
    		return self( );
    	}	
    	
    	inline friend Derived operator - ( Derived const& lhs, int n )
    	{
    		return Derived( lhs.self( ) ) -= n;
    	}
    	
    	inline Derived& operator -- ( void )
    	{
    		return *this -= 1;
    	}
    	
    	inline Derived operator -- ( int )
    	{
    		Derived
    			temp = self( );
    		--*this;
    		return temp;
    	}
    	
    	inline friend bool operator == ( Derived const& lhs, Derived const& rhs )
    	{
    		return lhs.self( ).compare( rhs ) == 0;
    	}
    
    	inline friend bool operator != ( Derived const& lhs, Derived const& rhs )
    	{
    		return lhs.self( ).compare( rhs ) != 0;
    	}
    
    	inline friend bool operator < ( Derived const& lhs, Derived const& rhs )
    	{
    		return lhs.self( ).compare( rhs ) < 0;
    	}
    
    	inline friend bool operator <= ( Derived const& lhs, Derived const& rhs )
    	{
    		return lhs.self( ).compare( rhs ) <= 0;
    	}
    
    	inline friend bool operator > ( Derived const& lhs, Derived const& rhs )
    	{
    		return lhs.self( ).compare( rhs ) > 0;
    	}
    
    	inline friend bool operator >= ( Derived const& lhs, Derived const& rhs )
    	{
    		return lhs.self( ).compare( rhs ) >= 0;
    	}
    	
    	inline Derived& self( void )
    	{
    		return static_cast< Derived& >( *this );
    	}
    	
    	inline Derived const& self( void ) const
    	{
    		return static_cast< Derived const& >( *this );
    	}
    };
    Note that the iterator_interface class doesn't define any dereferencing policies whatsoever, it only provides an interface for iteration. You could of course add that functionality, but I prefer to keep them separate, personally. So to properly inherit from the interface, you just need to define 'advance' and 'compare'. Here's a do-nothing example:

    Code:
    // minimal interface
    class test : public iterator_interface< test >
    {
    	public:
    	
    	// forward if positive, otherwise reverse
    	void advance( int n )
    	{	}
    	
    	// return 0 if equal, less than 0 if *this precedes rhs, otherwise greater than 0
    	int compare( test const& rhs ) const
    	{
    		return 0;
    	}
    	
    	// basic dereferencing
    	int& operator * ( void )
    	{
    		return unused;
    	}
    
    	int const& operator * ( void ) const
    	{
    		return unused;
    	}
    	
    	int
    		unused;
    };
    Code:
    #include <cmath>
    #include <complex>
    bool euler_flip(bool value)
    {
        return std::pow
        (
            std::complex<float>(std::exp(1.0)), 
            std::complex<float>(0, 1) 
            * std::complex<float>(std::atan(1.0)
            *(1 << (value + 2)))
        ).real() < 0;
    }

  11. #11
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    I'll give some insights on my plans for the moment. I want a random access stream iterator. The current stream iterators are just forward-only, which just won't do it for me.
    So basically, I would just create a small derived stream class, and provide an iterator for this. The idea is simple: use buffering. Whenever we try to access data in the file, check if we have that date, if and if not, read it and store it (buffer it).
    Since there can be an infinite number of iterators to the same stream, the actual buffer which represents the contents of the file must be in the stream class. The iterator needs access to that buffer, naturally, to be able to iterate its contents, so I was planning on exposing some protected functions in the stream class that the iterator can call to get its needed functionality.
    Those are my plans.

    And, of course, iterators are the de-facto standard for iteration in the standard library, so that's what I'm doing and not adding traversing functionality to the stream class.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  12. #12
    Lurking whiteflags's Avatar
    Join Date
    Apr 2006
    Location
    United States
    Posts
    9,613
    I thought about this some more beyond initially saying "it's a good idea." I thought a little harder.

    I'm curious as what you plan to do with a random access stream, because beyond certain applications (like inserting a line in the middle of a file) I don't really get it. Why would you want the stream to do this alone, instead of say using a list of strings? Cause I mean, streams already do buffering. Data structures are for processing.

  13. #13
    (?<!re)tired Mario F.'s Avatar
    Join Date
    May 2006
    Location
    Ireland
    Posts
    8,446
    Well, dunno Elysia.

    And, of course, iterators are the de-facto standard for iteration in the standard library, so that's what I'm doing and not adding traversing functionality to the stream class.
    The problem (not really a problem, mind you) is that an iterator class only makes sense in the presence of an aggregator class. The moment you try to protect aggregate functionality from the main class, your iterator becomes unintuitive. Personally, as a user of your class, I'd rather see it implementing it's own traversing functionality.

    Of course it is all a matter of design. It's your choice what you want to do. Being that you still would want an iterator class, in order to better protect yourself from future changes, I'd probably partition your stream class roles with a mixin interface class that implemented the aggregate functionality of your stream class. Then, implement the iterator pattern from that aggregate.

    If this is not an option because the stream class will always be used with a buffer, you'll probably be forced to indeed derive your iterator from the stream class. But that's really perverting the idea of a derived class. In any case, you are probably looking into a private inheritance with protected members in the stream class.

    Really, I'd go back to anon initial post and suggest you consider your design. If I was to use your stream class, i'd be slightly annoyed if I had to go through an iterator to traverse what is essentially a non aggregate.
    Last edited by Mario F.; 07-29-2009 at 12:19 PM.
    Originally Posted by brewbuck:
    Reimplementing a large system in another language to get a 25% performance boost is nonsense. It would be cheaper to just get a computer which is 25% faster.

  14. #14
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Quote Originally Posted by whiteflags View Post
    I thought about this some more beyond initially saying "it's a good idea." I thought a little harder.

    I'm curious as what you plan to do with a random access stream, because beyond certain applications (like inserting a line in the middle of a file) I don't really get it. Why would you want the stream to do this alone, instead of say using a list of strings? Cause I mean, streams already do buffering. Data structures are for processing.
    Because I intend to do parsing and I hate to have checks everywhere that says: "Oh hey, we're out of data; fetch another line and redo the whole thing".
    Further, since the stream iterators are only forward-only, it causes annoyances everywhere where I would have to increment the iterator, like, 3 times, do some checks, revert to old position (use old iterator since it can't back-track). Yada, yada.
    This is unintuitive. Wasn't the standard library built for powerful generic code? Well, I disagree. These iterators are just really pathetic. It's time to show them what a real iterator is capable of doing.
    Or so I think.
    Last edited by Elysia; 07-29-2009 at 12:20 PM.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  15. #15
    Lurking whiteflags's Avatar
    Join Date
    Apr 2006
    Location
    United States
    Posts
    9,613
    So you're saying you want to read the whole file at once?

    There's a trick for that, let me find it. *searching*

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. which design is better to wrap another class instance
    By George2 in forum C++ Programming
    Replies: 7
    Last Post: 04-13-2008, 12:27 AM
  2. any comments about a cache design?
    By George2 in forum C Programming
    Replies: 6
    Last Post: 09-14-2006, 12:53 PM
  3. Implementing Inheritence into your design
    By bobthebullet990 in forum C++ Programming
    Replies: 6
    Last Post: 08-05-2006, 04:40 PM
  4. Cprog tutorial: Design Patterns
    By maes in forum C++ Programming
    Replies: 7
    Last Post: 10-11-2004, 01:41 AM