views:

511

answers:

4
+2  Q: 

C++ friend classes

Hi,

I realize that there are a lot of questions regarding friend classes in C++. My question, though, is tied to a specific scenario. Given the below code, is it appropriate to use friend in such a manner?

class Software
{
    friend class SoftwareProducer;

    SoftwareProducer* m_producer;
    int m_key;
    // Only producers can produce software
    Software(SoftwareProducer* producer) : m_producer(producer) { }

public:
    void buy()
    {
        m_key = m_producer->next_key();
    }
};

class SoftwareProducer
{
    friend class Software;

public:
    Software* produce()
    {
        return new Software(this);
    }

private:
    // Only software from this producer can get a valid key for registration
    int next_key()
    {
        return ...;
    }
};

Thanks,

Best regards,

+2  A: 

This seems perfectly reasonable to me.

Check out Factories: encapsulating object creation from Bruce Eckel's Thinking in C++ for more on this particular use of friends.

You might also have a look at the C++ FAQ on friends for some more rules of thumb.

tgamblin
+3  A: 

Sure, that's perfectly reasonable. Basically what you have done is very similar to a factory pattern. Seems perfectly reasonable since your code seems to imply that every Software object should have a pointer to its creator.

Though, usually you can avoid to have "Manager" classes like SoftwareProducer, and just have static methods in Software. Since it seems likely that there will be only one SoftwareProducer. Something like this perhaps:

class Software {
private:
    Software() : m_key(0) { /* whatever */ }
public:
    void buy() { m_key = new_key(); }
public:
    static Software *create() { return new Software; }
private:
    static int new_key() { static int example_id = 1; return example_id++; }
private:
    int m_key;
};

Then you can just do this:

Software *soft = Software::create();
soft->buy();

Of course if you do plan on having more than one SoftwareProducer object, then what you have done seems appropriate.

Evan Teran
+1  A: 

I think making SoftwareProducer as a friend of Software is acceptable but I see no reason why Software has to be the friend of SoftwareProducer class. It is an unnecessary dependency between them. You can take key as a constructor argument for the Software class. Also you might want to make the destructor of Software class private so that nobody except the SoftwareProducer can destroy them.

Naveen
I made Software a friend of SoftwareProducer in order to have access to the next_key method which is private.
Alex
Isn't it the other way round? To allow Software's private constructor to be called from SoftwareProducer you need to make SoftwareProducer a friend of Software which as I said makes sense. But you also made Software a friend of SoftwateProducer so that the method next_key() can be called which I feel is unnecessary.
Naveen
How else could a software instance access its producer's next_key method if it's private?
Alex
That's why I said you can pass the key as the constructor parameter for the Software class.
Naveen
I haven't mentioned this, but the key is likely to change. And why would the software come with a key? It makes sense to only take one when the customer pays :) But I'm digressing.
Alex
Agreed..then it definitely makes sense to make them friends..
Naveen
+1  A: 

I won't discuss on the friendship issue, but I tend to dislike cyclic dependencies. Software depends on SoftwareProducer that depends itself on Software... I would try to refactor.

Also note that the friendship relationship opens the internals to all instances of the other class. That is as much as saying that your comment in *next_key* is false:

Making *next_key* private disallows any class from calling it, but once you open up to Software class, any software can call *next_key* on all SoftwareProducers.

David Rodríguez - dribeas
+1, you're technically right re: next_key() -- any Software can call it -- but it's also not a security risk since you have control over Software's definition and friendship is not transitive or inherited. Also it's good to avoid cyclic dependencies where possible, but they are sometimes necessary -- can you propose a better alternative in this case?
j_random_hacker
Indeed, I don't like cyclic dependencies, that's why I posted this question. I agree that it's a (purpousely) convoluted example.
Alex
Most cyclic dependencies can be removed by defining interfaces and depending on them. Now, once you start that way using private+friend to control access will become much harder. I would probably move the buy operation out of Software and into either an external Dealer/Shop or even SoftwareProducer, and push access control into the logic instead of the type system.
David Rodríguez - dribeas
@dribeas: Sounds good. I like interfaces in this situation because they reduce coupling -- Software and SoftwareProducer then both depend on an interface instead of each other (one implementing, the other as a client) -- but yes it makes the friend trick harder unfortunately.
j_random_hacker
Does it really make sense that buy() is a method of software? I guess a Seller interface (implemented by Shop and SoftwareProducer [on-line sales]) would seem more natural. Then the access relationship can be stablished between ParticularSoftwareProducer and the set of AuthorizedSellers, which makes sense in real life... again, it mostly depend on the model you want to implement
David Rodríguez - dribeas
A user would go to a Seller and request buyint a title. The Seller will provide the Software that is in fact the set of 'InstallationMedia' and a key acquired from the SoftwareProducer (that has declared that Seller as viable). You can implement access control through private+friendship, but you have ended up with a more complex model.
David Rodríguez - dribeas