Method that only one other class type may access

C

Christopher

I know I need some sort of friend, but I don't want to friend the
entire class.

I've got a TcpConnection class and it has a state member.
I've got a TcpListener class that accepts connections and creates a
TcpConnectionClass.

The TcpListener class needs to be able to change the state member of
the TcpConnection class, but I don't want anyone else to be able to.
How do I go about that?
 
C

Christopher

I know I need some sort of friend, but I don't want to friend the
entire class.

I've got a TcpConnection class and it has a state member.
I've got a TcpListener class that accepts connections and creates a
TcpConnectionClass.

The TcpListener class needs to be able to change the state member of
the TcpConnection class, but I don't want anyone else to be able to.
How do I go about that?

Problem 1 is that the first class needs to be aware of the second, so
when I use friend function I have a circular dependency problem when
keeping seperate headers.

Problem 2 is that the function I want to friend is virtual. I cannot
find whether or not making it a friend also makes its derived type
implementations of that function a friend also.

Problem 3 is that I will also derive from the class I am allowing
access to.
So, I want class B and all its derived types to have access
to the data in class A and all its derived types.
 
M

Marcel Müller

I know I need some sort of friend, but I don't want to friend the
entire class.

I've got a TcpConnection class and it has a state member.
I've got a TcpListener class that accepts connections and creates a
TcpConnectionClass.

The TcpListener class needs to be able to change the state member of
the TcpConnection class, but I don't want anyone else to be able to.
How do I go about that?

Most likely you have a design problem. A listener should not change any
other state than its own. A listener is a listener and a connection is a
connection. Why should a listener manipulate the connection state. What
about race conditions?

There are ways to do so, but I am in doubt whether this is a good
advise. You could create a base class of your connection to isolate the
state member and make the listener a friend of this base class.


Marcel
 
C

Christopher

Most likely you have a design problem. A listener should not change any
other state than its own. A listener is a listener and a connection is a
connection. Why should a listener manipulate the connection state. What
about race conditions?

There are ways to do so, but I am in doubt whether this is a good
advise. You could create a base class of your connection to isolate the
state member and make the listener a friend of this base class.

Marcel

Well, the listener accepts connections right?
So, it creates a connection in the NOT_CONNECTED state.
It queues it up for an accept via boost::asio
When the accept completes the listener is notified via boost::asio
the listener has to be notified, so it can add the connection to its
collection of clients
Now we have a connected connection in a NOT_CONNECTED state = bad.

I was fiddling with the boost::binds that the boost::asio example uses
and was thinking maybe:
I could have the listener post the accept request with a callback to
the connection itself
Have the connection itself, in its callback, call a callback on the
listener.

I don't want to turn things in to spaghetti though.
 
M

Marcel Müller

Well, the listener accepts connections right?
So, it creates a connection in the NOT_CONNECTED state.
It queues it up for an accept via boost::asio
When the accept completes the listener is notified via boost::asio
the listener has to be notified, so it can add the connection to its
collection of clients
Now we have a connected connection in a NOT_CONNECTED state = bad.

Move the connection pool to the connection class (or a separate
connection pool class) and provide a public factory function that
initiates a connected connection.

I was fiddling with the boost::binds that the boost::asio example uses
and was thinking maybe:
I could have the listener post the accept request with a callback to
the connection itself
Have the connection itself, in its callback, call a callback on the
listener.

Sorry, I didn't catch what you mean.


Marcel
 
N

Noah Roberts

I know I need some sort of friend, but I don't want to friend the
entire class.

I've got a TcpConnection class and it has a state member.
I've got a TcpListener class that accepts connections and creates a
TcpConnectionClass.

The TcpListener class needs to be able to change the state member of
the TcpConnection class, but I don't want anyone else to be able to.
How do I go about that?

I think you could do this with a function object. Something like this
untested bit:

struct TcpListener;

