Looking for right idiom

J

Johannes Bauer

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


--
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 <[email protected]>
 
V

Victor Bazarov

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
 
Ö

Öö Tiib

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();
 
J

Johannes Bauer

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;
}



--
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 <[email protected]>
 
J

Jorgen Grahn

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
 
V

Victor Bazarov

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
 
Ö

Öö Tiib

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.
 
J

Jorgen Grahn

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
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,955
Messages
2,570,117
Members
46,705
Latest member
v_darius

Latest Threads

Top