Friend Class with Limited Access

friend class with limited access

It depends on what you mean by "a nice way" :) At comp.lang.c++.moderated we had the same question a while ago. You may see the discussion it generated there.

IIRC, we ended up using the "friend of a nested key" approach. Applied to your example, this would yield:

class A
{
};

class B
{
public:
class Key{
friend class A;
Key();
};

void setFlags(Key){setFlags();}

private:
void setState();
void setFlags();
};

The idea is that the public setFlags() must be called with a "Key", and only friends of Key can create one, as its ctor is private.

Allowing a friend class to access only some private members

There's nothing to make a class a friend of one specific function, but you can make FooB a friend of a "key" class with private constructor, and then have FooA::Hello take that class as an ignored parameter. FooC will be unable to provide the parameter and hence can't call Hello:

Is this key-oriented access-protection pattern a known idiom?

c++ friend class keyword within class definition

There's a difference between "having access to private members" and "being composed of a" (Edit: actually composition is another thing).

#include <iostream>    

class B;

class A {
friend class B;
private:
int x;
};

class B {
static void print(const A& a) {
// A::x is private, but B is a friend of A, so it's fine
std::cout << a.x << std::endl;
}
};

int main() {
A a;
B::print(a);
return 0;
}

This is legal. A::x is private, but it can be used by B's methods as A declares that B is a friend class and can access its private/protected members. However, B isn't an A, in particular, B is an empty class (that's why you have to pass an A to print).

#include <iostream>

class A {
private:
int x;
};

class B : public A {
void print() {
// error A::x is private!
std::cout << A::x << std::endl;
}
};

int main() {
B b;
b.print();
return 0;
}

This is illegal. B inherits from A but it doesn't grant him any access to its private members, even if it actually contains them. B isn't empty anymore and actually contains an instance of A (hence of A::x although it cannot access it)

#include <iostream>

class A {
protected:
int x;
};

class B : public A {
void print() {
// A::x is protected, but B inherits from A, so it's fine
std::cout << A::x << std::endl;
}
};

int main() {
B b;
b.print();
A a;
// error a::x is protected and can only be accessed from A or from any class which inherits from A
// std::cout << a.x << std::endl;
return 0;
}

This is legal. B has access to the protected member A::x as it inherits from A, B is also an A and is actually non-empty.

c++ how to properly declare friend class method of another class

To make B a friend:

class A
{
int member;
friend /*class*/ B; // class is optional, required if B isn't declared yet
};

Note that friends are anti-patterns - if something is private, it probably shouldn't be accessed. What are you trying to accomplish? Why isn't A self-contained? Why does another class need to access its internal data?

Use friend if you have valid answers/reasons for these questions.

When should you use 'friend' in C++?

Firstly (IMO) don't listen to people who say friend is not useful. It IS useful. In many situations you will have objects with data or functionality that are not intended to be publicly available. This is particularly true of large codebases with many authors who may only be superficially familiar with different areas.

There ARE alternatives to the friend specifier, but often they are cumbersome (cpp-level concrete classes/masked typedefs) or not foolproof (comments or function name conventions).

Onto the answer;

The friend specifier allows the designated class access to protected data or functionality within the class making the friend statement. For example in the below code anyone may ask a child for their name, but only the mother and the child may change the name.

You can take this simple example further by considering a more complex class such as a Window. Quite likely a Window will have many function/data elements that should not be publicly accessible, but ARE needed by a related class such as a WindowManager.

class Child
{
//Mother class members can access the private parts of class Child.
friend class Mother;

public:

string name( void );

protected:

void setName( string newName );
};

friend class with limited access

It depends on what you mean by "a nice way" :) At comp.lang.c++.moderated we had the same question a while ago. You may see the discussion it generated there.

IIRC, we ended up using the "friend of a nested key" approach. Applied to your example, this would yield:

class A
{
};

class B
{
public:
class Key{
friend class A;
Key();
};

void setFlags(Key){setFlags();}

private:
void setState();
void setFlags();
};

The idea is that the public setFlags() must be called with a "Key", and only friends of Key can create one, as its ctor is private.

friend function cant access private memebers

First things first, Error #1:

