Design problem - proxy class acting as a reference to object

Discussion in 'C++' started by SpOiLeR, Apr 24, 2005.

  1. SpOiLeR

    SpOiLeR Guest

    Hello!

    I have a matrix class like this:

    class MyObject;

    // MyMatrix is contains MyObjects
    class MyMatrix {
    public:
    ...
    MyObject& operator() (int coll, int row);
    ...
    private:
    }

    Now, this allows me writing code like this:

    MyMatrix mat;
    MyObject obj1, obj2;

    mat (2,3) = obj1;
    obj2 = mat (3,2);

    So far, so good.
    MyMatrix also keeps some private constants that hold coordinates of some
    special MyObjects, so retrieving of those special MyObjects is faster. For
    example:

    struct coordinates {
    int col, row;
    }

    class MyMatrix {
    public:
    ...
    MyObject& operator() (int coll, int row);
    ...
    private:
    coordinates specialMyObject;
    }

    Because of operator(), client of MyMatrix is allowed to freely edit
    contents of MyMatrix object, thus invalidating specialMyObject.
    Now, I could write publicly available method Refresh () that refreshes
    state of internal constants. There are two ways how this methods could be
    used:

    1.) Client must call it after editing MyMatrix object.
    2.) It is called internally, by each method that relies on internal
    constants, before those methods start their work.

    Number 2.) is definitely bad because keeping state of object so it doesn't
    have to be checked thoroughly each time needed, and then check it
    thoroughly each time it is needed, completely nullifies purpose of internal
    constants.

    Number 1.) works fine, but from the OO view looks bad, because object
    should take care of its internal data, not the client of that object. So,
    there is something wrong with:

    MyObject& MyMatrix::eek:perator() (int coll, int row);

    I could either replace it with

    MyObject MyMatrix::eek:perator() (int coll, int row);

    and add method:

    void MyMatrix::AsignObject (int coll, int row, const MyObject& obj);

    but this invalidates expression:

    mat (2,3) = obj1;

    Or I could write some kind of proxy class that acts as a reference to
    MyObject but also changes state of MyMatrix object when needed.

    What would that proxy class look like? I don't know where to start from, so
    links or google search keywords would be greatly appreciated.
     
    SpOiLeR, Apr 24, 2005
    #1
    1. Advertising

  2. SpOiLeR

    Kanenas Guest

    On Sun, 24 Apr 2005 12:31:42 +0200, SpOiLeR <request@no_spam.org>
    wrote:

    >Hello!
    >
    >I have a matrix class like this:
    >

    [...]
    >MyMatrix also keeps some private constants that hold coordinates of some
    >special MyObjects, so retrieving of those special MyObjects is faster. For
    >example:
    >
    >struct coordinates {
    > int col, row;
    >}
    >
    >class MyMatrix {
    >public:
    > ...
    > MyObject& operator() (int coll, int row);
    > ...
    >private:
    > coordinates specialMyObject;
    >}
    >

    [...]
    >Or I could write some kind of proxy class that acts as a reference to
    >MyObject but also changes state of MyMatrix object when needed.
    >
    >What would that proxy class look like? I don't know where to start from, so
    >links or google search keywords would be greatly appreciated.


    Let's call the helper "MyMatrixElement". It could contain a reference
    to the MyMatrix object it's supposed to watch over and to an element
    of MyMatrix. Each MyMatrixElement::eek:perator= can call the method of
    MyMatrix which updates MyMatrix's state.

    class MyMatrix {
    private:
    class MyMatrixElement {
    private:
    MyMatrix& watched;
    MyObject& el;
    size_t col, row;

    public:
    MyMatrixElement(MyMatrix& matr, MyObject& obj,
    size_t c, size_t r)
    : watched(matr), el(obj), col(c), row(r) {}
    template <typename _Type>
    MyMatrixElement& operator=(_Type from)
    { el = from; watched.refresh(col, row); return *this;}
    operator MyObject() { return el; }
    operator const MyObject&() const { return el; }
    }

    public:
    MyMatrixElement operator() (size_t col, size_t row);
    const MyObject& operator() const (size_t col, size_t row);
    }

    Making class MyMatrixElement private in MyMatrix means
    MyMatrixElements can only be stored in temporaries outside of
    MyMatrix, so you don't have to worry about old MyMatrixElements
    floating about. If you want to allow a client to store a
    MyMatrixElement in a local (or what have you), make MyMatrixElement
    public, in which case you want to be careful no MyMatrixElement
    outlasts the MyMatrix and MyObject it refers to. Play around with the
    above and see if it works for you.

    Note that MyMatrixElement requires a non-const MyMatrix. This works
    fine, as a const MyMatrix doesn't need a proxy for operator(). The
    'operator() const' ensures a MyMatrixElement is never created for a
    const MyMatrix.

    Kanenas
     
    Kanenas, Apr 25, 2005
    #2
    1. Advertising

  3. SpOiLeR

    SpOiLeR Guest

    On Sun, 24 Apr 2005 21:43:51 -0700, Kanenas wrote:

    > [...]
    > Play around with the above and see if it works for you.
    >
    > [...]
    >
    > Kanenas


    After some adjustments for my real code, your code works perfectly. Thank
    you very much, it is exactly what I needed...
     
    SpOiLeR, Apr 25, 2005
    #3
  4. SpOiLeR

    Capstar Guest

    Just out of curiosity, I tried the following:

    /** matrix.h **/
    #ifndef MATRIX_H__
    #define MATRIX_H__

    class MyMatrix
    {
    private:
    class MyMatrixElement
    {
    private:
    MyMatrix& watched;
    MyObject& el;
    size_t col, row;

    public:
    MyMatrixElement(MyMatrix& matr, MyObject& obj,
    size_t c, size_t r)
    : watched(matr), el(obj), col(c), row(r) {}

    template <typename _Type>
    MyMatrixElement& operator=(_Type from)
    { el = from; watched.refresh(col, row); return *this;}

    //operator MyObject() { return el; }
    operator const MyObject&() const { return el; }
    };

    MyObject _object;

    void refresh(size_t, size_t) {}

    public:
    const MyObject& operator()(size_t col, size_t row) const;
    MyMatrixElement operator()(size_t col, size_t row);
    };

    #endif /*MATRIX_H__*/



    /** matrix.cpp **/
    class MyObject
    {
    public:
    MyObject & add(int) { return *this; }
    };

    //typedef int MyObject;

    #if defined __GNUC__
    typedef unsigned long size_t;
    #endif

    #include "matrix.h"

    const MyObject& MyMatrix::eek:perator()(size_t, size_t) const
    {
    return _object;
    }

    MyMatrix::MyMatrixElement MyMatrix::eek:perator()(size_t col, size_t row)
    {
    return MyMatrixElement(*this, _object, col, row);
    }

    int main(void)
    {
    MyMatrix matrix;
    MyObject obj;

    obj = matrix(0, 0); /* weird behaviour */
    matrix(1, 1) = obj;
    matrix(3, 3) = MyObject(matrix(2, 2)).add(1);

    return 0;
    }


    As you can see I removed "operator MyObject() { return el; }" since
    Visual studio wouldn't compile with both that one and "operator const
    MyObject&() const { return el; }".
    And I don't realy see the use in both of them anyway, but that could be
    caused by my inexpierience.

    Now the main thing I'm wondering is at the line I commented with weird
    behaviour, I noticed that with Visual Studio as well as gcc the method
    "MyMatrix::MyMatrixElement MyMatrix::eek:perator()(size_t col, size_t row)"
    is called followed by "MyMatrixElement::eek:perator const MyObject&()
    const". In my opinion it would be far easier and faster to just call
    "const MyObject& MyMatrix::eek:perator()(size_t col, size_t row) const".

    Can anyone explain this behaviour?

    Thanks,
    Mark
     
    Capstar, Apr 25, 2005
    #4
  5. SpOiLeR

    Axter Guest

    SpOiLeR wrote:
    > Hello!
    >
    > I have a matrix class like this:
    >
    > class MyObject;
    >
    > // MyMatrix is contains MyObjects
    > class MyMatrix {
    > public:
    > ...
    > MyObject& operator() (int coll, int row);
    > ...
    > private:
    > }
    >
    > Now, this allows me writing code like this:
    >
    > MyMatrix mat;
    > MyObject obj1, obj2;
    >
    > mat (2,3) = obj1;
    > obj2 = mat (3,2);
    >
    > So far, so good.
    > MyMatrix also keeps some private constants that hold coordinates of

    some
    > special MyObjects, so retrieving of those special MyObjects is

    faster. For
    > example:
    >
    > struct coordinates {
    > int col, row;
    > }
    >
    > class MyMatrix {
    > public:
    > ...
    > MyObject& operator() (int coll, int row);
    > ...
    > private:
    > coordinates specialMyObject;
    > }
    >
    > Because of operator(), client of MyMatrix is allowed to freely edit
    > contents of MyMatrix object, thus invalidating specialMyObject.
    > Now, I could write publicly available method Refresh () that

    refreshes
    > state of internal constants. There are two ways how this methods

    could be
    > used:
    >
    > 1.) Client must call it after editing MyMatrix object.
    > 2.) It is called internally, by each method that relies on internal
    > constants, before those methods start their work.
    >
    > Number 2.) is definitely bad because keeping state of object so it

    doesn't
    > have to be checked thoroughly each time needed, and then check it
    > thoroughly each time it is needed, completely nullifies purpose of

    internal
    > constants.
    >
    > Number 1.) works fine, but from the OO view looks bad, because object
    > should take care of its internal data, not the client of that object.

    So,
    > there is something wrong with:
    >
    > MyObject& MyMatrix::eek:perator() (int coll, int row);
    >
    > I could either replace it with
    >
    > MyObject MyMatrix::eek:perator() (int coll, int row);
    >
    > and add method:
    >
    > void MyMatrix::AsignObject (int coll, int row, const MyObject&

    obj);
    >
    > but this invalidates expression:
    >
    > mat (2,3) = obj1;
    >
    > Or I could write some kind of proxy class that acts as a reference to
    > MyObject but also changes state of MyMatrix object when needed.
    >
    > What would that proxy class look like? I don't know where to start

    from, so
    > links or google search keywords would be greatly appreciated.


    I still don't completely understand why you would need the proxy class,
    and what the proxy class would do for you in this requirement, however
    consider the following method for a matrix class, that allows the use
    of syntax similar to C-Style [][] double index brackets.
    See following link:
    http://code.axter.com/dynamic_2d_array.h
    You can use it like this:
    int SizeX = 3;
    int SizeY = 5;
    dynamic_2d_array<MyObject> mat(SizeX, SizeY);
    MyObject obj1, obj2;
    mat[2][3] = obj1;
    obj2 = mat[2][3];

    This type of syntax is more natural for C/C++ environment, and
    therefore IMHO, easier to read.

    See following link for a few more different versions of above
    dynamic_2d_array class, including one version that uses std::vector,
    and allows for resize.
    http://www.tek-tips.com/faqs.cfm?fid=5575
     
    Axter, Apr 25, 2005
    #5
  6. SpOiLeR

    SpOiLeR Guest

    On 25 Apr 2005 07:49:26 -0700, Axter wrote:

    >> SpOiLeR wrote:
    >> [...]

    >
    > I still don't completely understand why you would need the proxy class,


    Then re-read original post... I want to update MyObject's internals without
    client needing to interfere in that.

    > of syntax similar to C-Style [][] double index brackets.


    FAQ has something about this, and why it should better to be avoided, and
    replaced with operator()...

    > See following link:
    > http://code.axter.com/dynamic_2d_array.h
    > You can use it like this:
    > int SizeX = 3;
    > int SizeY = 5;
    > dynamic_2d_array<MyObject> mat(SizeX, SizeY);
    > MyObject obj1, obj2;
    > mat[2][3] = obj1;
    > obj2 = mat[2][3];
    >
    > This type of syntax is more natural for C/C++ environment, and
    > therefore IMHO, easier to read.


    Thanks, but that's not what I wanted, anyway check Kanennas post above to
    see what I mean.
     
    SpOiLeR, Apr 27, 2005
    #6
  7. SpOiLeR

    SpOiLeR Guest

    On Mon, 25 Apr 2005 16:09:53 +0200, Capstar wrote:

    > Just out of curiosity, I tried the following:
    >
    > /** matrix.h **/
    > #ifndef MATRIX_H__
    > #define MATRIX_H__
    >
    > class MyMatrix
    > {
    > private:
    > class MyMatrixElement
    > {
    > private:
    > MyMatrix& watched;
    > MyObject& el;
    > size_t col, row;
    >
    > public:
    > MyMatrixElement(MyMatrix& matr, MyObject& obj,
    > size_t c, size_t r)
    > : watched(matr), el(obj), col(c), row(r) {}
    >
    > template <typename _Type>
    > MyMatrixElement& operator=(_Type from)
    > { el = from; watched.refresh(col, row); return *this;}
    >
    > //operator MyObject() { return el; }
    > operator const MyObject&() const { return el; }
    > };
    >
    > MyObject _object;
    >
    > void refresh(size_t, size_t) {}
    >
    > public:
    > const MyObject& operator()(size_t col, size_t row) const;
    > MyMatrixElement operator()(size_t col, size_t row);
    > };
    >
    > #endif /*MATRIX_H__*/
    >
    >
    >
    > /** matrix.cpp **/
    > class MyObject
    > {
    > public:
    > MyObject & add(int) { return *this; }
    > };
    >
    > //typedef int MyObject;
    >
    > #if defined __GNUC__
    > typedef unsigned long size_t;
    > #endif
    >
    > #include "matrix.h"
    >
    > const MyObject& MyMatrix::eek:perator()(size_t, size_t) const
    > {
    > return _object;
    > }
    >
    > MyMatrix::MyMatrixElement MyMatrix::eek:perator()(size_t col, size_t row)
    > {
    > return MyMatrixElement(*this, _object, col, row);
    > }
    >
    > int main(void)
    > {
    > MyMatrix matrix;
    > MyObject obj;
    >
    > obj = matrix(0, 0); /* weird behaviour */
    > matrix(1, 1) = obj;
    > matrix(3, 3) = MyObject(matrix(2, 2)).add(1);
    >
    > return 0;
    > }
    >
    >
    > As you can see I removed "operator MyObject() { return el; }" since
    > Visual studio wouldn't compile with both that one and "operator const
    > MyObject&() const { return el; }".


    gcc works fine, don't have MSVC, so can't try it. But IMHO it should work!

    > And I don't realy see the use in both of them anyway, but that could be
    > caused by my inexpierience.


    > Now the main thing I'm wondering is at the line I commented with weird
    > behaviour, I noticed that with Visual Studio as well as gcc the method
    > "MyMatrix::MyMatrixElement MyMatrix::eek:perator()(size_t col, size_t row)"
    > is called followed by "MyMatrixElement::eek:perator const MyObject&()
    > const". In my opinion it would be far easier and faster to just call
    > "const MyObject& MyMatrix::eek:perator()(size_t col, size_t row) const".
    >
    > Can anyone explain this behaviour?
    >
    > Thanks,
    > Mark


    If you have const MyMatrix object, you won't be able to call constructor
    for MyMatrixElement because it takes non-const reference to MyMatrix. So,
    for const MyMatrix you will need to have

    const MyObject& MyMatrix::eek:perator()(size_t col, size_t row) const

    as a reference operator, because const object would'n be able to call

    MyMatrixElement operator()(size_t col, size_t row);

    So, you need both.



    If you have non const MyMatrix object, and ask for non const Myobject&:

    MyMatrix mat;
    Myobject& objref = mat (2,3);

    you will get compile time error because there MyMatrixElement can't be
    converted into non const Myobject&. This is exactly what was wanted,
    because we don't want client to reference directly MyObject
    unless it just reads them (either by obtaining copy, or const&).

    That's why you need both:

    operator MyObject() { return el; }
    operator const MyObject&() const { return el; }

    So, to conclude, you can either get copy or const reference, but if you
    want to change anything on MyObject you will have to do it through
    reference operator, i.e.

    matrix (2,3) = obj; // This would refresh our matrix
     
    SpOiLeR, Apr 27, 2005
    #7
  8. SpOiLeR

    red floyd Guest

    Capstar wrote:
    > Just out of curiosity, I tried the following:
    >
    > /** matrix.h **/
    > #ifndef MATRIX_H__
    > #define MATRIX_H__


    Don't do this. Any identifier with two consecutive underscores is
    reserved to the implementation. Use MATRIX_H_ instead.
    >
    > [redacted]
     
    red floyd, Apr 27, 2005
    #8
  9. SpOiLeR

    Axter Guest

    SpOiLeR wrote:
    > On 25 Apr 2005 07:49:26 -0700, Axter wrote:
    >
    > >> SpOiLeR wrote:
    > >> [...]

    > >
    > > I still don't completely understand why you would need the proxy

    class,
    >
    > Then re-read original post... I want to update MyObject's internals

    without
    > client needing to interfere in that.
    >
    > > of syntax similar to C-Style [][] double index brackets.

    >
    > FAQ has something about this, and why it should better to be avoided,

    and
    > replaced with operator()...


    I've read the FAQ, and the information on the FAQ is base on a method
    that returns a temporary Type to allow access to second [] index.
    My class does not use that method, and therefore the class I posted is
    much more efficient.
    It's as efficient, if not more efficient then using operator().
    Moreover, it's more compatible to C-Style syntax, and therefore easier
    to use and read.
    It's much better to use common syntax methods, then to use methods that
    result in having to use ambiguous syntax.
     
    Axter, Apr 28, 2005
    #9
  10. SpOiLeR

    Kanenas Guest

    On Mon, 25 Apr 2005 16:09:53 +0200, Capstar <>
    wrote:

    >Just out of curiosity, I tried the following:
    >

    [...]
    >
    >int main(void)
    >{
    > MyMatrix matrix;
    > MyObject obj;
    >
    > obj = matrix(0, 0); /* weird behaviour */
    > matrix(1, 1) = obj;
    > matrix(3, 3) = MyObject(matrix(2, 2)).add(1);
    >
    > return 0;
    >}
    >
    >
    >As you can see I removed "operator MyObject() { return el; }" since
    >Visual studio wouldn't compile with both that one and "operator const
    >MyObject&() const { return el; }".
    >And I don't realy see the use in both of them anyway, but that could be
    >caused by my inexpierience.
    >

    I put that in mostly out of habit. Normally when there's a conversion
    operator returning a reference, there will be a corresponding
    conversion operator differing only in its const-ness and the
    const-ness of the returned reference.

    Offhand, I can't think of a situation using 'MyMatrixElement::eek:perator
    MyObject()' that can't be handled with 'MyMatrixElement::eek:perator
    const MyObject&()' and possibly a temporary created via MyObject's
    copy constructor.

    >Now the main thing I'm wondering is at the line I commented with weird
    >behaviour, I noticed that with Visual Studio as well as gcc the method
    >"MyMatrix::MyMatrixElement MyMatrix::eek:perator()(size_t col, size_t row)"
    >is called followed by "MyMatrixElement::eek:perator const MyObject&()
    >const". In my opinion it would be far easier and faster to just call
    >"const MyObject& MyMatrix::eek:perator()(size_t col, size_t row) const".
    >
    >Can anyone explain this behaviour?
    >

    Here are three reasons for you to consider:

    1) matrix is non-const, so matrix(0,0) binds to the non-const
    'MyMatrix::eek:perator()(size_t, size_t)'. Const-overloading resolution
    is modeled thusly: non-static methods are given an "implicit object
    parameter". E.g. for 'X::f(...)' the implicit object parameter is
    'X&' and for 'X::f(...) const' it's 'const X&'. Consider:
    X x;
    x.f(/* ... */);
    From the view of the resolver, binding 'x.f' to 'X::f(...) const'
    requires an implicit conversion of 'x' to a 'const X&' while binding
    'x.f' to 'X::f(...)' requires no conversions. 'X::f(...)' is thus the
    best viable function.

    2) Picking the const version over the non-const as being the "better"
    would require examining the context of the call to
    'MyMatrix::eek:perator()'. This is outside the realm of C++ semantic
    context sensitivity.

    3) (a biggie) An optimizer could examine context and pick the const
    version, but this may produce undesirably different behavior (which
    includes return type, return value, exceptions and side effects) than
    calling the non-const operator(). There's no semantic or (except for
    some operators) connotative connection between const-overloaded
    methods at the language level; though they may often be semantically
    similar when implemented, that's only a matter of convention. Even
    with operator[], the const version can be semantically different from
    the non-const version.
    As an example, consider map from the STL.
    'map<...>::eek:perator[](const Key& k)' creates an entry if none exists
    for 'k'. There's no 'map<...>::eek:perator[](const Key& k) const', but
    it's easy to imagine an extended library which defined one. The const
    [] shouldn't create an entry; instead, it could throw an out_of_range
    exception. Suppose compilers were allowed to call the const indexing
    operator when its result is used as an rvalue and consider the
    following fragment:
    map<std::string, int> settings;
    //...
    try {
    //if "debug_level" isn't set, it defaults to 0
    dbg_lvl = settings["debug_level"];
    } catch (std::eek:ut_of_range) {dbg_lvl=0;}
    For safety's sake, you have to catch a possible out_of_range
    exception. If the compiler ends up binding 'foo[name]' to
    'map<...>::eek:perator[](std::string)', no out_of_range is ever thrown
    but you still incur the overhead of the try/catch block. Note also
    that all the try/catch does is produce the behavior of the non-const
    []. IMO, blech.

    If you want to ensure 'MyMatrix::eek:perator() const' is called, you can
    use a cast to make 'matrix' const.

    Kanenas
     
    Kanenas, May 30, 2005
    #10
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. John Nagle
    Replies:
    2
    Views:
    1,056
    John Nagle
    Feb 26, 2007
  2. Replies:
    1
    Views:
    514
    Mark Rae [MVP]
    Sep 20, 2007
  3. Mike Mr.
    Replies:
    4
    Views:
    221
    Mike Jo
    Sep 21, 2008
  4. Angela

    acting as a proxy server

    Angela, Nov 25, 2004, in forum: Perl Misc
    Replies:
    2
    Views:
    150
    Tad McClellan
    Nov 25, 2004
  5. blazb
    Replies:
    5
    Views:
    196
Loading...

Share This Page