struct TcpConnection
{
struct funky_operator
{
friend class TcpListener;
funky_operator(TcpConnection * c) : conn(c) {}
private:
void operator() (??) { ?? }
TcpConnection * conn;
};
friend class funky_operator;
funky_operator op;

TcpConnection() : op(this) {}
};

Now, everyone can see "op" but only TcpListener can call it.
 
N

Noah Roberts

Problem 1 is that the first class needs to be aware of the second, so
when I use friend function I have a circular dependency problem when
keeping seperate headers.

No you don't, because you're going to forward declare your classes
only in headers and only include stuff you are USING in that file.
Problem 2 is that the function I want to friend is virtual. I cannot
find whether or not making it a friend also makes its derived type
implementations of that function a friend also.

Have the functor I described in my last post call a protected virtual
function.
Problem 3 is that I will also derive from the class I am allowing
access to.
          So, I want class B and all its derived types to have access
to the data in class A and all its derived types.

Have a protected function in the base Listener that can be called by
derived functions. Do your friendly access here. Your derived
objects won't be able to access the class that friended the base
directly.

Of course, as others have said this is probably a bad design. It is
certainly more than just a little coupled. But if you really need to,
that's one way it could be done.
 
P

Paul N

I know I need some sort of friend, but I don't want to friend the
entire class.

I've got a TcpConnection class and it has a state member.
I've got a TcpListener class that accepts connections and creates a
TcpConnectionClass.

The TcpListener class needs to be able to change the state member of
the TcpConnection class, but I don't want anyone else to be able to.
How do I go about that?

As a very low tech solution - but which may be sufficient for you...

Why not provide a suitable access function to do what needs to be
done, but give it a long name that includes the name of the class/
function that you want to give access to? Any other programmers will
see that the function exists but will realise that they're not meant
to use it.

Paul.
 
I

Ian Collins

I know I need some sort of friend, but I don't want to friend the
entire class.

I've got a TcpConnection class and it has a state member.
I've got a TcpListener class that accepts connections and creates a
TcpConnectionClass.

The TcpListener class needs to be able to change the state member of
the TcpConnection class, but I don't want anyone else to be able to.
How do I go about that?

If a design doesn't fit the problem, the design is flawed. It looks to
me like you are over engineering what is a very simple (and common)
task. Don't fret though, I see this all the time! Socket related
activities simply don't fit well within an OO approach.
 
J

Jorgen Grahn

If a design doesn't fit the problem, the design is flawed. It looks to
me like you are over engineering what is a very simple (and common)
task. Don't fret though, I see this all the time! Socket related
activities simply don't fit well within an OO approach.

It is not clear to me what you mean with that last sentence, but I was
going to say something similar:

I see no great benefits of wrapping the BSD socket API in lots of stuff.
If you wrap them a bit to get:

- file descriptors which aren't convertible to and from ints
- RAII

then the API is pretty usable as it is. Bonus: anyone who knows the
API doesn't have to relearn another one.

/Jorgen
 
I

Ian Collins

It is not clear to me what you mean with that last sentence, but I was
going to say something similar:

Trying to use things like connection and listener classes!
I see no great benefits of wrapping the BSD socket API in lots of stuff.
If you wrap them a bit to get:

- file descriptors which aren't convertible to and from ints
- RAII

then the API is pretty usable as it is. Bonus: anyone who knows the
API doesn't have to relearn another one.

Agreed.
 
C

Christopher

Trying to use things like connection and listener classes!




Agreed.

Yes, I am coming to realize the sockets just don't fit OO.
The concept that a listener creates an accepted connection, but the
accepted connection does the communicating is a pain in my butt.
I've done many socket implementations before and didn't have such a
problem with this, the added restrictions of how boost::asio::ip::tcp
works is really adding headaches.

Any operation gets posted for completion and calls back a given method
when it completes. that is enforced by boost::asio::ip::tcp
asynchronous calls.

