Reputation: 5246
I am currently learning c++ and I am an experienced C# and Java developer.
I have a class B
which contains a member of class A
, and I want users of class B
to be able to change values in class A
but not be able to change the instance of class A
itself.
Basically it want to prevent b.getA() = anotherA;
being allowed.
Is this possible in c++ or is my design completely wrong here?
This is my small c++ program
#include <string>
#include <iostream>
using namespace std;
class A {
public:
string getName()
{
return name;
}
void setName(string value)
{
name = value;
}
private:
string name = "default";
};
class B {
public:
A &getA()
{
return anInstance;
}
private:
A anInstance;
};
int main(int argc, char** argv) {
B b;
cout << b.getA().getName() << std::endl; // outputs "default"
b.getA().setName("not default");
cout << b.getA().getName() << std::endl; // outputs "not default"
A a;
a.setName("another a instance");
b.getA() = a; // I want to prevent this being possible
cout << b.getA().getName() << std::endl; // outputs "another a instance"
}
A C# example of what I am trying to do
class Program
{
class A
{
private string name = "default";
public string getName()
{
return name;
}
public void setName(string value)
{
name = value;
}
}
class B
{
private A anInstance;
public A getA()
{
return anInstance;
}
}
static void Main(string[] args)
{
B b = new B();
Console.WriteLine(b.getA().getName()); // outputs "default"
b.getA().setName("not default");
Console.WriteLine(b.getA().getName()); // outputs "not default"
}
}
Upvotes: 2
Views: 1544
Reputation: 52
explicitly disable value-copying!
private:
A(const A&);
A& operator=(const A&);
Upvotes: 0
Reputation: 45434
When you allow general non-const
access to a member (such as A B::anInstance
), then this implies access to all the (public
) members of that member (const
and not). In particular, you provide access to the operator=
, which allows the contents of the member to be changed. Of course, it's still the same A
(with the same address), but its data have changed.
In your C# code you are actually never allowing/using non-const
access to the B::anInstance
, so your C++ is not really equivalent. Consider
class B
{
A anInstance; // private by default
public:
A const& getA() const { return anInstance; } // const access only
A & getA() { return anInstance; } // full access
A copyA() const { return anInstance; } // make a copy
};
The first getA()
is accessible from const B
and only allows const
access to anInstance
, i.e. only to const
members of A
. The second getA()
is only accessible from non-const
B
and allows full (public
) access to anInstance
, including A::operator=(A const&)
(if present, i.e. either declared or not =delete
in the definition of class A
).
Finally, copyA()
provides no access whatsover to B::anInstance
, but returns merely a copy to it. Typically (if A
is non-trivial and/or large) this requires much more effort than merely returning a reference (like a pointer), but in terms of usage/effect, it is very similar to getA() const
(it's different if some const
member of A
actually change the state of A
or if you use the dreaded const_cast<>
as in const_cast<A&>(b.getA())=otherA
).
Upvotes: 2
Reputation: 171303
You wrote:
A a;
a.setName("another a instance");
b.getA() = a; // I want to prevent this being possible
I ask, why?
Why do you want to prevent that?
Your next line is:
cout << b.getA().getName() << std::endl; // outputs "another a instance"
But that is misleading, you have not changed the instance of A
inside b
, you've only changed b.anInstance
to be a copy of a
. In other words, you've changed the name to say "another a instance"
but that doesn't mean it's true. It's no more true that it's another instance than if you called b.getA().setName("another a instance")
(in fact, the result is identical to doing that!)
Try it:
A a;
a.setName("another a instance");
std::cout << &b.getA() << std::endl;
b.getA() = a;
std::cout << &b.getA() << std::endl;
You'll get the same address printed both times, because b.getA() = a
does not replace b.anInstance
, it just modifies it, just like calling setName
does.
That's because in C++ B::anInstance
is not just a reference to an A
, it is an A
, and so by assigning to it you don't change the reference to point to a different A
, you change the A
itself.
So, to go back to your original question, since the thing you're worried about doesn't happen anyway why do you need to prevent it? If you already allow b.anInstance
to be modified via the setName()
function, why not just let it be modified by assignment as well?
If the answer to that question is that there are other properties of A
which you don't want to be changed, and the assignment would change them, then instead of exposing the whole A
object via B::getA()
just add a new member function to B
which sets the name. Doing this is better encapsulation than simply exposing the entire A
object anyway. Too often Java and C# seem to encourage bad designs involving getters and setters for everything, which is pointless and encapsulates nothing; you might as well just make every member public and access them directly if there is a setter for everything.
If you want a B
that contains an A
that doesn't change except for its name, then don't expose the whole A
, just provide a setter on the outer object:
class A {
public:
string getName() const // N.B. Added 'const' here
{
return name;
}
void setName(string value)
{
name = value;
}
private:
string name = "default";
};
class B {
public:
// Read-only access to the contained object:
const A& getA() const
{
return anInstance;
}
// Update the name of the contained object:
void setName(string value)
{
anInstance.name = value;
}
private:
A anInstance;
};
Upvotes: 4
Reputation: 521
You might want to use const pointer instead of reference, but it can have a lot of side effects:
class A {
public:
string getName() const
{
return name;
}
void setName(string value)
{
name = value;
}
private:
string name;
};
class B {
public:
A * const getA() const
{
return anInstance;
}
private:
A* anInstance;
};
int main(int argc, char** argv) {
B b;
cout << b.getA()->getName() << std::endl; // outputs "default"
b.getA()->setName("not default");
cout << b.getA()->getName() << std::endl; // outputs "not default"
A a;
a.setName("another a instance");
b.getA() = a; // I want to prevent this being possible
cout << b.getA()->getName() << std::endl; // outputs "another a instance"
}
As @Captain Price mentioned you can prohibit =operator of A class:
class A {
public:
string getName()
{
return name;
}
void setName(string value)
{
name = value;
}
private:
string name;
A& operator=(const A& other);
};
class B {
public:
A &getA()
{
return anInstance;
}
private:
A anInstance;
};
int main(int argc, char** argv) {
B b;
cout << b.getA().getName() << std::endl; // outputs "default"
b.getA().setName("not default");
cout << b.getA().getName() << std::endl; // outputs "not default"
A a;
a.setName("another a instance");
b.getA() = a; // I want to prevent this being possible
cout << b.getA().getName() << std::endl; // outputs "another a instance"
}
Upvotes: 0