Virtual Assignment Operator in Protocol class

Discussion in 'C++' started by N4M, Aug 17, 2004.

  1. N4M

    N4M Guest

    Dear,
    Suppose I have a Protocol class, in which I need also an assignment
    operator =().
    class B
    {
    .....
    virtual B& operator=(const B& rb) =0;
    };
    Now in some derived class D: public B, how would I proceed with
    operator=? Do I need to supply 2 operators:
    -01 to override D& operator=(const B& d)
    -and 01 to overload D& operator=(const D& d)?
    How about the (im)purality of = ?
    Thanks for your guidance.
     
    N4M, Aug 17, 2004
    #1
    1. Advertising

  2. N4M wrote:
    >
    > Dear,
    > Suppose I have a Protocol class, in which I need also an assignment
    > operator =().
    > class B
    > {
    > ....
    > virtual B& operator=(const B& rb) =0;
    > };
    > Now in some derived class D: public B, how would I proceed with
    > operator=? Do I need to supply 2 operators:
    > -01 to override D& operator=(const B& d)
    > -and 01 to overload D& operator=(const D& d)?
    > How about the (im)purality of = ?
    > Thanks for your guidance.


    In a nutshell: having op= as virtual is seldome a good idea. Mostly
    because it doesn't work how most people would expect it to work.
    Hint: polymorphism works only by examining the object the function
    is called for, but doesn't take the runtime type of the arguments
    into account.

    Assume class D, derived from class B

    B* p1 = new D;
    B* p2 = new D;

    *p1 = *p2;

    In the above, what do you think the compiler is looking for
    when searching a function to fullfil the request of the
    assignment? :

    1) B::eek:perator=( const B& Arg );
    or 2) B::eek:perator=( const D& Arg );
    or 3) D::eek:perator=( const B& Arg );
    or 4) D::eek:perator=( const D& Arg );

    --
    Karl Heinz Buchegger
     
    Karl Heinz Buchegger, Aug 17, 2004
    #2
    1. Advertising

  3. Karl Heinz Buchegger wrote:
    >
    > N4M wrote:
    > >
    > > Dear,
    > > Suppose I have a Protocol class, in which I need also an assignment
    > > operator =().
    > > class B
    > > {
    > > ....
    > > virtual B& operator=(const B& rb) =0;
    > > };
    > > Now in some derived class D: public B, how would I proceed with
    > > operator=? Do I need to supply 2 operators:
    > > -01 to override D& operator=(const B& d)
    > > -and 01 to overload D& operator=(const D& d)?
    > > How about the (im)purality of = ?
    > > Thanks for your guidance.

    >
    > In a nutshell: having op= as virtual is seldome a good idea. Mostly
    > because it doesn't work how most people would expect it to work.
    > Hint: polymorphism works only by examining the object the function
    > is called for, but doesn't take the runtime type of the arguments
    > into account.


    After rethinking (and smoking a cigarette) I don't think this to be
    relevant any more. Read on ...

    >
    > Assume class D, derived from class B
    >
    > B* p1 = new D;
    > B* p2 = new D;
    >
    > *p1 = *p2;
    >
    > In the above, what do you think the compiler is looking for
    > when searching a function to fullfil the request of the
    > assignment? :
    >
    > 1) B::eek:perator=( const B& Arg );
    > or 2) B::eek:perator=( const D& Arg );
    > or 3) D::eek:perator=( const B& Arg );
    > or 4) D::eek:perator=( const D& Arg );
    >


    Next thought experiment (you might want to try this with your compiler
    anyway): make the operator virtual.
    Which op= is called then?

    Hint: Think about what a dereference operation does when the runtime
    type of an object differs from the static type of the pointer pointing
    to it.

    --
    Karl Heinz Buchegger
     
    Karl Heinz Buchegger, Aug 17, 2004
    #3
  4. "Karl Heinz Buchegger" <> wrote in message
    news:...
    >
    > In a nutshell: having op= as virtual is seldome a good idea. Mostly
    > because it doesn't work how most people would expect it to work.
    > Hint: polymorphism works only by examining the object the function
    > is called for, but doesn't take the runtime type of the arguments
    > into account.


    Then programmer must do that, no?

    > Assume class D, derived from class B
    >
    > B* p1 = new D;
    > B* p2 = new D;
    >
    > *p1 = *p2;
    >
    > In the above, what do you think the compiler is looking for
    > when searching a function to fullfil the request of the
    > assignment? :
    >
    > 1) B::eek:perator=( const B& Arg );
    > or 2) B::eek:perator=( const D& Arg );
    > or 3) D::eek:perator=( const B& Arg );
    > or 4) D::eek:perator=( const D& Arg );


    The symptoms you demonstrate do not exist (nor become better
    or worse) because of virtual assignment, but because of how
    C++ object model works. In real world, virtual assignment is not
    the disease, but the cure!

    Consider this example:

    // ========================================

    struct B
    {
    // this is a pretty canonical class, except for virtual assignment

    virtual B &operator=( const B & );

    // virtual destructor needed for derived deletion thru base *

    virtual ~B();

    // add more members (data or functions) to suit the taste...
    };


    struct D : public B
    {
    int value;

    D( int i=0 ) : value(i)
    {
    }

    D( const D &r ) : B(r) , value(r.value)
    {
    }

    virtual D &operator=( const D &r )
    {
    B::eek:perator=( r );
    value = r.value;
    return *this;
    }

    virtual B &operator=( const B &r )
    {
    const D *p = dynamic_cast<const D *>(&r);

    if( p )
    {
    *this = *p;
    }
    else
    {
    // D is being sliced, but now you can detect and compensate!!!
    B::eek:perator=( r );
    value = 0;
    }

    return *this;
    }
    };

    // ========================================

    Now your example...

    > B* p1 = new D;
    > B* p2 = new D;
    >
    > *p1 = *p2;


    .... will assign as-if using the most intuitive choice...

    > 1) B::eek:perator=( const B& Arg );
    > or 2) B::eek:perator=( const D& Arg );
    > or 3) D::eek:perator=( const B& Arg );
    > or 4) D::eek:perator=( const D& Arg );


    .... which, of course, is 4 .

    - Risto -
     
    Risto Lankinen, Aug 17, 2004
    #4
  5. Risto Lankinen wrote:
    >
    >
    > ... which, of course, is 4 .
    >
    > - Risto -


    Thanks for bringing me back on track.
    I don't know why, but somehow I always get lost when
    thinking about a virtual op= :)


    --
    Karl Heinz Buchegger
     
    Karl Heinz Buchegger, Aug 17, 2004
    #5
  6. N4M

    N4M Guest

    "Risto Lankinen" <> wrote in message news:<fEnUc.22991$>...
    > "Karl Heinz Buchegger" <> wrote in message
    > news:...
    > >

    Thanks for your example, but what if it is insisted to have all
    operators and functions in the ABC be PURE ? Then I cannot initiate B
    b...
    That's driving my crazy.
    N4M
     
    N4M, Aug 17, 2004
    #6
  7. N4M

    N4M Guest

    "Risto Lankinen" <> wrote in message news:<fEnUc.22991$>...
    > "Karl Heinz Buchegger" <> wrote in message
    > news:...
    > >

    Thanks for your example, but what if it is insisted to have all
    operators and functions in the ABC be PURE ? Then I cannot initiate B
    b...
    That's driving my crazy.
    N4M
     
    N4M, Aug 17, 2004
    #7
  8. N4M wrote:
    >
    > "Risto Lankinen" <> wrote in message news:<fEnUc.22991$>...
    > > "Karl Heinz Buchegger" <> wrote in message
    > > news:...
    > > >

    > Thanks for your example, but what if it is insisted to have all
    > operators and functions in the ABC be PURE ? Then I cannot initiate B
    > b...
    > That's driving my crazy.


    I don't understand.

    Making those functions PURE (by adding = 0), doesn't mean that
    you cannot provide an implementation for them :)

    --
    Karl Heinz Buchegger
     
    Karl Heinz Buchegger, Aug 18, 2004
    #8
  9. N4M

    Daniel T. Guest

    (N4M) wrote:

    > Dear,
    > Suppose I have a Protocol class, in which I need also an assignment
    > operator =().
    > class B
    > {
    > ....
    > virtual B& operator=(const B& rb) =0;
    > };
    > Now in some derived class D: public B, how would I proceed with
    > operator=?


    Let's consider this tree:

    class B {
    public:
    virtual ~B() { }
    virtual B& operator=( const B& ) = 0;
    };

    class D {
    int foo;
    public:
    virtual B& operator=( const B& );
    };

    class C {
    double bar;
    public:
    virtual B& operator=( const B& );
    };

    Can either D::eek:p= or C::eek:p= be implemented reasonably? I don't think so.
    What is supposed to happen with this code?

    void fun( B& b1, B& b2 ) {
    b1 = b2;
    }

    If b1 is an D and b2 is a C?


    > Do I need to supply 2 operators:
    > -01 to override D& operator=(const B& d)


    You need to provide this one.

    > -and 01 to overload D& operator=(const D& d)?


    You would only need this one if something in the class requires it,
    otherwise it will be created properly for you.

    The moral of the story here is "don't provide a pure virtual op=".
     
    Daniel T., Aug 18, 2004
    #9
  10. "Daniel T." <> wrote in message news:postmaster->
    > Let's consider this tree:
    >
    > class B {
    > public:
    > virtual ~B() { }
    > virtual B& operator=( const B& ) = 0;
    > };
    >
    > class D {
    > int foo;
    > public:
    > virtual B& operator=( const B& );
    > };
    >
    > class C {
    > double bar;
    > public:
    > virtual B& operator=( const B& );
    > };
    >
    > Can either D::eek:p= or C::eek:p= be implemented reasonably?


    Absolutely. Here's one way:

    B &D::eek:perator=( const B &r )
    {
    const D *p = dynamic_cast<const D *>(&r);
    if( p )
    foo = p->foo;
    else
    throw "Silly assignment!";
    return *this;
    }

    B &C::eek:perator=( const B &r )
    {
    const C *p = dynamic_cast<const C *>(&r);
    if( p )
    bar = p->bar;
    else
    throw "Silly assignment!";
    return *this;
    }

    > What is supposed to happen with this code?
    >
    > void fun( B& b1, B& b2 ) {
    > b1 = b2;
    > }
    >
    > If b1 is an D and b2 is a C?


    With virtual assignment, it would throw a "Silly assignment!"
    exception (or alternatively, whatever else the author of the
    class D deems appropriate for the case when, so to speak,
    an Orange is assigned to a Banana thru a reference to Fruit).

    WithOUT virtual assignment it would create a banana the size
    of a grape, with an orange skin that doesn't need to be peeled
    before eating.

    > The moral of the story here is "don't provide a pure virtual op=".


    This belief, I think, needs reconsideration.

    - Risto -
     
    Risto Lankinen, Aug 18, 2004
    #10
  11. N4M

    Daniel T. Guest

    In article <IuHUc.23123$>,
    "Risto Lankinen" <> wrote:

    > "Daniel T." <> wrote in message news:postmaster->
    > > Let's consider this tree:
    > >
    > > class B {
    > > public:
    > > virtual ~B() { }
    > > virtual B& operator=( const B& ) = 0;
    > > };
    > >
    > > class D {
    > > int foo;
    > > public:
    > > virtual B& operator=( const B& );
    > > };
    > >
    > > class C {
    > > double bar;
    > > public:
    > > virtual B& operator=( const B& );
    > > };
    > >
    > > Can either D::eek:p= or C::eek:p= be implemented reasonably?

    >
    > Absolutely. Here's one way:
    >
    > B &D::eek:perator=( const B &r )
    > {
    > const D *p = dynamic_cast<const D *>(&r);
    > if( p )
    > foo = p->foo;
    > else
    > throw "Silly assignment!";
    > return *this;
    > }
    >
    > B &C::eek:perator=( const B &r )
    > {
    > const C *p = dynamic_cast<const C *>(&r);
    > if( p )
    > bar = p->bar;
    > else
    > throw "Silly assignment!";
    > return *this;
    > }


    I wouldn't call that a reasonable implementation, but maybe that's just
    me. If the "Silly assignment" is thrown, the calling code can't do
    anything resonable with it and in order for the calling code to avoid
    the throw, it must use RTTI to ensure both arguments are the same
    derived type, then call op= which again uses dynamic_cast. That sounds
    quite expensive...
     
    Daniel T., Aug 18, 2004
    #11
  12. N4M

    N4M Guest

    > This belief, I think, needs reconsideration.
    >
    > - Risto -


    How do you think about this solution:

    class D; //forward
    class B
    {
    public:
    virtual ~B() = 0;
    virtual B& operator=(const B&b) = 0;
    virtual void Assign(D&) const = 0;
    };
    B::~B() {}
    class D: public B
    {
    public:
    explicit D(int i):m_n(i) {}
    ~D() {}
    B& operator=(const B& rb) {
    rb.Assign(*this);
    return dynamic_cast<B&>(*this);//static_cast is fine, too.
    }
    virtual void Assign(D& rd) const { rd.m_n = m_n;}
    int GetValue() {return m_n;}
    private:
    int m_n;

    };
    // Test
    void Test(B& b1, B& b2)
    {
    b1 = b2;
    }
    //
    int main()
    {
    D d1(1), d2(2);
    cout<<"Before d1, d2:"<< d1.GetValue() <<","<<d2.GetValue()<<endl;
    Test(d1,d2);
    cout<<"After d1, d2:"<< d1.GetValue() <<","<<d2.GetValue()<<endl;
    }
    I have tested this, it's fine, and it serves the purpose that all
    Derived classes need to define the assignment operator indirectly thru
    Assign().
    Nguyen Mai.
     
    N4M, Aug 18, 2004
    #12
  13. "Daniel T." <> wrote in message
    news:p...
    >
    > If the "Silly assignment" is thrown, the calling code can't do
    > anything resonable with it...


    First, I'll repaste a section deleted from my original article:

    >> With virtual assignment, it would throw a "Silly assignment!"
    >> exception (or alternatively, whatever else the author of the
    >> class D deems appropriate ...


    If you deem the default behaviour (by which I mean simulating
    the non-virtual assignment) more appropriate, you could do the
    following:

    B &D::eek:perator=( const B &r )
    {
    const D *p = dynamic_cast<const D *>(&r);'
    if( p )
    // Calls D::eek:perator=( const D & ); Explicit call deliberately
    // not used to enable late binding for D::eek:p=(D) in case it has
    // been declared virtual in this class and similarly overloaded
    // in the next level derived class:
    return *this = *p;
    else
    return B::eek:perator=( p );
    }

    > ... and in order for the calling code to avoid the throw it must
    > use RTTI to ensure both arguments are the same derived type,
    > then call op= which again uses dynamic_cast. That sounds
    > quite expensive...


    If RTTI is too costly, simply leave it away! But then you can't
    completely emulate Derived-to-Derived assignment thru Base
    pointers or references. This will still be useful for detecting the
    slicing (e.g. to retain the class-level invariants of D consistent
    even if someone assigns only a Base part to it):

    B &D::eek:perator=( const B &r )
    {
    B &result = B::eek:perator=( r );
    // This 'D' was just sliced by a 'B' called 'r'! Counteract!
    return result;
    }

    Without virtual assignment in base class, this would be impossible.

    Cheers!

    - Risto -
     
    Risto Lankinen, Aug 19, 2004
    #13
  14. N4M

    Daniel T. Guest

    In article <>,
    (N4M) wrote:

    > > This belief, I think, needs reconsideration.
    > >
    > > - Risto -

    >
    > How do you think about this solution:
    >
    > class D; //forward
    > class B
    > {
    > public:
    > virtual ~B() = 0;
    > virtual B& operator=(const B&b) = 0;
    > virtual void Assign(D&) const = 0;
    > };
    > B::~B() {}
    > class D: public B
    > {
    > public:
    > explicit D(int i):m_n(i) {}
    > ~D() {}
    > B& operator=(const B& rb) {
    > rb.Assign(*this);
    > return dynamic_cast<B&>(*this);//static_cast is fine, too.
    > }
    > virtual void Assign(D& rd) const { rd.m_n = m_n;}
    > int GetValue() {return m_n;}
    > private:
    > int m_n;
    >
    > };
    > // Test
    > void Test(B& b1, B& b2)
    > {
    > b1 = b2;
    > }
    > //
    > int main()
    > {
    > D d1(1), d2(2);
    > cout<<"Before d1, d2:"<< d1.GetValue() <<","<<d2.GetValue()<<endl;
    > Test(d1,d2);
    > cout<<"After d1, d2:"<< d1.GetValue() <<","<<d2.GetValue()<<endl;
    > }
    > I have tested this, it's fine, and it serves the purpose that all
    > Derived classes need to define the assignment operator indirectly thru
    > Assign().


    You end up needing to add an assign to the base class and implement a
    new assign method in every other derived class whenever you to create a
    derived class. Very ugly...
     
    Daniel T., Aug 19, 2004
    #14
  15. N4M

    Daniel T. Guest

    (N4M) wrote:

    > Dear,
    > Suppose I have a Protocol class, in which I need also an assignment
    > operator =().
    > class B
    > {
    > ....
    > virtual B& operator=(const B& rb) =0;
    > };
    > Now in some derived class D: public B, how would I proceed with
    > operator=? Do I need to supply 2 operators:
    > -01 to override D& operator=(const B& d)
    > -and 01 to overload D& operator=(const D& d)?
    > How about the (im)purality of = ?
    > Thanks for your guidance.


    I've been thinking more about this whole situation... Given the above
    class (ie op= is pure virtual and the class has no data members) what is
    the post-condition of the op=? I mean, usually the post-condition of op=
    is that *this == rb after the function, but would:

    bool fun( B& r, B& l ) {
    return r == l;
    }

    even compile? If yes, under what conditions should the above return
    false? Under what condtions should it return true?
     
    Daniel T., Aug 19, 2004
    #15
  16. N4M

    Daniel T. Guest

    "Risto Lankinen" <> wrote:

    > Consider this example:

    [formatting changed some to save space. I also added a pure virtual
    to the base to make it an ABC like the origional example.]
    >
    > // ========================================
    >
    > struct B
    > {

    virtual void foo() = 0;
    > virtual B &operator=( const B & );
    > virtual ~B();
    > };
    >
    >
    > struct D : public B
    > {
    > int value;
    > D( int i=0 ) : value(i) { }
    >
    > D( const D &r ) : B(r) , value(r.value) { }

    void foo() { }
    >
    > virtual D &operator=( const D &r ) {
    > B::eek:perator=( r );
    > value = r.value;
    > return *this;
    > }
    >
    > virtual B &operator=( const B &r ) {
    > const D *p = dynamic_cast<const D *>(&r);
    > if( p ) {
    > *this = *p;
    > }
    > else {
    > // D is being sliced, but now you can detect and compensate!!!
    > B::eek:perator=( r );
    > value = 0;
    > }
    > return *this;
    > }
    > };


    Using the example above, given this code:

    struct E: public B {
    float value;
    B(): value( 0.01 ) { }
    void foo() { }
    B& operator=( const B& r ) {
    const E* p = dynamic_cast< const E* >( &r );
    if ( p )
    *this = *p;
    else {
    B::eek:perator=( r );
    value = 0.01;
    }
    return *this;
    }
    };

    void ick( B& l, B& r ) {
    l = r;
    }

    int main() {
    E e;
    D d;

    ick( d, e );
    }

    Are 'd' and 'e' supposed to be equal after the call to 'ick'? Could 'd'
    and 'e' ever be equal? If not, then what is B's op= supposed to do?
     
    Daniel T., Aug 19, 2004
    #16
  17. "Daniel T." <> wrote in message
    news:p...
    > "Risto Lankinen" <> wrote:
    >
    > > Consider this example:

    > [formatting changed some to save space. I also added a pure virtual
    > to the base to make it an ABC like the origional example.]
    > >
    > > // ========================================
    > >
    > > struct B
    > > {

    > virtual void foo() = 0;
    > > virtual B &operator=( const B & );
    > > virtual ~B();
    > > };
    > >
    > >
    > > struct D : public B
    > > {
    > > int value;
    > > D( int i=0 ) : value(i) { }
    > >
    > > D( const D &r ) : B(r) , value(r.value) { }

    > void foo() { }
    > >
    > > virtual D &operator=( const D &r ) {
    > > B::eek:perator=( r );
    > > value = r.value;
    > > return *this;
    > > }
    > >
    > > virtual B &operator=( const B &r ) {
    > > const D *p = dynamic_cast<const D *>(&r);
    > > if( p ) {
    > > *this = *p;
    > > }
    > > else {
    > > // D is being sliced, but now you can detect and compensate!!!
    > > B::eek:perator=( r );
    > > value = 0;
    > > }
    > > return *this;
    > > }
    > > };

    >
    > Using the example above, given this code:
    >
    > struct E: public B {
    > float value;
    > B(): value( 0.01 ) { }
    > void foo() { }
    > B& operator=( const B& r ) {
    > const E* p = dynamic_cast< const E* >( &r );
    > if ( p )
    > *this = *p;
    > else {
    > B::eek:perator=( r );
    > value = 0.01;
    > }
    > return *this;
    > }
    > };
    >
    > void ick( B& l, B& r ) {
    > l = r;
    > }
    >
    > int main() {
    > E e;
    > D d;
    >
    > ick( d, e );
    > }
    >
    > Are 'd' and 'e' supposed to be equal after the call to 'ick'?


    Well, yes, if you use the same definition of "equal" as
    when B &B::eek:p=(B) is non-virtual.

    > Could 'd' and 'e' ever be equal?


    You tell me. Sure you knew that if you implemented
    bool operator(B,B) you'd be able compare a C to a D
    anyway (again, regardless of whether the assignments
    occurred thru virtual or non-virtual operators).

    Nevertheless, I don't see it as a big problem that op=()
    and op==() lose a bit of consistency in the presence of
    type conversions. Even C with no operator overloading
    or virtual methods manifests a similar inconsistency:

    int i = 12;
    double d = 3.4;
    i = d;
    assert( i == d );


    Cheers!

    - Risto -
     
    Risto Lankinen, Aug 19, 2004
    #17
  18. N4M

    Daniel T. Guest

    In article <pD2Vc.23249$>,
    "Risto Lankinen" <> wrote:

    > "Daniel T." <> wrote in message
    > news:p...
    > > "Risto Lankinen" <> wrote:
    > >
    > > void ick( B& l, B& r ) {
    > > l = r;
    > > }
    > >
    > > int main() {
    > > E e;
    > > D d;
    > >
    > > ick( d, e );
    > > }
    > >
    > > Are 'd' and 'e' supposed to be equal after the call to 'ick'?

    >
    > Well, yes, if you use the same definition of "equal" as
    > when B &B::eek:p=(B) is non-virtual.


    That's a little hard to do when B::eek:p=(B) is pure-virtual...

    > > Could 'd' and 'e' ever be equal?

    >
    > You tell me. Sure you knew that if you implemented
    > bool operator==(B,B) you'd be able compare a C to a D
    > anyway (again, regardless of whether the assignments
    > occurred thru virtual or non-virtual operators).


    Let's do that:

    struct Base {
    int x;
    Base( int a ): x(a) { }
    virtual ~Base() { }
    };

    bool operator==( const Base& l, const Base& r ) {
    return l.x == r.x;
    }

    //====================================================================
    struct Derived : Base {
    int y;
    Derived( int a, int b ): Base( a ), y( b ) { }
    };

    //====================================================================
    void comp( Base& l, Base& r ) {
    assert( l == r );
    }

    int main() {
    Derived d1( 0, 1 ), d2( 0, 2 );
    comp( d1, d2 );
    cout << "OK";
    }

    (The above prints "OK")
    Is this really something anyone would want?

    > Nevertheless, I don't see it as a big problem that op=()
    > and op==() lose a bit of consistency in the presence of
    > type conversions. Even C with no operator overloading
    > or virtual methods manifests a similar inconsistency:
    >
    > int i = 12;
    > double d = 3.4;
    > i = d;
    > assert( i == d );


    And that causes no end of trouble for those who think that double should
    be substitutable for int. It isn't.

    By using inheritance (rather than say a conversion function) we are
    saying that any type Derived from Base is subsitutable for it.
     
    Daniel T., Aug 19, 2004
    #18
    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. Stephan Kurpjuweit
    Replies:
    3
    Views:
    3,602
    Andrey Tarasevich
    Jun 14, 2004
  2. G Patel

    comma operator and assignment operator

    G Patel, Feb 7, 2005, in forum: C Programming
    Replies:
    4
    Views:
    495
    Barry Schwarz
    Feb 8, 2005
  3. Chris
    Replies:
    34
    Views:
    1,535
  4. Replies:
    2
    Views:
    343
    James Kanze
    Apr 13, 2007
  5. sukhpal
    Replies:
    5
    Views:
    358
    James Kanze
    Feb 9, 2009
Loading...

Share This Page