Well Ok, so the listener gets notified when an accept has occured.
boo.
Now the state of the new connection needs to change and do the things
it does when it gets connected!
So, either
1) The listener has to tell the accepted connection, "you are alive
now" or
2) We change the callback to the connection itself, who has to tell
the listener, "Hey, I'm alive now, do stuff with me or hand me off to
someone"

Either way there is a circular dependancy.
----
Forward declarations to get rid of this circular dependency do not
seem to work, because of another restriction that is a side effect
from using boost::asio::ip::tcp.

I must make sure that every object in my inheritance tree that will
post an operation for io-completion, gets instantiated as a
shared_ptr, such that the object is guarenteed to stay alive between
the times of posting an operation and the time it completes.

So, when it comes into the code:

class Listener;

Connection::Accepted(/*stuff*/)
{
// Snip
listenerPtr->OnAccepted(shared_from_this());
}

The compiler has to see the declaration of Listener::OnAccepted, the
Listener::SmartPtr, etc.
----------

So what I've decided to do....and I hate doing it...it deriving a
ServerSideConnection class that has the accept, onaccept methods, etc.
and takes, upon its construction, a whole bunch of boost::functions
that can be called back. Then when the connection gets notified of
something, it in turn notifies the listener...This is ugly because
I've got callback everywhere, its debugging hell, and I've got quite a
number of connection implementations that do nothing but get notified
and notify the listener...yuck!

I don't see a way around it.
---------

I suppose when I get time, I'll try and write up a visible and
simplified example explaining my problems on my website and then point
people to it for comment. It might just be too big and complex of a
thing...and off topic...to get into the details here.
 
I

Ian Collins

So what I've decided to do....and I hate doing it...it deriving a
ServerSideConnection class that has the accept, onaccept methods, etc.
and takes, upon its construction, a whole bunch of boost::functions
that can be called back. Then when the connection gets notified of
something, it in turn notifies the listener...This is ugly because
I've got callback everywhere, its debugging hell, and I've got quite a
number of connection implementations that do nothing but get notified
and notify the listener...yuck!

I don't see a way around it.

Just use the native interface. On one of my projects we had a long
discussion about this topic and that was our conclusion: KISS!
 
W

Werner

I know I need some sort of friend, but I don't want to friend the
entire class.

I've got a TcpConnection class and it has a state member.
I've got a TcpListener class that accepts connections and creates a
TcpConnectionClass.

The TcpListener class needs to be able to change the state member of
the TcpConnection class, but I don't want anyone else to be able to.
How do I go about that?

This is how I did it:


TcpSvrConnectionListener:

void subscribeForConnections( IfToSvr& ifToSvr );
void unsubscribeForConnections( IfToSvr& ifToSvr );

IfToSvr was nested...
class TcpSvrConnectionListener::IfToSvr
{
public:
virtual void connectedEvt( SOCKET acceptDescriptor ) = 0;
unsigned getPortNum() const{ return portNum_; }
const std::string& getIpAddr() const { return ipAddr_; }

protected: //Operations
//Derivitives to specify the port number
IfToSvr(): portNum_( 0 ), ipAddr_( "" ){ }
IfToSvr( unsigned portNum, const std::string& ipAddr )
: portNum_( portNum ), ipAddr_( ipAddr )
{
}

//won't be deleted through this interface
virtual ~IfToSvr(){}
void setPortNum( unsigned portNum ){ portNum_ = portNum; }
void setIpAddr( const std::string& ipAddr ){ ipAddr_ = ipAddr; }

private:
unsigned portNum_;
std::string ipAddr_;
};

TcpSvrConnectionListener ran in it's own thread (listening),
and subscription routines had to be synchronized, as well
as connectedEvt...

The server itself, well, it just needs to implement the
interface and subscribe. Turned out that I separated
the actual server from connections. The server was, on
connectedEvt, responsible for realizing the actual
connection. The server then used the subject observer
pattern to dispatch the connection to all sockets
interested. The first socket accepting the connection
would take ownership of it, and on it being accepted,
the server would relinquish ownership of the connection.

