Hi, I was writing some code recently and I came up with this small trick to implement read-only public members that are not POD types. Let's say I create a class called Foo that has a list (std::vector) of Bar objects. I could do something like this:
1 2 3 4 5
class Foo
{
public:
std::vector<Bar> barList;
}
Unfortunately if someone then uses this class in the following way, terrible things could happen.
1 2 3 4 5
Foo* myFoo = new Foo();
myFoo->barList.push_back(42); //store this for later
myFoo->barList = std::vector<Bar>(); //this could cause some trouble
printf("%d", myFoo->barList[0]);
However writing the Foo class like this:
1 2 3 4 5 6 7 8
class Foo
{
private:
class myList : public std::vector<int>
{ };
public:
myList barList;
}
will prevent the field barList from being reassigned. The same idea could be achieved by encapsulating the field with a Get() function, but the syntax for this way is nicer.
It is fine. The method should be const though.
It is not necessary to be friends, because constructing it requires direct reference to otherwise restricted members.
Public/private stuff is a contract with the programmer -- not a guarantee. You can subvert it. In this case, it is not subverted, but is used to enforce the contract.
@firedraco,
yeah what you've written looks good, but it still has a messier syntax. If I understand your code correctly, then for a situation like the one i described in the first post, the code would look like this:
1 2 3 4 5 6 7 8 9
class Foo
{
public:
readonly<std::vector<Bar> > barList;
};
Foo* myFoo = new Foo();
myFoo->barList().push_back(0);
printf("%d", myFoo->barList()[0]); //this is what i want to avoid
Also @Duoas, the intention was not to prevent people from calling the public methods available to the class (e.g. assign in your example). My technique just prevents the actual field from being reassigned. Well at least makes it harder anyway. Although I guess people could still do this:
1 2
std::vector<int>* newVec = new std::vector<int>();
memcpy(&(myFoo->barList), newVec, sizeof(std::vector<int>));
It would be nice if the whole thing could be encapsulated as a template, but I can't see a way to preserve the syntax that way.
Actually it will compile without the const keyword. It still doesn't seem to work very nicely though. I can't get the conversion operator to work without an explicit cast. The code I have looks like this:
@Athar, vector was just an example. I was planning on writing my own container class that would not contain methods like the ones you describe. So in that case the only way of wiping out the data in the container in an unsafe way would be to reassign the field to an empty container. The way I described allows you to just make the field public, so there's no need for encapsulation in a function.
Also @firedraco, I finally realised what you were getting at, and your code looks good for POD types, but won't work correctly with a class that has methods you want to call. To prevent assignment it works fine though.
wiping out the data in the container in an unsafe way
What is an "unsafe way" to you? Then what about erase and pop_back?
Or do you basically want a container that does not allow removal of any elements at all?
Then don't do it assignable in the first place...
With your original code,
1 2
Foo a,b;
a.barList = b.barList; //perfectly legal
¿what do you understand by read-only?
By the way, I prefer to avoid T *var = new T(); in C++.
In general use T var; so there is no risk of memory leaks, (as you seem to know at compile time what you want)
@ne555 by read-only i meant a field that could be read but not assigned to (from outside the class). I think you might have sunk my battleship though, hadn't thought of assigning the field from one instance to another instance of the same class. I guess it's not completely foolproof.
@Athar, what I want in a container depends on the situation, but most of the time I don't want it to be possible to reassign the field to a different container. The original idea for this method came about when I wrote a class that populates a container, and I want that container to be accessible from outside the class (using the container's public interface), but I didn't want it to be possible for someone to overwrite the entire container with a brand new instance. And yes, in some cases, I may want a container that does not allow removal of elements.