main.cpp:9:1: error: ‘::main’ must return ‘int’
9 | void main(int args, char* argv) {
| ^~~~
main.cpp:9:6: warning: second argument of ‘int main(int, char*)’ should be ‘char **’ [-Wmain]
9 | void main(int args, char* argv) {
| ^~~~

The fix is easy:

int main(int args, char* argv[])

or

int main([[maybe_unused]] int args, [[maybe_unused]] char* argv[])

or even

int main()

Error #2:

In file included from player.hpp:6,
from main.cpp:3:
item.h:18:12: error: ‘player’ does not name a type
18 | friend player operator+(player &&obj,const item &tem);
| ^~~~~~

This is more difficult to interpret. Your class item depends on class player and player depends on item. This is impossible for the compiler to go through. Solution:

In item.h replace

#include "player.hpp"

with

class player;

This is a forward declaration. Class item uses player only here:

    friend player operator+(player &&obj,const item &tem);

that is, the compiler needs to form only a reference to a player, it needs not to have the detailed knowledge about what player actually is. This is a common "trick": when class A uses only pointers or references to B, the forward declaration of B is completely enough. Moreover, be eliminating #include, you speed up the compilation a bit.

  main.cpp: In function ‘int main(int, char*)’:
main.cpp:13:9: error: cannot bind rvalue reference of type ‘player&&’ to lvalue of type ‘player’
13 | a = a + deathSword;
| ^

Don't use things you don't understand. Or better: don't use two things you don't understand at the same time. Move semantics is rarely seen outside move constructors. Until you're an expert, try and refrain from using && in places other than the move constructor and moving operator=. Actually, even if you won't use them at all, your program will be perfectly correct - not using move semantics does not make the program incorrect, it may only render it run a bit slower that it could with move semantics being used correctly. So, turn:

    friend player operator+(player &&obj,const item &tem);

into

    friend player operator+(player &obj, const item &tem);

Also, delete the move constructor in player and any cases where you use &&, because it moves nothing. All you do is to shoot at your knee.

Error #4

After all these changes, the compiler presents a series of new complaints of similar type:

 player.cpp: In function ‘player operator+(player&&, const item&)’:
player.cpp:58:24: error: ‘int item::health’ is private within this context
58 | *obj.health += tem.health;
| ^~~~~~
In file included from player.hpp:6,
from player.cpp:1:
item.h:11:9: note: declared private here
11 | int health; // Bonus health

This is because you messed up almost all friend declaration(s).
The fix is similar to the one used in `item.hpp". Instead of

friend player operator+(player& obj,const  player& tem);

declare

class item;

and then the true operator+:

friend player operator+(player& obj, const item& tem);

Error 5
Remove * from *obj.health += tem.health;

GENERAL REMARKS

  • Don't write tons of code without compiling it. If you're learning a new language / how to program, write a few lines and compile. In this way you'll always face 1, perhaps two bugs, usually easy to localize.
  • Don't learn several things at a time. Here you've shown you have a very limited understanding of:
    • how to handle multiple source / header files,
    • what is move semantics,
    • how to use friend declarations.

and I didn't even look into the quality of your code, I just tried to made it compile.

  • If you're C++ beginner, you don't need move semantics, friend declaration, operator overloading. Trying to use these features, you're distracting your attention from really important objectives. You don't have to be a C++ expert to use it. Learn gradually and use only the tools you understand well, or learn one thing at a time.

C++ friend subclasses access private members (strategy pattern)

Option 2 should be rejected because it does not scale. You will be continually modifying Chromosome to keep it up to date with new CrossoverStrategies.

Option 1 is a strange idea because it places the getter function for Chromosome's data members outside of Chromosome. I can see some cases where this is an attractive idea, if getGenes is made protected, but I'm not convinced here. Consider instead

Option 1-A

class Chromosome
{
public:
const std::vector<double>& getGenes() const
{
return m_genes;
}
private:
std::vector<double> m_genes;
};

Everyone who can access a Chromosome can access getGenes, but they can't do anything to damage it and Chromosome remains blissfully ignorant of its users.

Option 3: Use The Pimpl Idiom

Short and stupid example with a few flaws to keep the demo short

Chromosome.h ++++++++++++++++++++++++++++++++++++++++++++++++

#include <vector>
class Chromosome; // forward declaration only
class CrossoverStrategy
{
public:
virtual ~CrossoverStrategy() = default;
virtual std::vector<Chromosome*> crossover(Chromosome *parent1, Chromosome *parent2) = 0;
};

Chromosome * ChromosomeFactory(/* some construction parameters here */);
// should also provide a declaration of a factory function to provide CrossoverStrategies

CrossoverStrategies.h ++++++++++++++++++++++++++++++++++++++++++++++++

#include "Chromosome.h"

class CrossoverStrategyExample1 : public CrossoverStrategy
{
public:
virtual std::vector<Chromosome*> crossover(Chromosome *parent1, Chromosome *parent2);
private:
};

CrossoverStrategies.cpp ++++++++++++++++++++++++++++++++++++++++++++++++

#include "CrossoverStrategies.h"
class Chromosome
{
public:
std::vector<double> m_genes;

// silence a warning
Chromosome(): m_genes{}
{

}
};

// Because Chromosome is only defined in this file, only this file can use the internals
// of Chromosome. They are public, but the outside world doesn't know that

Chromosome * ChromosomeFactory(/* some construction parameters here */)
{
// Probably makes and returns a pointer to a Chromosome,
// but could pull it from a list, copy construct from a template, etc...
return new Chromosome(/* some construction parameters here */);
}

// should also provide a definition of a factory function to provide CrossoverStrategies

std::vector<Chromosome*> CrossoverStrategyExample1::crossover(Chromosome *parent1,
Chromosome *parent2)
{
for (unsigned long i = 0; i < parent1->m_genes.size(); i++)
parent1->m_genes[i] = 0.0;
return std::vector<Chromosome*>{}; // silence a warning
}

Main.cpp ++++++++++++++++++++++++++++++++++++++++++++++++

#include "Chromosome.h"
#include "CrossoverStrategies.h" // A bad idea. Forces recompilation when strategies are added

int main()
{
Chromosome * p1 = ChromosomeFactory(/* some construction parameters here */);
p1->m_genes.push_back(0.0); // will fail to compile (incomplete type)
Chromosome * p2 = ChromosomeFactory(/* some construction parameters here */);

// probably should hide the next line with a factory as well
CrossoverStrategy * strategy = new CrossoverStrategyExample1();
strategy->crossover(p1, p2);
}

A quick afterword on security. It always comes at a cost. Generally it makes things harder to use. It makes them harder for an attacker, but it also makes things harder for the legitimate users. Whether it's worth it or not is up to you.



Related Topics



Leave a reply



Submit