Connections not accepted within a certain amount of time
was destroyed.

All reading was done by a service class (similar
to what boost does) that monitors for data in a
single thread and dispatches data to the applicable
registrant associated with the socket on which the data
was received (also via interface, the TcpSvrConnection
deriving from it...).

Contrary to what Ian Collins says, in my opinion especially
using blocking sockets complicate user code. KISS, but not
at the expense of usage. I haven't looked at boost sockets
much yet, but I've looked at their usage of IO Completion
Ports, and I like the idea of having a "service" responsible
for listening and connecting(for clients), a "service"
responsible for reading, as not having such a service
complicates user code IMHO. Obviously the service class
will be more complicated, but it has a well defined
responsibility and usage is simple, if not opaque.

You (as user) just create a server, associate a socket. Hookup the
appropriate callbacks to receive data (sending is synchronous), and
wholla. In our implementation, the service class has to exist
prior to this, but enabling of the service classes are done
from a central location. One could instantiate the service classes
when needed. We kept that part simple :) and opted for explicit
creation upfront.

We've created interfaces to break dependencies and to communicate
responsibilities of classes in terms of each other.

Good luck :),

Kind regards,

Werner
 
J

Jorgen Grahn

Just use the native interface. On one of my projects we had a long
discussion about this topic and that was our conclusion: KISS!

I'm repeating myself, but an anecdote:

A few years back I decided to write The One True C++ Wrapper For BSD
Sockets. I wanted type safety: distinct types for UDP sockets, TCP
listening sockets, "normal" TCP sockets ... with the type system
preventing mistakes such as doing listen() on an UDP socket or setting
UDP-specific socket options on a TCP socket. But at the same time I
didn't want to invent a brand new interface to learn.

After a while I realized that (a) this was hard work and (b) it was
for the most part not useful work. Any half-decent design in your
application makes most of the mistakes I wanted to eliminate
- unlikely to happen, because you tend to keep different kinds of
sockets apart
- likely to be found quickly if they /did/ happen

So I threw most of it away. I just kept some read/write buffering code
which had proven useful in real life.

(Disclaimer: I have no experience with Boost.asio; perhaps it forces you
in another direction.)

/Jorgen
 
I

Ian Collins

I'm repeating myself, but an anecdote:

A few years back I decided to write The One True C++ Wrapper For BSD
Sockets. I wanted type safety: distinct types for UDP sockets, TCP
listening sockets, "normal" TCP sockets ... with the type system
preventing mistakes such as doing listen() on an UDP socket or setting
UDP-specific socket options on a TCP socket. But at the same time I
didn't want to invent a brand new interface to learn.

After a while I realized that (a) this was hard work and (b) it was
for the most part not useful work. Any half-decent design in your
application makes most of the mistakes I wanted to eliminate
- unlikely to happen, because you tend to keep different kinds of
sockets apart
- likely to be found quickly if they /did/ happen

So I threw most of it away. I just kept some read/write buffering code
which had proven useful in real life.

At least you had the good sense to give up after only one attempt. I
had three goes, over 15 years before realising the error of my ways.
(Disclaimer: I have no experience with Boost.asio; perhaps it forces you
in another direction.)

I've only ever seen it end in tears.
 
M

Miles Bader

Ian Collins said:
At least you had the good sense to give up after only one attempt. I
had three goes, over 15 years before realising the error of my ways.

Ah well, such things tend to be a learning experience, at least ...

-miles
 
J

Jorgen Grahn

On 10/ 5/11 09:19 PM, Jorgen Grahn wrote: ....

At least you had the good sense to give up after only one attempt. I
had three goes, over 15 years before realising the error of my ways.

Well, ask me again 15 years from now ;-)

/Jorgen
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top