Looking for right idiom

Discussion in 'C++' started by Johannes Bauer, Aug 23, 2012.

  1. Hi group,

    I'm looking for a OO idiom and currently just have a knot in my brain.
    I'll explain what the result should be and hopefully somebody can help
    me out.

    Let's assume I have a class A which has a method setValue(int, int).
    Let's further assume that the operation is quite expensive (for example,
    think of a database commit). However, in order to keep complexity low,
    consecutive setValue() operations can be combined. Assume for example that

    a.setValue(w, x)
    a.setValue(y, z)

    would be equivalent to

    a.setValue((w ^ y) * z, z - x)

    i.e. the expensive setValue() operation would be executed only once at
    the cost of cheap arithmetic operations.

    The way I would like this to behave is have the setValue() operation
    return some kind of cascadable object that does a "commit" when the last
    operation has finished. In other words, it should be possible to:

    a.setValue(w, x).setValue(y, z)

    (and cascade them infinitely) so that the operands are first all fully
    evaluated and then there's only one commit action.

    How is this possible with the least overhead in times of runtime
    complexity? I don't really have any good ideas here. Would be great if
    you could help me out.

    Best regards,
    Joe


    --
    >> Wo hattest Du das Beben nochmal GENAU vorhergesagt?

    > Zumindest nicht öffentlich!

    Ah, der neueste und bis heute genialste Streich unsere großen
    Kosmologen: Die Geheim-Vorhersage.
    - Karl Kaos über Rüdiger Thomas in dsa <hidbv3$om2$>
    Johannes Bauer, Aug 23, 2012
    #1
    1. Advertising

  2. On 8/23/2012 6:33 AM, Johannes Bauer wrote:
    > I'm looking for a OO idiom and currently just have a knot in my brain.
    > I'll explain what the result should be and hopefully somebody can help
    > me out.
    >
    > Let's assume I have a class A which has a method setValue(int, int).
    > Let's further assume that the operation is quite expensive (for example,
    > think of a database commit). However, in order to keep complexity low,
    > consecutive setValue() operations can be combined. Assume for example that
    >
    > a.setValue(w, x)
    > a.setValue(y, z)
    >
    > would be equivalent to
    >
    > a.setValue((w ^ y) * z, z - x)
    >
    > i.e. the expensive setValue() operation would be executed only once at
    > the cost of cheap arithmetic operations.
    >
    > The way I would like this to behave is have the setValue() operation
    > return some kind of cascadable object that does a "commit" when the last
    > operation has finished. In other words, it should be possible to:
    >
    > a.setValue(w, x).setValue(y, z)
    >
    > (and cascade them infinitely) so that the operands are first all fully
    > evaluated and then there's only one commit action.
    >
    > How is this possible with the least overhead in times of runtime
    > complexity? I don't really have any good ideas here. Would be great if
    > you could help me out.


    This is untested code. Typed in off the top of my head.

    struct A {
    struct SetterProxy {
    SetterProxy(int, int, A&);
    SetterProxy& setValue(int,int);
    ~SetterProxy();
    private:
    A& m_owner;
    // other members needed to keep the cache
    };
    friend SetterProxy; // need this to call 'perform'
    SetterProxy setValue(int,int);

    private:
    void performLongSetOperation(/* args */);
    };

    A::SetterProxy::SetterProxy(int x, int y, A& owner)
    : m_owner(owner)
    {
    /* other stuff for setting up cache */
    }

    void A::SetterProxy::~SetterProxy()
    {
    m_owner.performLongSetOperation(/* whatever */);
    }

    A::SetterProxy A::setValue(int x, int y)
    {
    return SetterProxy(x, y, *this);
    }

    A::SetterProxy& A::SetterProxy::setValue(int x, int y)
    {
    // add 'x' and 'y' to the cache...
    return *this;
    }

    void A::performLongSetOperation(/* args */)
    {
    // ... whatever
    }

    Can you fill in the rest?

    V
    --
    I do not respond to top-posted replies, please don't ask
    Victor Bazarov, Aug 23, 2012
    #2
    1. Advertising

  3. Johannes Bauer

    Öö Tiib Guest

    On Thursday, August 23, 2012 1:33:19 PM UTC+3, Johannes Bauer wrote:
    > Let's assume I have a class A which has a method setValue(int, int).
    > Let's further assume that the operation is quite expensive (for example,
    > think of a database commit). However, in order to keep complexity low,
    > consecutive setValue() operations can be combined. Assume for example that
    >
    > a.setValue(w, x)
    > a.setValue(y, z)
    >
    > would be equivalent to
    >
    > a.setValue((w ^ y) * z, z - x)
    >
    > i.e. the expensive setValue() operation would be executed only once at
    > the cost of cheap arithmetic operations.


    Ok, the idiom is "laziness". Typically when you traverse the net you find examples of lazy initialization but anything can be made lazy.

    Make your "value setter" to collect the not yet done work (or partially done work) until user explicitly calls commit(). You may want to check that the work has been actually completed in destructor. If the destructor then commit()'s itself or complains (avoid exceptions there) about a bug is matterof how explicit interface you like.


    Chaining is usually assumed to be just a syntax convenience thing and done by returning *this from setValue(). So:

    result = a.setValue(w, x).setValue(y, z).commit();

    Is just convenient way to say:

    a.setValue(w, x);
    a.setValue(y, z);
    result = a.commit();
    Öö Tiib, Aug 23, 2012
    #3
  4. On 23.08.2012 13:49, Victor Bazarov wrote:

    > This is untested code. Typed in off the top of my head.


    Thank you very much, Victor. It worked perfectly and is just what I was
    looking for (my solutions were MUCH more clumsy). gcc actually doesn't
    complain when the friend declaration is missing, are inner classes
    always friends or must the declaration be there?

    I especially like what gcc does with the code. On x86-64 with gcc 4.6.2
    with -O3 the surrogate object is completely optimized away and just the
    actual "perform" tasks are done. This is wonderful! Fantastic.

    Here's a link to your code (I merely filled in the blanks and renamed
    some stuff and inserted logging in case anyone else is interested):

    http://pastebin.com/ghUrmNpi

    In case this doesn't get archived, I'll also post the full source code
    below this message.

    Thank you again for giving me the correct direction. I'll implement this
    right now :)

    Best regards,
    Joe


    Modified code of Victor (same as Pastebin):
    #include <iostream>

    //#define LOGGING

    volatile int fooVar;

    class MyObject {
    private:
    class SetterProxy {
    private:
    MyObject &owner;
    int xCache, yCache;

    public:
    SetterProxy(int aX, int aY, MyObject &aOwner) : owner(aOwner) {
    #ifdef LOGGING
    std::cerr << "Proxy Construct " << aX << " / " << aY << std::endl;
    #endif
    xCache = aX;
    yCache = aY;
    }

    SetterProxy& setValue(int aX, int aY) {
    #ifdef LOGGING
    std::cerr << "Proxy Set " << aX << " / " << aY << std::endl;
    #endif
    xCache += aX;
    yCache += aY;
    return *this;
    }

    ~SetterProxy() {
    owner.performLongSetOperation(xCache, yCache);
    }
    };

    void performLongSetOperation(int aX, int aY) {
    #ifdef LOGGING
    std::cerr << "Perform: " << aX << " / " << aY << std::endl;
    #endif
    fooVar = aX + aY;
    }

    public:
    SetterProxy setValue(int aParam1, int aParam2) {
    return SetterProxy(aParam1, aParam2, *this);
    }
    };


    int main(int argc, char **argv) {
    MyObject obj;
    obj.setValue(1, 2);
    obj.setValue(1, 2).setValue(10, 20);
    obj.setValue(1, 2).setValue(10, 20).setValue(100, 200);
    obj.setValue(1, 2).setValue(10, 20).setValue(100, 200).setValue(1000,
    2000);
    obj.setValue(1, 2).setValue(10, 20).setValue(100, 200).setValue(1000,
    2000).setValue(argc, 9);
    return 0;
    }



    --
    >> Wo hattest Du das Beben nochmal GENAU vorhergesagt?

    > Zumindest nicht öffentlich!

    Ah, der neueste und bis heute genialste Streich unsere großen
    Kosmologen: Die Geheim-Vorhersage.
    - Karl Kaos über Rüdiger Thomas in dsa <hidbv3$om2$>
    Johannes Bauer, Aug 23, 2012
    #4
  5. On 8/23/2012 9:12 AM, Johannes Bauer wrote:
    > [..]
    > void performLongSetOperation(int aX, int aY) {
    > #ifdef LOGGING
    > std::cerr << "Perform: " << aX << " / " << aY << std::endl;

    .. ^^^^
    > #endif
    > fooVar = aX + aY;

    .. ^^^^
    > }
    > [..]


    :)

    V
    --
    I do not respond to top-posted replies, please don't ask
    Victor Bazarov, Aug 23, 2012
    #5
  6. Johannes Bauer

    Jorgen Grahn Guest

    On Thu, 2012-08-23, Johannes Bauer wrote:
    > Hi group,
    >
    > I'm looking for a OO idiom and currently just have a knot in my brain.
    > I'll explain what the result should be and hopefully somebody can help
    > me out.
    >
    > Let's assume I have a class A which has a method setValue(int, int).
    > Let's further assume that the operation is quite expensive (for example,
    > think of a database commit). However, in order to keep complexity low,
    > consecutive setValue() operations can be combined. Assume for example that
    >
    > a.setValue(w, x)
    > a.setValue(y, z)
    >
    > would be equivalent to
    >
    > a.setValue((w ^ y) * z, z - x)
    >
    > i.e. the expensive setValue() operation would be executed only once at
    > the cost of cheap arithmetic operations.
    >
    > The way I would like this to behave is have the setValue() operation
    > return some kind of cascadable object that does a "commit" when the last
    > operation has finished. In other words, it should be possible to:


    I suspect that much of the time, it's better to accept that this /is/
    something expensive, and expose that fact in the interface. I.e. have
    an explicit a.commit() somewhere.

    (Note that I'm not saying it's /always/ a bad idea to make it
    automatic. File I/O for example -- it would not be convenient in the
    general case to have to keep track of how much you've written so you
    can flush at the right moment.)

    /Jorgen

    --
    // Jorgen Grahn <grahn@ Oo o. . .
    \X/ snipabacken.se> O o .
    Jorgen Grahn, Aug 23, 2012
    #6
  7. On 8/23/2012 5:51 PM, Jorgen Grahn wrote:
    > On Thu, 2012-08-23, Johannes Bauer wrote:
    >> Hi group,
    >>
    >> I'm looking for a OO idiom and currently just have a knot in my brain.
    >> I'll explain what the result should be and hopefully somebody can help
    >> me out.
    >>
    >> Let's assume I have a class A which has a method setValue(int, int).
    >> Let's further assume that the operation is quite expensive (for example,
    >> think of a database commit). However, in order to keep complexity low,
    >> consecutive setValue() operations can be combined. Assume for example that
    >>
    >> a.setValue(w, x)
    >> a.setValue(y, z)
    >>
    >> would be equivalent to
    >>
    >> a.setValue((w ^ y) * z, z - x)
    >>
    >> i.e. the expensive setValue() operation would be executed only once at
    >> the cost of cheap arithmetic operations.
    >>
    >> The way I would like this to behave is have the setValue() operation
    >> return some kind of cascadable object that does a "commit" when the last
    >> operation has finished. In other words, it should be possible to:

    >
    > I suspect that much of the time, it's better to accept that this /is/
    > something expensive, and expose that fact in the interface. I.e. have
    > an explicit a.commit() somewhere.
    >
    > (Note that I'm not saying it's /always/ a bad idea to make it
    > automatic. File I/O for example -- it would not be convenient in the
    > general case to have to keep track of how much you've written so you
    > can flush at the right moment.)


    IOW, there is no general recommendation, is there? By the same token,
    the 'Setter' could keep track of any sort of parameter of the operation
    (like how many 'setValue' calls there have been, or how much time has
    passed since it was created or since last 'flush') and perform that
    lengthy operation (commit) according to some other heuristic.

    V
    --
    I do not respond to top-posted replies, please don't ask
    Victor Bazarov, Aug 24, 2012
    #7
  8. Johannes Bauer

    Öö Tiib Guest

    On Friday, August 24, 2012 3:22:49 AM UTC+3, Victor Bazarov wrote:
    > On 8/23/2012 5:51 PM, Jorgen Grahn wrote:
    > > I suspect that much of the time, it's better to accept that this /is/
    > > something expensive, and expose that fact in the interface. I.e. have
    > > an explicit a.commit() somewhere.
    > >
    > > (Note that I'm not saying it's /always/ a bad idea to make it
    > > automatic. File I/O for example -- it would not be convenient in the
    > > general case to have to keep track of how much you've written so you
    > > can flush at the right moment.)

    >
    > IOW, there is no general recommendation, is there? By the same token,
    > the 'Setter' could keep track of any sort of parameter of the operation
    > (like how many 'setValue' calls there have been, or how much time has
    > passed since it was created or since last 'flush') and perform that
    > lengthy operation (commit) according to some other heuristic.


    First it is good to be lazy and wait with implementing such laziness
    until no one can call it a "preliminary optimization".
    When done it may be still good to be more explicit but since the code
    that uses the 'Setter' is already there the full automation will be
    implemented. When it raises some other issues then some ways to 'flush'
    manually will be added.
    Öö Tiib, Aug 24, 2012
    #8
  9. Johannes Bauer

    Jorgen Grahn Guest

    On Fri, 2012-08-24, Victor Bazarov wrote:
    > On 8/23/2012 5:51 PM, Jorgen Grahn wrote:
    >> On Thu, 2012-08-23, Johannes Bauer wrote:
    >>> Hi group,
    >>>
    >>> I'm looking for a OO idiom and currently just have a knot in my brain.
    >>> I'll explain what the result should be and hopefully somebody can help
    >>> me out.
    >>>
    >>> Let's assume I have a class A which has a method setValue(int, int).
    >>> Let's further assume that the operation is quite expensive (for example,
    >>> think of a database commit). However, in order to keep complexity low,
    >>> consecutive setValue() operations can be combined. Assume for example that
    >>>
    >>> a.setValue(w, x)
    >>> a.setValue(y, z)
    >>>
    >>> would be equivalent to
    >>>
    >>> a.setValue((w ^ y) * z, z - x)
    >>>
    >>> i.e. the expensive setValue() operation would be executed only once at
    >>> the cost of cheap arithmetic operations.
    >>>
    >>> The way I would like this to behave is have the setValue() operation
    >>> return some kind of cascadable object that does a "commit" when the last
    >>> operation has finished. In other words, it should be possible to:

    >>
    >> I suspect that much of the time, it's better to accept that this /is/
    >> something expensive, and expose that fact in the interface. I.e. have
    >> an explicit a.commit() somewhere.
    >>
    >> (Note that I'm not saying it's /always/ a bad idea to make it
    >> automatic. File I/O for example -- it would not be convenient in the
    >> general case to have to keep track of how much you've written so you
    >> can flush at the right moment.)

    >
    > IOW, there is no general recommendation, is there? By the same token,
    > the 'Setter' could keep track of any sort of parameter of the operation
    > (like how many 'setValue' calls there have been, or how much time has
    > passed since it was created or since last 'flush') and perform that
    > lengthy operation (commit) according to some other heuristic.


    Yes. My point was just that all this can be overkill, and make the
    code less clear. Even though it's a good technique to be familiar
    with.

    /Jorgen

    --
    // Jorgen Grahn <grahn@ Oo o. . .
    \X/ snipabacken.se> O o .
    Jorgen Grahn, Aug 24, 2012
    #9
    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. Tom
    Replies:
    0
    Views:
    412
  2. =?Utf-8?B?QmlzaG95?=
    Replies:
    0
    Views:
    962
    =?Utf-8?B?QmlzaG95?=
    Dec 28, 2006
  3. Paul  Moore
    Replies:
    33
    Views:
    701
    Hendrik van Rooyen
    Dec 16, 2008
  4. Bogdan
    Replies:
    1
    Views:
    767
    Bogdan
    Jun 16, 2009
  5. Leon
    Replies:
    3
    Views:
    160
    TaeHo Yoo
    Nov 26, 2004
Loading...

Share This Page