c# style read-only properties

Pages: 12
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.

1
2
3
myFoo->getBarList()[0];
vs.
myFoo->barList[0];


thoughts, comments?
Last edited on
myFoo->barList.assign( ... ); still works.

Although, I do like the pretty syntax.
The way I've seen other people do read-only members in C++ is like this:
1
2
3
4
5
6
class Foo {
private:
        int bar;
public:
        const int& Bar;
};

Ideally you'd be able to encapsulate that concept in a class so you could do this:
1
2
3
4
5
6
7
8
9
template <typename T>
class readonly {
        // ?
};

class Foo {
public:
    readonly <int> Bar;
};

but I don't know how it could be done.
You could have the readonly class have a conversion to a const int& as well as some constructors etc to make the internals:

1
2
3
4
5
6
7
8
9
10
template <typename T>
class readonly {
private:
    T m_data;
public:
    readonly() = default;
    readonly(const readonly& other) = default;
    readonly(const T& data) : m_data(data) {}
    const T&() { return this->m_data; } //I don't remember the syntax on conversions so this might be wrong
};
Last edited on
Should a readonly attribute have a default constructor? And it should be friend of the class using it, no?
Yes, what happens if you want to make wrap an std::string? You'd need a default constructor.

As for friendship, yeah it probably should since the class would need to modify it...so I dunno if you could do that then.
Yes, what happens if you want to make wrap an std::string? You'd need a default constructor.


 
readonly<std::string> stuff(std::string());
?
Last edited on
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.

Hope this helps.
True. That would waste some extra time constructing a default string then copying it over though. You might as well add the default constructor.
But your code doesn't compile with objects that have no default constructor.
@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.
You don't need to have the () on the end. The conversion operator will convert it for you IIRC.
Ok I get you now, but I can't get your code to compile. Can you give a (compilable) example?
What compiler errors do you get?
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
template <typename T>
class readonly {
private:
    T m_data;
public:
    readonly() {}
    readonly(const readonly& other) {}
    readonly(const T& data) : m_data(data) {}
    operator T&() { return this->m_data; }
};

class Foo
{
public:
    readonly<std::vector<int> > intList;
    void DoSomething()
    {
        intList.push_back(0);    //this won't work
        ((std::vector<int>)intList).push_back(0);    //this works but is messy
    }
}
Last edited on
It seems this question is based on a misconception.

myFoo->barList = std::vector<Bar>(); //this could cause some trouble

So, what kind of trouble, exactly?
Why do you think it's different from things like
1
2
3
std::vector<Bar>().swap(myFoo->barList);
myFoo->barList.clear();
myFoo->barList.assign(0,Bar());
Last edited on
@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.
Last edited on
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?
Last edited on
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.
Pages: 12